|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑
! L3 P3 ?! D$ c% Z- m g+ ^. w; G5 C" V2 c3 M1 D
[FC][SRAM扩容教程(Mapper 4为例)]! F8 i2 m0 N# j4 _# ~
+ h6 N( Y) l9 V时间:2017.4.28! D* r4 `- F+ W$ n- C' k W6 T& u
作者:FlameCyclone
+ T! L- X6 K8 c: {/ x工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
3 n; |! {( e& b6 W+ V+ x% i- Z0 zROM:双截龙2(J).nes
5 `6 `3 Z9 X! u' q/ q适用:没有使用SRAM的ROM' o8 k7 r) T; N3 J
* V$ Q' D8 N. p1 X
首先用Hxd打开ROM:) R8 b* Q( l+ r6 x( Z; k
4 ?6 H# @. i v3 x8 Y$ U7 q; Q然后扩容:" k: _: m$ U& p, J5 l
/ R* c: _6 m5 F+ ~6 L. N1 w0 G( ?- j% h+ |$ V
( O4 `& u/ P/ N* |7 F- C& @3 ~" _
" q2 O7 |; M* c$ _+ N9 L
. m; Q" ?& o/ N% q& n A: Z2 `6 I- i; L+ K, a
+ W" h! p! t! v1 P
! z" }8 f! q1 {8 J h/ p
5 _: p- }5 e$ B. Z* W. l+ r
先看看任天堂产品系统文件对NES文件的说明:/ Z% u% j# b S/ T- p- C' [
NES文件格式) i% \ R& N; s! S
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。
a. S1 E; V0 ^; K" D) X- \偏移 字节数 内容
& d+ t# k0 j8 |, b; I: p% _4 z0-3 4 字符串“NES^Z”用来识别.NES文件 0 g# ~" g, d. _: h- [
4 1 16kB ROM的数目
' u( V0 ?9 b9 ^; N3 h0 }6 W5 1 8kB VROM的数目
) d5 a% q+ w' H ?1 N6 1 D0:1=垂直镜像,0=水平镜像
4 |, `. c o% s1 ^% c ?3 n% N D1:1=有电池记忆,SRAM地址$6000-$7FFF ) r! H, d/ c) h4 Y9 n4 G
D2:1=在$7000-$71FF有一个512字节的trainer 3 d8 \9 ~' |/ @' x% H
D3:1=4屏幕VRAM布局 # d) c2 i- ^ ?. k- [% |
D4-D7:ROM Mapper的低4位 " A# z6 H; Z+ w6 O; V
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^)
5 p0 V2 [' o3 [6 X( p% K D4-D7:ROM Mapper的高4位 3 h+ |( f2 y; b$ ^
8-F 8 保留,必须是0 1 g0 q6 s N$ }$ g$ k& o! Z+ R
16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
0 {8 c1 N. y- ]-EOF 8KxN VROM段, 升序排列 4 K5 }9 |, |6 i* v8 @
8 t% ]2 ]: {' c+ \6 W然后知道这个ROM有0x08个PROM和0x10个VROM$ D F" h Y! y( \! ^
接下来扩展PROM位0x10个:# U3 T3 P6 v& }- o
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):9 [- B. [6 i) ~! F. a& d
) l2 B/ G1 |/ O) _ \8 }/ u3 t
由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:
# D) l) S5 ^/ v5 [% u9 Y6 {1 W5 J) D最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
9 G0 J, S, G. P- n# h2 ~于是可以得到双截龙2的是:* ]/ o6 e- X2 D O/ ?' `6 ~
(8-1)x 0x4000 + 0x10 = 1C010。
: g( u: G! Q0 k. w7 w: l) Z& b) c然后跳转到1C010:
. U; W. ?& e; D5 S& H- |! V7 [6 ~) \% P" L1 O' `
; o$ }9 j( _5 f- [5 U
. t0 k- N) p t. o然后插入0x20000字节的FF:# W2 M6 U7 N( i" A
/ u$ \+ f2 E3 Y0 t$ P# U- s+ D( G$ Y: ?% k1 c' \# L, z5 a9 ]$ Q
d/ l* B. q6 O# B' I; Y" M; x然后保存:
# q/ j3 B* P8 H+ k* p" C/ Q
3 L& ]9 y) W" Z& D
% k& E) A+ e5 H( i, r7 T; P7 M i# F用FCEUX打开正常运行:' @) Z( |- O) S+ R( D
0 Y/ P, _/ u$ T: y查看文件信息:! X$ T5 c; ~+ F3 }+ l
9 F$ }- i( u2 _
* T5 @) M2 r. O& q; }接下来切页:
$ A; F d% H( x1 Z+ H& y1 c先打开十六进制编辑器:; b+ Y) X C5 I3 \
9 I: N, @% ^4 f. |拉到滑块最后,看看重启中断
# t# C. K; e# f* H: |中断地址 中断 优先权 3 a4 v; Z J) b4 L; S0 V( _
$FFFA NMI 中
! D- J0 `4 n4 f5 r0 \9 ]$FFFC RESET 高 ) D4 r* d# }+ }5 y' f
$FFFE IRQ/BRK 低
2 A3 \/ F3 p |4 p9 ^0 u" O! P. f& o$ c0 ^
RESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
1 @; Q3 i$ k9 P3 I7 ^% q; c4 I由此可知双截龙2的RESET中断$FF65。4 B+ y1 s/ f. k. t% d
接下来添加$FF65的执行断点:
& Q! _9 i1 s. l# X" z+ E0 K打开调试器:
( D) _4 q! A ^ L% p/ w. N5 _& w' r9 I; p$ ` M# }
添加$FF65的执行断点:
/ q2 |5 e. ?: B9 J+ ?$ T1 [/ }$ Z; a- n* y$ v2 |( N
: R& Y: h4 L9 v" ~
1 a/ z; X7 W+ I" x- P* _8 k: F1 Q4 _单击确定:: D' S9 u; V6 P8 k1 w( Z
Z, ~, }$ M+ S. o
6 e U1 V+ {2 e4 ^
然后重启ROM:8 f) [/ K! Y1 s
3 W( y2 b/ |; R% G调试器此时弹出来:) W$ P, z! \+ U+ _/ F
/ E- s9 p r# Z6 P# y然后打开Hxd,写一段mapper 4的切换bank程序:: M0 Z* P- p( Z; @
先看看mapper 4的说明文档:9 s/ Q; Y! t" F- j
Mapper 4) p( B, o! X7 | s
# O& m) M$ f1 ~
$8000: 模式号( o1 T+ x. {3 l4 o" F5 q- @1 T
位D0-D2:# ~. Y1 L; G8 i0 y' z
0:选择2KB的VROM存储体映射到PPU的$0000* N1 C. Z' V$ s
1:选择2KB的VROM存储体映射到PPU的$0800! r: ~# }3 B! R$ \' R
2:选择1KB的VROM存储体映射到PPU的$1000
: O& J1 n! u; r( F. y 3:选择1KB的VROM存储体映射到PPU的$14007 b# \3 y6 Y6 k2 w4 w# D
4:选择1KB的VROM存储体映射到PPU的$1800( q1 Y! D# ]/ Q( R+ b' p
5:选择1KB的VROM存储体映射到PPU的$1C00
% u3 b0 n' G. k/ t: o9 @) K5 M* \ 6:选择8KB的ROM存储体映射到$8000! Q$ V5 Q& [% i1 u; o
7:选择8KB的ROM存储体映射到$A0009 S$ X- k; v* F2 q5 z
位D6:) b1 L. l7 ]1 [
0:允许擦写$8000和$A000
. I3 O: D `& q7 ^0 k" k6 \1 f 1:允许擦写$A000和$C0008 ?5 Y* X: t2 g1 g$ _, e. P X
位D7:
* R1 t# g; ~2 j, k/ t 0:模式号D0-D2使用普通地址+ p5 k- k9 x/ d7 L# b$ @
1:模式号D0-D2地址异或$1000
0 H% y8 |! ^& w+ b( b9 m; B7 J- e% c$ w6 z3 t& M& c
$8001: 模式页面号
' G9 @* |. {5 F, ~% z$ s) a! l7 q5 @$ | 写入一个数(00-07),切换存储体到对应地址' {& D$ t7 Y4 G* `3 o0 v4 [% ~
8 ~# N4 V: Q5 _# c* C
$A000: 镜像选择
' U# c; C, ^/ `6 H 0:垂直镜像% o. @8 n2 B9 q' W( Y
1:水平镜像
+ z7 i0 v% ^' t# v0 f0 U4 ~
- c1 r7 E: m# I' k( P# Z4 `$A001: SaveRAM 切换
- H1 s8 ~7 b6 v& x9 N/ y* t( y" _ 0:禁用$6000-$7FFF9 N) W) Z" K: E4 m2 z5 G8 d1 E. @% B
1:启用$6000-$7FFF, I* `7 a9 \, u6 E
7 ~& _* F- U f6 l- O6 B* y' c
$C000: IRQ计数器/ o( D: P5 A3 [9 I8 Q+ q
IRQ计数器的值存储在此处
' p) ^7 ~3 p/ O, W
' [, G- D, X2 Q* ~5 G/ M$C001: IRQ暂存器" {0 w* t, |' b- D7 y2 ]5 d. G. V
IRQ暂存器的值存储在此处
3 T+ Z7 V% {/ V5 F0 @
# c( y/ B& V3 k4 q' M$E000: IRQ控制计数器0
4 Z. A1 y3 z: h+ t5 ]1 e 向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ1 T. D W7 q5 h
. k7 w }$ L4 r, u7 f6 a
$E001: IRQ控制计数器1
5 n' h4 p, M1 t% I# F 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)
" _: M0 }, S1 t: v* a2 F8 e# o" n3 P' y
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:* u( J+ U4 f; l& e
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68/ F& U# T8 j- T5 L* m
PHA 累加器A入栈+ ~, F% z3 w& O: z- d6 }8 A
LDA #$06 设置切bank地址为$8000-$9FFF
2 k2 m! h# {' o, VSTA $8000
7 h- B% W2 ~9 m# C6 I2 [# GLDA #$0E 将第0x0E号bank切到$8000-$9FFF5 g6 i9 X% G3 z4 l5 v: h$ E
STA $8001
' C0 J/ w/ F* ?) W- }- TJSR $8000 跳转到子程序$8000
# t* F% |' {6 K( Y- aPLA 累加器A出栈; J6 F5 Q" v8 z
1 S1 q/ p8 T0 U) W2 I, F1 K为何切换的bank号是0x0E呢?3 L: b" t3 J; e' X( i% Z0 H
因为在扩容ROM后,文件PROM结构为:1 w: n) n' P8 `0 J0 p# p# E1 l
原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM2 V& i3 k$ r' {7 a# i/ Y9 k. l- W; x
J" p4 Q- V( f8 U$ U, RMapper 4切bank一次是切8KB,那么文件结构就是:
`' f; {2 Q+ M6 A6 p# d原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)1 Y) t4 w o, {# u* A5 U
因此选择扩容的第一个空白bank就是0x0E号bank。; R# i( U* c* M }1 E
6 {/ o: q4 I' ~6 d& O6 e; h
" S; }: r7 u' [然后去调试器找RESET中断中可以放下切页程序的地方:
4 y% b# A# L) Y1 F* L首先长度要小于等于自己写的切页程序。
* M7 m% n8 E) |; O可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
- _& ?5 U1 h3 g9 M9 c& q1 j
* f, C# v0 k7 |0 t+ w7 N" i o然后跳转到$FF7C:8 ^' ]- v" S) u k2 D
+ X H6 X/ z! d# i I# z" V
' r$ X$ W: B! j
9 r/ X# Z( r) y/ V9 c5 f8 l& Q0 z. {8 o( e% w; D% l! Y
复制下可以被替换用于写切页的程序:+ J! C4 |$ H" w* F9 C
先选中,再复制:
$ N0 b9 G6 Y: |$ R' Y: g7 F& c
+ @3 @& m9 v2 {, G* x1 N# ~2 Y: N J- H: @; F
在Hxd中新建一个文件,把复制的数据粘贴上去:
8 D- S3 f1 e# y4 s+ z+ f0 ~/ h: A
! q S" N/ ]* Z
B8 |6 M; A& g. A1 T6 \! {' T
0 y+ E7 v$ }* t6 x$ X& w, U
( L* {) ?! f( d然后回到十六进制模拟器:! Q. q, i f+ B0 O: N2 E- @$ Z$ n
转到$FF7C对应的ROM地址:
) s7 x; B/ f7 x9 W( T/ l: S
! W; `; `# h" r+ [& @. _7 K" L f$ d z: I3 ~" |) O) W7 I) {, h
然后复制Hxd的切页程序,粘贴到这里: ~$ c" Z! R q" t
4 D; ?7 L* E0 d; C/ w- t; M
" r* j4 C. ^9 @
# d5 ~( M+ h" G! e. ?2 W" {) U. k# }没有覆盖的用EA覆盖:
6 `( i- |' z! y3 Z, {0 @: ^( x$ H) D7 Y4 f
打开调试器可以看到变化:
& Y( a# X, V' g) ^8 Q, M) K7 P4 Z
然后添加$FF7C执行断点:+ H5 I0 e, c7 I8 _8 M( t: o% ~5 h
: Y# x" D: Z6 i7 X7 W7 Y
. C6 H n+ ]0 M. J" @& {; z- ?' R, c% }/ K+ U
单击运行:
! n0 {( T( Y. O( E& _8 E( `& e然后程序在$FF7C这里停下来了。
+ O/ R, X3 }2 p4 v/ `& ~- C: [8 F; W( W2 P( W2 A: q! L, r
然后单击单步进入慢慢跟踪,直到跳转到$8000:
) |: ~3 J' _6 v$ p
2 x8 X' u! C/ h! i) e5 Y然后打开6502_Simulator:
$ e8 P% K7 B) v1 y7 ~9 ]1 F
5 k' o, y: @1 r! S3 E再打开我写的数据搬移程序:! T* `) Y* H: C' c4 [5 O; C# Q1 ^
' K* }& s* m2 Z6 K2 P. o
$ ]1 T U' V; O7 @5 G# I. m然后修改对应的数据:! k O# g$ K+ |9 K6 o6 ^! }8 c* h
程序开始地址:修改比如$8100就修改为 .ORG $8100
& s2 I* r8 s' e: ~) c+ b复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$708 @4 a0 L, j5 N( I# u/ l
从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82
. e3 `$ w# r3 \; M9 e& r想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91
- z7 m7 [5 ?" {$ G0 G, P! k5 ~也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。; M2 V5 d, l3 ?! v
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。/ _2 @0 g. }3 i3 \: R0 k# v' X2 A
中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。3 r1 s/ G3 c% ?# G6 _# e
后面的不用管。5 a/ Q7 E! |( o! X3 M& _) u
! _' f+ n3 b: I% Q4 N
设置完数据后单击编译:
( z' c- O: D+ h3 p/ l3 z0 g
( P% r# Q" E1 s4 m4 O. F# _然后保存编译文件:
$ s/ R, t4 L$ U5 G& N& _$ Y) `% T7 }6 |% ^' Q
选择二进制方式保存:% z, [; X2 r. F
3 v: d' \: t1 S; A b" F( ?- I
5 B( ?! z3 W8 A用Hxd打开保存的二进制文件:
, A9 w3 i+ s/ [( I0 A/ |5 j j+ V% {' @' }6 ]
3 _. }3 c2 q, w; S' P# d# \
跳转到设置的程序开始$8100:
( H4 `1 h3 Q. x e: D& X, l7 J7 R+ G5 W( h" F6 T
U3 Z/ J& k( f# L5 O$ ~' ^; U! K3 P& S4 p3 d5 Y
回到FCEUX,转到NES内存的$8100对应的ROM地址:
) V/ y( D8 g6 G4 |* |0 o0 U6 m: p z$ W5 A! Z
, N. |9 L$ `" o& r5 t! A9 C b
- P+ {" X! I9 Y5 Q$ h
" R6 k' m/ W: Y$ y
$ Q3 l+ O ^9 R1 ?8 Z0 G# z/ W0 n然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
$ g7 p$ d% R) [4 O8 G% r
( k/ K* A. J% ~, N% g( q
8 o e8 u; x( T7 L8 W6 y \% A* {/ \- ?+ ^2 ~* g- a; ~
然后转到NES内存的$8000对应的ROM地址:8 L$ r- A6 W4 ]
% J, z( z- m" L2 Y
. w3 v* _$ S ^6 d: N1 ^* a1 J5 z2 E* x1 m0 m3 A$ E7 K, s
# y0 n; S! \: ^2 u' \1 a0 u, E
写上如下程序:
' u& O& C' v o% g! g/ UA9 80 8D 01 A0 20 00 81 : m! c' l, g4 O# ~8 z
LDA #$80
: Y- ~" G) z5 n* x' m; v5 [% {# [STA $A001 可写方式开启SRAM9 k5 g1 M4 r$ h) Q" L! w0 u
JSR $8100 跳转到子程序$81000 i/ Y7 a+ b. R& w. ?; R
7 G2 _% x* o- `0 ?# _& q
然后把Hxd里被覆盖的程序复制过来粘贴在后面:
8 J6 V* J" _8 _. q; B3 x. A9 H9 r" v! h5 R- ?* c
7 v$ E1 B1 i" Z8 l. [3 _
& h m; J# a6 u$ r( V$ d; d
末尾补上一个0x60:- k D6 q, h" h6 y% b
RTS 子程序返回
; l4 Z8 i9 A7 r' ^6 ]5 p
) _0 T0 k2 Z' n/ q" S0 m. H( h然后单击运行,ROM音乐响起,正常运行:
$ l5 O; L5 y: n: ]1 Y' }5 \! q
/ a8 G+ g, _7 S. _2 `& K1 b7 H H9 }! x% w5 R" H" i9 r5 k* v/ t
然后转到NES地址$7000:
1 i5 d4 t! J1 _) r7 g( a
6 u# e2 o! ~: t7 M- ?; P9 } K6 m( q: `, k5 _+ a) n( l2 u! \
4 X$ F |( c! L
5 s+ p2 p; W+ C1 r+ M" S' ^+ ?
: o$ f2 e$ I1 t/ |8 I0 ?: A可以看到,$7000-7FFF都被复制了一片数据。6 S( S& \: i5 l5 N' K4 C
测试没有问题,然后保存文件:/ @. l9 k) m: G% Z' |4 s
; u! a6 y/ L, [5 [+ O# f' H以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。
* e$ j' }" |2 Q9 `$ j后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。
" i; B1 t* e- z- p/ c |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|