|
|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 1 a& M( Z$ M x+ Q5 ~% M; o
" z& v2 f6 @7 Z% {8 w8 s* N$ O! W
[FC][SRAM扩容教程(Mapper 4为例)]
& e# u2 B+ u* \7 F4 k- b! H+ n& T
- ?9 c- x' j: P1 A) M% V5 P" I( j时间:2017.4.28: S' c9 S) a" p
作者:FlameCyclone
1 F- H# c! q& }工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
3 ~5 m# i" J; T2 F0 {) Z$ J" _ROM:双截龙2(J).nes6 O% ^/ d. o" ~
适用:没有使用SRAM的ROM5 e6 l% B! N. `$ W
4 {' C0 x) t4 [# \首先用Hxd打开ROM:
) r& b& J, y* d0 j, U5 z1 F
& V- X2 K5 |" `# D3 I' W然后扩容:4 p0 K+ b" R _$ s! Y/ E; a
5 i. E& b4 X2 \" n8 ]9 ]4 ^' O
. W7 E/ F; Q0 V) \+ }
5 G$ t& c+ X7 ?3 E- I/ N' A* v+ g( o m: z: s ] R
" q, F+ e- A- ^- o' o( I6 `. w x
4 D( f. l$ f- N: {7 u1 s$ y" U7 ~0 v$ S6 `4 M
先看看任天堂产品系统文件对NES文件的说明:" |( n0 A# D) n$ B
NES文件格式
# ?& O+ |' s) T+ q.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 0 ~- v4 [% {/ z1 j( o7 k
偏移 字节数 内容
3 D' V$ i" ~9 [# _" T: D0-3 4 字符串“NES^Z”用来识别.NES文件 N X( _9 c0 a5 j7 I$ ]
4 1 16kB ROM的数目 5 m w3 J9 ?2 L$ |; W B
5 1 8kB VROM的数目 , q6 C6 d1 s8 c" o, M0 S
6 1 D0:1=垂直镜像,0=水平镜像
- c4 V1 {; v3 R7 @: _6 D0 [1 s- l D1:1=有电池记忆,SRAM地址$6000-$7FFF ) `! _& a- H3 M4 ^2 ^5 ?( Q
D2:1=在$7000-$71FF有一个512字节的trainer
6 Z1 B% t! T4 b" r4 k D3:1=4屏幕VRAM布局
5 P6 F: }' i- v2 c8 C D4-D7:ROM Mapper的低4位 2 P; e% r9 x# }' \, Z" |' G) Q: g
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) 6 s/ u/ z: ~9 Q
D4-D7:ROM Mapper的高4位
0 N9 ]4 L+ q% @8-F 8 保留,必须是0
) l# k6 |; u: e, f- c2 `! `16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
2 }* T7 \( q& j# }4 }- n-EOF 8KxN VROM段, 升序排列 \* l. f4 z( y# n" c
0 A. L I' W! S
然后知道这个ROM有0x08个PROM和0x10个VROM) G. c0 \6 a' ~* s x B
接下来扩展PROM位0x10个:' \) s5 w3 X, R# h; K' l% ?* q
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
9 |* [: G6 j' l; W; D9 `
; k H# }8 f" @ B8 a由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:
/ r0 p1 Y8 B+ M7 F最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
! ]8 G1 F! ]6 p- p5 h2 }于是可以得到双截龙2的是:
2 K9 M: }5 B5 Z0 P4 m$ O(8-1)x 0x4000 + 0x10 = 1C010。
# C8 A0 O/ t; z j然后跳转到1C010:
) F! c( ]6 D5 Q* ]+ z) k# k5 [0 z6 L
* ^7 Z4 T" Y4 _/ p
3 G. X/ g6 j* j) d
9 Z6 q0 ^3 J3 D" H* y2 _然后插入0x20000字节的FF:' Q1 G* F9 `9 a4 O, {0 o& h6 l: T

) F0 H, V+ n, u0 r2 L
/ @: b, c0 `* [2 |: f" e
! ]/ }" K' r( h( J1 i然后保存:% f9 }0 [/ y2 {/ k
& g# u5 }3 j S2 Q* H; z
8 T8 R; _& ^6 [# A
用FCEUX打开正常运行:& d) h3 j4 ^- K6 a
2 L& m- U1 }( {/ e L! O: _
查看文件信息:1 V$ r+ \2 ^* F. i# a* p9 n' V

3 z% D v w8 n6 Q ) Q5 s. B) E9 V! F- J* g! C# D
接下来切页:. {2 x: j; K" b$ D0 s% V5 f
先打开十六进制编辑器:
/ a9 q# _' e* j3 E# T
# [( s7 Y9 @* h0 q4 f, w拉到滑块最后,看看重启中断: ^* W$ v# j( W7 c1 }& b( |
中断地址 中断 优先权
$ o2 f8 d' o) j, D+ U$FFFA NMI 中
4 G* `& F3 E' S+ \$FFFC RESET 高
' E. V" r4 M! \$FFFE IRQ/BRK 低 4 @9 L" O& y0 L% }' Z4 F

