|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑
4 }: q# U8 f6 F5 C
$ u/ a' Y2 [! [[FC][SRAM扩容教程(Mapper 4为例)]8 z& a7 _2 I, Y4 X, J# [' T/ P
' ~. E. t- |$ X: T6 L& v& w9 l0 L时间:2017.4.288 F" @7 y& m- i+ ^" t" f; f$ |
作者:FlameCyclone& V# |5 }* B8 p+ h1 W( W9 c
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
# A- W4 m: H2 Q. y% X; kROM:双截龙2(J).nes! M9 [% }( K" \
适用:没有使用SRAM的ROM0 V4 X, x2 @0 g8 c2 b$ z0 {
3 V0 O+ S6 X2 D# e) J" \7 B5 ^
首先用Hxd打开ROM:* u2 D6 x S! N6 F1 l* L

3 a2 t/ {) s% ]2 ?. Z9 [8 |5 x$ d然后扩容:* |) g2 m) i6 d' ^( b8 L
# c1 @7 q9 l/ O1 R; H: U
0 M' Y0 a/ S7 k/ Y) g( `! h+ n/ i' i
% a7 c! R; [# [' j* _- x6 P+ D! i
2 @) }# e2 Z. z! q6 ~
7 J: n" i, M& P$ n8 G3 |1 X) u
+ k0 {9 _9 {! ]2 \& n
# b# e& g8 s1 \. J7 m/ ^
5 P5 W6 `; n, k先看看任天堂产品系统文件对NES文件的说明:0 s+ n: E: k/ U. m3 u/ E& ?
NES文件格式
2 Y1 l/ G b: V' h$ o.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。
7 n( c* a- i' f2 v u4 s7 z! _& ]偏移 字节数 内容 1 a2 I4 |* P5 ]/ t, z
0-3 4 字符串“NES^Z”用来识别.NES文件
- A0 y( Q) l: U: f( J4 @. a4 1 16kB ROM的数目
# F0 E/ [ d; @! x' h" E, E5 1 8kB VROM的数目
/ l D. F; ?1 [+ {* H& E" ^6 1 D0:1=垂直镜像,0=水平镜像
5 w8 S8 k N. e2 D% a5 b D1:1=有电池记忆,SRAM地址$6000-$7FFF % y# }% t/ O2 O# g9 z0 u
D2:1=在$7000-$71FF有一个512字节的trainer . J( ]" N' o2 N7 R0 ]2 ~
D3:1=4屏幕VRAM布局 6 N% @9 J5 x( X V) T% e
D4-D7:ROM Mapper的低4位 9 b/ P0 \% ~' V1 g: ^' Z
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) 8 ]- E1 O! W8 c7 g
D4-D7:ROM Mapper的高4位 + K8 B+ O# r: |
8-F 8 保留,必须是0 " x8 q" f* X5 s( p, N
16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前 , h. H$ _) {8 m$ {( N
-EOF 8KxN VROM段, 升序排列
2 b. _+ }. _' [6 P
( s' V9 d% n% `6 C ?; B- P, V然后知道这个ROM有0x08个PROM和0x10个VROM
9 F5 o( [/ i- i' d1 |; } j接下来扩展PROM位0x10个:; m. k4 ~2 U7 S5 D
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
8 r; g7 Z) X) B9 e { * g. W& u T. [, }- Z0 j
由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:7 d" O- V j( N5 c
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。( c! g) Q; d2 S7 c, S+ ~: t' L9 g
于是可以得到双截龙2的是:
% q) u a- z, H(8-1)x 0x4000 + 0x10 = 1C010。
" f, j- d; F* |2 a" u然后跳转到1C010:
- n6 F9 M4 h5 C3 _ + f& H# Q1 T4 }- {/ m' `8 L, Q

/ \9 I" v: _$ q
8 x; M* |0 C' D; k+ U- F然后插入0x20000字节的FF:
& F" `) D- ^1 \2 F% G( U ( ?! d+ }9 G2 J* B
7 ]" I/ v9 Q8 a$ `" p/ Y9 x1 J- ]
; J3 q% O& E5 Y/ P2 U$ O
然后保存:
2 I3 L& n( p1 m$ Z8 w+ n 4 X( l% g; [' R2 Q B! _( `; S- x7 ?9 l
3 h, \" ~8 X9 E" f! l- ]
用FCEUX打开正常运行:/ V4 O; N0 k1 ^5 e+ o

/ g( X1 F8 m# b查看文件信息:; t1 w; l: ?' {4 {
4 e- D) z% c+ ]
; ~7 J# N9 @* i$ O
接下来切页:
2 }# Z# p& n, o先打开十六进制编辑器:9 o; \' H% m% b2 j0 e
7 _8 Y& V" r3 A3 j+ U4 j6 S
拉到滑块最后,看看重启中断9 W" S" A6 O7 V
中断地址 中断 优先权
$ j* x/ ?/ b* j* J7 ?" U$FFFA NMI 中
0 e. ]9 i7 F- I9 \$FFFC RESET 高
- `6 R/ x7 h0 O! t$FFFE IRQ/BRK 低
% ?# c6 h/ ~. p1 I
: O/ A4 z% C8 H4 s" |RESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。$ f4 g2 R8 _ e. Y e" e
由此可知双截龙2的RESET中断$FF65。/ C9 ]6 C; n' T' r% _
接下来添加$FF65的执行断点:
$ G5 M m2 j+ q5 q E, o2 Y打开调试器:
/ D/ F, B% h) ]- X* X 5 j4 a3 l& M" E7 W& h
添加$FF65的执行断点:' `8 r% t; W% O
/ H- c, o }4 O
* q0 M' U5 O, D4 X' r5 A/ a2 c. U
' J& ]2 U% r; ]* F/ g* Y9 T6 P单击确定:; d( C- p3 ?; n k/ y0 \
7 x! R: h: }/ m v4 I1 n& t
+ d4 B& c7 B, T2 t# K3 I1 X
然后重启ROM:( Z6 D- W8 V$ ]- [

5 g( O: Y% F, b5 p% M* ?- n调试器此时弹出来:
. k& I' h6 z6 k7 H6 V 1 x) p x" t5 g- {. K
然后打开Hxd,写一段mapper 4的切换bank程序:# D" }7 h' H" N
先看看mapper 4的说明文档:2 C/ H! G/ B( y0 J& @1 W& T U. E
Mapper 4
3 ?8 b. n5 U: w. e) X4 x; \2 y) z# h1 I( ]. J, s
$8000: 模式号
: y* P* N U$ P, U) ~ 位D0-D2:
|$ J0 g3 u3 V6 \ 0:选择2KB的VROM存储体映射到PPU的$0000, |( o- b! T0 m/ J0 |% Q( t
1:选择2KB的VROM存储体映射到PPU的$0800% `6 l# M% G9 n# G, ^5 z
2:选择1KB的VROM存储体映射到PPU的$1000+ a( w) F! z3 D- k6 ^
3:选择1KB的VROM存储体映射到PPU的$1400
0 q$ B) z9 l2 Q; Z: m" X/ d6 P/ Z 4:选择1KB的VROM存储体映射到PPU的$18005 @. F; Y+ k# x. ^! }! x9 @
5:选择1KB的VROM存储体映射到PPU的$1C00
7 g( Q/ J: J% m 6:选择8KB的ROM存储体映射到$80006 \0 |- z( | p: l
7:选择8KB的ROM存储体映射到$A000/ d4 y! k; w1 J9 K" G* l
位D6:( y" C8 q% l2 I3 f0 E9 `
0:允许擦写$8000和$A0007 v* p, e6 _5 k: K& {: _) X
1:允许擦写$A000和$C000
! \; ~% a2 i2 B6 z 位D7:; ~! i2 p$ G. F3 u- E2 ]$ b
0:模式号D0-D2使用普通地址5 |: B% q- ]2 L+ H4 h8 E2 H
1:模式号D0-D2地址异或$1000
( r, H$ y4 N {4 o6 ?/ i! y1 C1 }" S
$8001: 模式页面号
2 B2 s. o$ ?8 ~# }( F5 n# d: H 写入一个数(00-07),切换存储体到对应地址: s7 L# v- Y) k/ z+ T Q. p
$ }# J( u6 ^/ _
$A000: 镜像选择
4 @0 D7 T L% s: m# z/ V 0:垂直镜像
' V. f0 y) w0 I1 H( e3 } 1:水平镜像
! ]" Q* p* U5 ^$ _, w2 y
4 I* O0 r4 q* x q9 R$A001: SaveRAM 切换
1 B# ?2 E0 i7 Z2 A' D1 S& Z( N4 _. a0 \ 0:禁用$6000-$7FFF7 @; D( C# k/ |9 }8 n( k0 I$ V: J
1:启用$6000-$7FFF$ t( x5 K$ q2 N/ x
! J3 |. G1 N! x- G2 G+ [) }
$C000: IRQ计数器
- A: j* p0 K$ l. T7 n IRQ计数器的值存储在此处# S! ]$ X, l& A+ Y3 ~% d
# x2 ]4 N+ w+ ]4 K- C3 m$C001: IRQ暂存器
) ~5 N- d8 H" h; b# ^ IRQ暂存器的值存储在此处; Y% |: ~* T! E9 i8 q
( A7 v# c3 Z1 D% }9 i/ E. k$E000: IRQ控制计数器02 M, e8 H7 S0 G7 k
向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ8 p% B8 c$ [( C9 {7 l. y$ F7 F
' Y: G: q0 w& _- T$E001: IRQ控制计数器16 F& ?) h4 z+ d- w3 [# J7 C
向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来) ?$ x7 ?3 i* O, _' g
& Y! l3 z+ C& G9 G* i: j
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:
9 I( M6 }; S+ P5 t8 L4 K48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68$ I+ F( X, q* e& W* Q
PHA 累加器A入栈
# ~: j, ~. F1 Z) }4 I* oLDA #$06 设置切bank地址为$8000-$9FFF" e$ b* p# K. U
STA $80009 U/ H+ ?5 S1 H/ V. `
LDA #$0E 将第0x0E号bank切到$8000-$9FFF
9 w: `: J& O1 ]- ]8 Z( E0 mSTA $8001
- I. s6 d, }1 m$ w. [. n' [JSR $8000 跳转到子程序$8000: U- o1 {- X& o4 [
PLA 累加器A出栈2 Y/ ?# A: L! G& \' I- @
- }7 w) f! |' u& \9 w( O" Z, t
为何切换的bank号是0x0E呢?; F, \3 B! r1 U) m' l8 ?7 B
因为在扩容ROM后,文件PROM结构为:+ ^# R$ `$ v. _! \ d0 X! `
原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
! v5 p. I& J* R2 D( u% o S$ j
7 W& k9 N& `3 C: xMapper 4切bank一次是切8KB,那么文件结构就是:. X' K+ S6 N/ }% F8 m3 s5 f
原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)+ I3 X! [3 e; U! I
因此选择扩容的第一个空白bank就是0x0E号bank。
& L$ ?2 V* y3 ?8 H* W. Y* i- o8 t- O
1 L. u+ F, o' K; n4 M" O* {
3 C* ^6 M& _4 N* x% J9 ~0 o8 B然后去调试器找RESET中断中可以放下切页程序的地方:; T) F# {3 i& P/ w3 ~( W J1 Y s& G
首先长度要小于等于自己写的切页程序。
2 e( w+ G: h' ]# o [可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)% k7 y0 u; X- `! W, X+ v# `3 r, A

, V& l7 @8 E4 K- R然后跳转到$FF7C:; Z3 t! B- }2 I7 g# G Y0 L4 \1 _! t' H
, `( \# y, @2 ^( ?' @) L7 p
6 Q% _+ O* {$ h, v& f! V4 p
H& V- K+ d! G+ q, }) G( ?
* Q. _; X/ l( I b
复制下可以被替换用于写切页的程序:! k( L' Q+ z( C$ H
先选中,再复制:/ E( [; w+ G! y" u/ ~$ ?! |2 J: [
0 o1 C! y" x1 E. `* J. r
( F) \0 ~4 J5 G5 a: n
在Hxd中新建一个文件,把复制的数据粘贴上去:
4 ]; Q8 x4 t! g# E
- g" w* d: ?& _5 A* I) N ; Q- m7 `$ _: d
. z% l' N3 z" F. z) j
1 h/ ?9 x5 d6 d然后回到十六进制模拟器:- e0 t/ x- M+ A, W
转到$FF7C对应的ROM地址:
5 ]- l3 \' `; @ Q, }
& N4 \9 y U; N/ A" K
: \* u5 x" B5 G8 G* Q然后复制Hxd的切页程序,粘贴到这里:- y7 G6 d c4 D- U. `1 ]