. e+ t+ e h" v. ~ b/ zRESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。# t* F2 G( i2 t7 C
由此可知双截龙2的RESET中断$FF65。8 }/ n, K0 y3 q- f, Y/ \$ i
接下来添加$FF65的执行断点:" b& ^2 ]2 p. R# D3 x+ p
打开调试器:
, u9 u0 L6 ~( K7 q 6 c! e/ T7 z t5 }
添加$FF65的执行断点:
2 B) N6 l' ]; O" M 0 O6 {+ x* N" k( g9 v' g2 @% x1 @
+ [+ L" b- k' t4 v. D8 ]2 H
1 ?0 ^2 w. o! v, P. l单击确定:
9 k3 t" j$ Z" g8 y+ {1 l 8 `& n# u, y o4 u) |0 q/ \
# V7 v6 Y q8 p* {
然后重启ROM:
1 {% x7 P* W% } Y) |3 f. N* ~1 b
: ]1 c h5 E8 W" q$ d" |调试器此时弹出来:$ }3 A6 y8 E: E" m
$ H. e) ]- [+ t) c
然后打开Hxd,写一段mapper 4的切换bank程序:
, y# \9 o3 F8 O( a/ W% }6 a先看看mapper 4的说明文档:- E7 {6 Q: k2 e( O- d2 B
Mapper 4" Z. u" t- ^" Q7 K x
4 J: ^1 G t: i% E a/ Z
$8000: 模式号 s8 b0 {/ `- o6 A1 B
位D0-D2:
$ Q' k$ C2 ]' F$ ^ 0:选择2KB的VROM存储体映射到PPU的$00004 H0 G- Y7 Q# m, G% w
1:选择2KB的VROM存储体映射到PPU的$0800: q, C8 y7 M* z
2:选择1KB的VROM存储体映射到PPU的$1000 s! e/ P/ x, k2 t; ^: w9 \! }8 J6 B0 {
3:选择1KB的VROM存储体映射到PPU的$1400
H" s1 O0 k5 R 4:选择1KB的VROM存储体映射到PPU的$1800! g9 N$ t) u: Q9 e& Q4 `# p+ s! P
5:选择1KB的VROM存储体映射到PPU的$1C00
2 ]0 D4 P; L {4 T 6:选择8KB的ROM存储体映射到$8000' x4 k+ i% w: q9 I9 `
7:选择8KB的ROM存储体映射到$A000
1 ]4 E) K9 i/ H6 e5 b 位D6:
0 C/ S5 M) T. O# ?3 h' T 0:允许擦写$8000和$A0000 O+ r6 l. u7 q1 K
1:允许擦写$A000和$C000/ s+ g, E# ~1 p9 x8 p; P
位D7:
5 B7 s$ P* ?, Z, d 0:模式号D0-D2使用普通地址! K+ \- t/ I' E" k+ p, s
1:模式号D0-D2地址异或$1000
+ q$ ?& f+ A2 g# c: b1 a5 U5 b: a* C3 y3 f7 }' k
$8001: 模式页面号
: X; c. b8 M& B |# z6 f; B 写入一个数(00-07),切换存储体到对应地址% {' A- r& [; o' o4 J$ |- j. u+ f
# a/ ~) ?$ N$ L& E5 i( F2 A
$A000: 镜像选择% u" ?$ F- ^* a3 w
0:垂直镜像
" B7 N: u1 @4 T2 P 1:水平镜像
! S. h0 S6 p$ P- k! ^- z
2 R( I/ S, j% O. A. m$A001: SaveRAM 切换
H5 e. {1 I2 } 0:禁用$6000-$7FFF
% c! f; z# N6 d% [9 o' s 1:启用$6000-$7FFF
: @* F8 W) s$ f9 Q
& v* b4 ]8 V5 f3 L7 P' A; [( ^8 |$C000: IRQ计数器, ?& a. Y+ F9 H, e9 s
IRQ计数器的值存储在此处" d1 C+ I" I3 x3 y
$ O+ R: }/ }8 W! Q5 Q }' @/ D$C001: IRQ暂存器
' I& ~+ F6 f$ s6 p4 ?/ K, h" t IRQ暂存器的值存储在此处
4 Z& G! Q7 [9 K4 D% @. K: x9 S# @8 {
$E000: IRQ控制计数器0* x( p9 F }9 ^! n' Q& w/ n1 S
向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ/ S$ F6 G6 G% D+ i& u2 }
2 D T5 Q( S1 {! U0 @: [' f6 x# L
$E001: IRQ控制计数器1: l* f {/ {* s; Z
向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)8 C$ m$ c3 o/ ]
( ]- A- C4 U0 X1 D1 x7 t那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:5 c3 a8 L0 E D$ A: z% E
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68
( r1 I% M. O2 O) ]# [PHA 累加器A入栈- f! W! W2 N( l0 h3 K3 d W+ c$ n! C* s
LDA #$06 设置切bank地址为$8000-$9FFF
7 L O$ C0 h; l3 fSTA $80004 _( A5 b& B& Z+ A
LDA #$0E 将第0x0E号bank切到$8000-$9FFF
1 q" s1 g; l9 ]3 [" t3 s$ bSTA $8001( t5 M) d" B. t) Z$ w
JSR $8000 跳转到子程序$8000. R0 h8 n6 [5 M6 l2 A
PLA 累加器A出栈8 x3 x% }# G) L. `; F L" ?
" F/ B" G2 H/ `+ |5 _. x* n为何切换的bank号是0x0E呢?
; j! X$ C1 i' {因为在扩容ROM后,文件PROM结构为:
7 Q% R0 }- G+ @) a4 S9 s5 c% ?2 I原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
3 r5 A- a$ h% l, L
; j/ c& {3 [* MMapper 4切bank一次是切8KB,那么文件结构就是:3 B$ P, {/ X/ m0 Y0 ?
原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
+ p' r! v# T. I; p j5 u因此选择扩容的第一个空白bank就是0x0E号bank。
' l5 b% E7 V3 q
+ m6 @- `' v5 |; S* g4 P
8 ?# j9 P/ }$ B然后去调试器找RESET中断中可以放下切页程序的地方:
" U* V( q9 [4 \# ^& X0 p: j- ~7 {首先长度要小于等于自己写的切页程序。! G5 W2 m- {4 e/ l5 K; P' [/ `
可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
9 B9 o( w1 I8 j1 l, q5 Z
& J) t7 ~2 F" |( h" }然后跳转到$FF7C:
* P$ }9 i4 x! }( s) a5 `5 n! C
" O! `1 m* ?/ M/ w5 c2 t3 g1 j) \
7 Z$ h0 L/ G1 {! k3 ] 7 ]6 O; q2 j2 G J; f, T% B0 F, k5 j; u9 A
6 f% Y- Z, |8 m6 J/ X复制下可以被替换用于写切页的程序:
8 i% T9 `( y; |; p' ^# U先选中,再复制:! R4 I$ O, S4 Y7 C
8 [/ e% ~4 L( q2 B. k( X, x- p

. Y& `6 W$ |3 ~6 r* F在Hxd中新建一个文件,把复制的数据粘贴上去:2 c2 S2 I, v; b
3 w4 i2 ] F: {$ M

( p$ a( ]/ E% ^" [
+ j0 {# b, t. O0 r1 x" v& [4 [7 W; i* a* W
然后回到十六进制模拟器:
F0 L' G; t" n, i转到$FF7C对应的ROM地址:2 P& A" ~: J$ d

! u0 q* |2 W3 {1 l% V( n
5 m& v5 R5 F) G" o8 F然后复制Hxd的切页程序,粘贴到这里:
8 [8 f/ c2 f* d3 I6 f! B
7 T/ ^5 t! t' t7 x0 s 5 P5 Y- Q- T ?" t

% Y1 A2 V, Q6 Y; E# V/ p" e没有覆盖的用EA覆盖:7 x0 Y; x- }: h
7 h6 m: l) K# Z3 @3 e3 h8 N
打开调试器可以看到变化:
5 D L* h: x# \. O' S, e
6 k% i6 [9 K$ Y1 P然后添加$FF7C执行断点:
: W* j5 P/ h9 y
% b& F/ O+ E; B 7 x0 p5 g$ G+ \! H5 E' ?% l
+ V T5 E( [- k) c- Z' Z. [
单击运行:: [2 z/ D+ i& j7 t) B8 O
然后程序在$FF7C这里停下来了。
; B- \5 Z6 G, a + @0 Z8 Y \4 z4 W( e
然后单击单步进入慢慢跟踪,直到跳转到$8000:
7 ?5 B: U$ m: h 4 c# b; f" n) p8 n7 p
然后打开6502_Simulator:
; l8 {! k& c" @) J4 K* v & T* I, C) Y* q
再打开我写的数据搬移程序:( A3 k4 n4 q2 ]
# j( x/ Q9 Y9 Y, ?

0 M h8 Z: E- d3 y4 i, Y然后修改对应的数据:. o# h; b& L3 \; n9 g2 r
程序开始地址:修改比如$8100就修改为 .ORG $8100
$ U% K5 p) h+ e2 n4 K! Q5 n3 d- l$ U复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$702 v. K* D. z7 T6 i0 C( j5 {3 U
从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82& M8 i9 `" q- d4 D& `% r+ |
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91" ], F8 g% S4 L) P% e- {% m+ [
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。
P% g$ }; f5 M/ c1 H如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。
, D1 x2 c. J+ Y/ Z: ^% Z中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。0 k0 d7 Q. b6 _) W
后面的不用管。# n5 W, s6 ~0 Y8 X; ^1 N* d; s( X
! V# Z+ q8 I4 o( Y1 |
设置完数据后单击编译:( R- h+ t& D6 v( A" W

2 h6 `' j6 n( {! w2 h! Z0 Y" \# P! |然后保存编译文件:
T1 _% S: c- {9 B9 L% K: |* y# b
5 K0 V0 @" b2 Q" {" n( t选择二进制方式保存: F5 k1 A. }7 U, }- h+ O
+ Q* O4 r3 k( j+ a6 G

- k5 s' }7 B4 Z+ ?用Hxd打开保存的二进制文件:6 Q1 M2 L. v3 @. s9 r
3 x; ~1 O- c3 P2 ?( B7 T
6 y% G @) j0 w; ?$ d. r
跳转到设置的程序开始$8100:5 d: D; g) k+ X4 b

6 q4 ~) N. G) h- k s6 J& f
, x) s& i1 d, k3 W( W& J0 t
5 \ s7 I" x6 ~) E- W3 |回到FCEUX,转到NES内存的$8100对应的ROM地址:. x- }. f8 x9 C/ j3 S

) N2 X+ D2 z. S5 h8 A% v
4 T: E% C+ t7 e5 f # W; b4 P" T$ I1 p9 G0 S

6 I/ C3 }4 P. u9 q+ } : U0 E+ Z' ]: P2 s) `
然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
$ W; S5 t+ C$ x' v
5 O" C% {' O6 {) }7 P
# a! K6 a( G! i/ o! m. U ~
; s8 P+ k) C# m# [: C2 g然后转到NES内存的$8000对应的ROM地址:) J" I+ L1 b; h; s# a
5 [+ x% l( Z# @$ G

. v2 L8 D5 `# ]( M3 u l* C& M
) t: X; ~0 F9 y
- o E" Z! E1 u" O- c写上如下程序:
! ^$ t8 ~% A6 a, O' A" YA9 80 8D 01 A0 20 00 81
! j2 m2 ]* U9 |: hLDA #$80: r5 n3 I7 v' `% U; B2 M+ \
STA $A001 可写方式开启SRAM1 A8 n& y. V/ Z2 s' M9 Y3 a
JSR $8100 跳转到子程序$8100
7 z( l6 G1 p) a3 t. w% u
3 f! i0 {4 T6 a9 r; X% F8 Z然后把Hxd里被覆盖的程序复制过来粘贴在后面:
1 \! \/ |0 H& n& r3 X7 B8 _# K
, S" m3 r- r, b4 |; S* ]
; g# G6 L; s2 r6 ?- I& F ! t) s! M& y" C9 R8 |* c" F* ?, A
末尾补上一个0x60:4 c3 D9 K5 m# }, H2 |0 e( c
RTS 子程序返回3 K/ p3 C3 Q( \0 k$ M7 Z
4 v. V3 L5 R) {+ G j4 Z9 r
然后单击运行,ROM音乐响起,正常运行:) ]& o- n$ w- F7 t

* c4 e- Z- y9 D9 e" i
, u1 T! S) H. K) D3 d6 f然后转到NES地址$7000:
) Z1 t1 p& s9 S0 V$ M! N4 M# \
7 \# z% X& U& ?% D% S4 G% V 3 A) ~$ u" E, M4 `/ ] D/ }

3 K; ` [+ a# \; A% ?8 P 5 O6 h. m b5 L: }

+ I6 z2 r; U& h* e5 }, f可以看到,$7000-7FFF都被复制了一片数据。
' G7 _3 m! ~; ~9 m3 q测试没有问题,然后保存文件:
& T! k9 Z5 l; W0 ~+ `# q % c6 |+ n. A) M3 e' K' I
以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。
0 x( w. f* l, K& {: U后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。- q" q! M( o$ l: j
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|