- E% D: o% K* }9 |, L) a- u 8 n+ E8 Z* z2 ~- p

2 @; U9 y4 }4 t/ r没有覆盖的用EA覆盖:( ^! u. j( @" @
" G8 _) |3 @9 B$ O6 O7 y4 o$ x, p
打开调试器可以看到变化:
( }3 L/ S5 n' p- V9 p
# P$ |" r) a3 b1 a) g; ]然后添加$FF7C执行断点:
5 e$ ]+ {9 B5 S% T7 j8 H - D; ~ V K* x2 m3 n
6 E6 P9 p2 }8 U) [' Q( N
5 e U5 f9 D' _ C/ @4 O
单击运行:$ ~# a: u5 x8 O2 l: C. b R0 o9 g
然后程序在$FF7C这里停下来了。1 q2 x$ l3 U1 Y2 c- v% Z7 I
4 ]! g& S3 A0 C) Y W! H
然后单击单步进入慢慢跟踪,直到跳转到$8000:* i; L/ ]6 g+ B4 r8 }7 w

4 A( y5 [. T4 L! ~! C然后打开6502_Simulator:
1 z: J2 f3 H) D9 K% i/ P
. ~- ^" ^$ u9 k9 g2 a再打开我写的数据搬移程序:
0 s! Y+ `9 j+ Q2 w) R* B " l: Z$ |! ~2 Y8 `; `! j2 l" E
" o, k" I: m+ ^0 a7 I) a/ T' Z0 v
然后修改对应的数据:
- S& h+ s( J1 @( E& C程序开始地址:修改比如$8100就修改为 .ORG $81000 x7 W& g6 V! c. U5 ]" n3 P
复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$703 t: k/ f* ^; T/ o
从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82" y1 K- v7 s7 Z! o6 t8 j
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91 Q) A; w- Z5 X1 x
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。/ O7 d6 m g9 T8 F
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。2 X) H. D. e7 h. c! O2 X
中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。
) D m) j0 ? j5 Q% `8 i后面的不用管。+ X/ F2 u [: ?) G% m% L' Q i
6 x8 T0 }8 }# |3 p
设置完数据后单击编译:
9 O' |5 d |0 o% V8 ~" n- Y, u/ Z / }& }& r( N Y
然后保存编译文件:
( s* S% c. P# Z: x) t R% D9 A; O
& d& g' E& Z& w: o选择二进制方式保存:
! J1 Y0 u3 ~2 q 5 p3 s, n4 M' X$ Y" Z3 U
- Z6 y0 Y* P, a# f# p+ d
用Hxd打开保存的二进制文件:# _" Y$ U7 z, f7 ]' |
* R* x* c5 A' x( t8 A8 F5 _# h8 L

% N6 g% q4 t: b* i跳转到设置的程序开始$8100:
% O2 ]2 |0 S0 R4 R \ 6 o$ L0 Y7 w. }* `, O& ^, T0 N
6 O) u0 V4 r* Z3 z X

4 f& ^3 a" y% W W回到FCEUX,转到NES内存的$8100对应的ROM地址:
: t" I9 i" w% ?8 j J, r; c
' z& Z3 U' e" |5 A) C8 J- |5 S 1 B* p, @! z: M# f% `2 _$ z

, I2 F# I; D6 n4 |2 I* E1 I* S 2 }8 M9 A5 L2 }/ S( O7 ?' h$ [8 ?

' N$ Z+ s* g9 y* G, y/ [然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
$ S: f' _! R i
, X& n4 `; @" ?0 i 3 E, O4 Q7 s9 Y- D

( j1 t# F& v& |9 x: w" q! E然后转到NES内存的$8000对应的ROM地址:+ ^0 z" T0 z% s5 W% N" R: Z1 X
. K* I4 v7 f, H, u& |

! X! J2 Y1 J- G l: C
0 R. `$ R1 T* V: n3 F 5 V/ J) ~! W, {" ]
写上如下程序: ^! _- w3 E0 R* t" J
A9 80 8D 01 A0 20 00 81
+ G$ w7 X7 c" ?. b1 s8 sLDA #$80
; ~5 ?: v1 U9 y: S2 }! R0 \STA $A001 可写方式开启SRAM! |+ z) C$ I8 t6 W
JSR $8100 跳转到子程序$8100
3 K; g# a0 D# d # I3 T' Q0 }- c; O
然后把Hxd里被覆盖的程序复制过来粘贴在后面:
/ V( P d8 G$ E # F, H ^% V6 l$ i
% K( u4 e. p" V% N+ _

3 [) @/ X& L d9 z8 N1 T/ g末尾补上一个0x60:! B4 l+ z7 \7 J; p* ~5 }% n+ R1 @) C
RTS 子程序返回' I8 o. y7 f# }

* c% i( ?. z" g$ V6 r' u然后单击运行,ROM音乐响起,正常运行:
* M4 g" o5 J6 {3 U$ S / c6 s( [% v0 e7 C8 n7 ?' C( f4 ]9 ^
0 h9 D6 N! v$ F& C
然后转到NES地址$7000:
9 j* p: R: I; s: t # Z7 b, H9 Z g' @7 X7 V3 x

+ n: c2 p, f2 _& n/ s% r, e2 F $ _6 l% Z* I# w& ]

9 n8 _7 |, \* H( l
0 G+ F7 d5 {! D2 T8 V可以看到,$7000-7FFF都被复制了一片数据。* C& Z- n# `, Y, C6 S2 {" b
测试没有问题,然后保存文件:+ [, i3 R7 [5 {& w! I$ k. H

9 K: ~, \+ g+ o3 c( a以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。* H' C0 S' `2 d/ d; q
后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。% E6 P# U" }/ o0 C
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|