|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 8 i6 J3 a1 X" v N7 M% i, i4 W
7 _" T% q5 ]8 b$ ]( S9 @4 ?3 s
[FC][SRAM扩容教程(Mapper 4为例)]8 G6 I, F8 B/ T" r" ^1 U
& S+ t# m' L" G0 \$ M) o
时间:2017.4.281 S1 Z: `8 T& W ~% O+ Y$ I
作者:FlameCyclone
" B* n* ]" r4 s/ \4 M. x工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
; Y8 e& c8 b9 \: l2 {ROM:双截龙2(J).nes: F1 t5 A7 a* J" o; g
适用:没有使用SRAM的ROM! H, r; b( `5 l# v$ V% q& j
$ R/ h7 d) E* q+ W3 v3 J# i
首先用Hxd打开ROM:/ N0 p: m& q0 Z ^$ X
+ c& v/ {- L# }% i0 `& y, D e然后扩容:
7 x4 l7 ? M1 |0 ? E
+ [# {! Z4 n' |2 d5 B4 m. S& O2 v/ ]8 U" }% E' s
" J, l0 U% K9 u- j+ i5 U# M3 \* R6 ^) l3 @0 D
8 D+ a; x* Y9 H U |
# k2 U' H; @$ r7 V' L3 B7 y5 I* k( K: a- S# A' x/ b: ?
# p4 Q* Q3 d% t2 V# r" K$ _% f j- }. _6 F7 D7 T% ?
先看看任天堂产品系统文件对NES文件的说明:4 w& y* }: w" B$ O1 ^! I5 z. C
NES文件格式. d: g5 q8 J$ B% M
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 $ M9 c- `: T ]6 N ^
偏移 字节数 内容
* i3 _: Y" a7 w0-3 4 字符串“NES^Z”用来识别.NES文件 & }3 x9 Y% R2 O x' P
4 1 16kB ROM的数目 ; M' {( A; A# }1 \* \8 S
5 1 8kB VROM的数目 1 ]1 t9 P' b1 o' n
6 1 D0:1=垂直镜像,0=水平镜像 % O, L' U8 j: M- E2 Y& ]: q. U) S. G
D1:1=有电池记忆,SRAM地址$6000-$7FFF
' Z8 b! _+ {. G8 h- R D2:1=在$7000-$71FF有一个512字节的trainer # Q; S# k1 N/ q8 Q
D3:1=4屏幕VRAM布局 ; w- ]2 E3 T4 }& G$ y6 e, m
D4-D7:ROM Mapper的低4位 , N6 A" @# |" C; u6 V( t
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^)
7 G6 P( N* b. Q, P5 f* U) J: }/ e9 Y2 q D4-D7:ROM Mapper的高4位
. I. ~, y! f% X' d6 M. P8-F 8 保留,必须是0
& v7 |: v: c4 t2 I- G+ l16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前 & u+ r2 a# }6 O5 ` @0 ?0 u' M
-EOF 8KxN VROM段, 升序排列 & Q% G& O& i A' m
; j3 @" h( Q# u/ j. A
然后知道这个ROM有0x08个PROM和0x10个VROM+ X4 D1 R* G# a2 ]; w
接下来扩展PROM位0x10个:
0 g3 m1 B# J! c" B8 G1 o先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
+ }" P8 b- O/ G) t& W( h6 M6 w7 v
5 J! d8 @" q+ }% W) g" k, h! v由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:" A2 o( B' t) v" g5 Z
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。+ {* m( f' y+ c/ _( X/ ^% f1 y
于是可以得到双截龙2的是:' x5 P9 C Y6 g0 h5 C4 |, U C+ M
(8-1)x 0x4000 + 0x10 = 1C010。. f! G4 Y' ?& _8 |$ z+ D, v
然后跳转到1C010:5 T1 w6 r, y0 h$ O
( ]) C; c. h# D/ [/ ^/ ?+ u: n; k
# {! D p( ]6 b E4 S
& P! [* S% T' q W I3 B5 O0 J3 |然后插入0x20000字节的FF:
3 V" t( q# s7 F! w" f8 b$ Z9 \! Z" z! x) B# y, P" O1 H5 R+ @6 W
2 H7 S E2 l, z6 H
5 k9 e$ u: k8 b5 M" s4 M3 P
然后保存:3 Y3 v4 h/ r/ x7 t) r; b
3 k! }( O6 ^" C- ]* ] W* M
0 S! R3 j# z4 G2 m E4 h* s用FCEUX打开正常运行:, D9 h% T) ?3 M1 N& b
% k* R! U& d2 M3 x6 W
查看文件信息:4 _9 g* c6 u8 h4 t+ \
/ C2 }; W' V# k% j1 A H! ?& S
# ^5 o; u, g7 @4 L: x g接下来切页:! q+ X# F4 i: g7 Z2 I2 `9 I
先打开十六进制编辑器:
8 i9 j4 U k3 B/ F
- ?- D9 R( h+ ]; P) N拉到滑块最后,看看重启中断
% ~. a# T! ~ w9 {中断地址 中断 优先权 / v! Z5 }* e3 f! w* \0 s
$FFFA NMI 中 % c1 L6 m- Q' Z3 [# U
$FFFC RESET 高
7 r* Q3 g" u0 b7 x0 Z+ J$FFFE IRQ/BRK 低 . M% _/ ~1 d+ i8 F4 ]" [
' d* A( g& D% B& l% r9 ]( V; X4 d
RESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。+ t5 y% u( H! ]* e& _
由此可知双截龙2的RESET中断$FF65。
+ u4 {; w- z' ?接下来添加$FF65的执行断点:
5 y+ u; w+ I) `/ m打开调试器:8 W0 y8 D4 Z# G1 m9 q F
* B: a7 p5 A( Z0 k- F7 a添加$FF65的执行断点:
$ F( h5 a" k2 N+ Z8 _# e' q& h' l3 N. X% t5 w
. t7 z2 Z2 I0 z8 z2 ` D
, o2 |: U) `4 y6 ~8 w5 A$ q
单击确定:
K/ [% j9 U% p1 u* ` K o' U' x
( V* W, j4 s' s8 u- q7 I% ^% ~7 V8 h
1 d0 o' t6 |& A, j然后重启ROM:+ u. [) W0 x/ _" E" o, M4 E" u" P
( B' ?! C- x& r/ H
调试器此时弹出来:
2 V8 y, M1 S' V" @/ E
4 [$ \4 X5 K* q c然后打开Hxd,写一段mapper 4的切换bank程序:
# m! m3 k: R3 A" m2 {, X先看看mapper 4的说明文档:1 F4 Y Y$ b" x" _& g' e* A
Mapper 4( B1 Y* C y6 J: K: q! M: g
- s; U5 h5 y" [; U0 W1 P9 ]* y$8000: 模式号& j% Z$ R) L# T1 V
位D0-D2:4 _0 U9 q" B3 T( m' E
0:选择2KB的VROM存储体映射到PPU的$0000 `5 p( l0 T8 K; n. p4 Y5 E- f3 v+ n
1:选择2KB的VROM存储体映射到PPU的$0800% T( m( B* `& ]' g# V- Q) C
2:选择1KB的VROM存储体映射到PPU的$1000
r" N! A* K2 Z' i" X& }1 ? 3:选择1KB的VROM存储体映射到PPU的$14005 f) z2 K+ Y/ \& J. P
4:选择1KB的VROM存储体映射到PPU的$1800, s' n; R6 w- p* `
5:选择1KB的VROM存储体映射到PPU的$1C00
1 @6 u& l) z# N( w. t: @' \! a 6:选择8KB的ROM存储体映射到$80003 A- A) Y1 c) [( J8 _: M8 d8 z
7:选择8KB的ROM存储体映射到$A000, Y* ]5 t; T# K; d5 Z% [6 s v
位D6:
# V4 R4 i4 n/ i2 C; ]: R# \ 0:允许擦写$8000和$A000
r( I+ o7 r% q" I: X0 T 1:允许擦写$A000和$C000; W3 D# @, Q) w
位D7:
1 D6 E' ^# ~, z! \2 b4 E, x 0:模式号D0-D2使用普通地址0 j5 u: A* y* J) p" l" _' S
1:模式号D0-D2地址异或$1000
. c8 R4 J% V; F6 a' G: {% J2 F, x9 g2 @+ ^$ f3 J2 ]
$8001: 模式页面号
* v- ]3 T$ A4 y 写入一个数(00-07),切换存储体到对应地址
* X1 v& O G& ]# O6 S$ ~
6 e: }4 M; S6 s2 N$ I$A000: 镜像选择0 N1 v" G) c+ K& g6 z! d/ Q' p$ o
0:垂直镜像% ~) C$ K7 p) i% i0 A/ W# X
1:水平镜像& c( ]6 C2 M, ^( l: v
) ]; V4 G! u8 z# f0 x7 p: r q
$A001: SaveRAM 切换
* V9 [& ^' Q/ S; ^9 m 0:禁用$6000-$7FFF
) p. a" f2 ^0 f, W: p 1:启用$6000-$7FFF
' t: B r! Q3 S, R9 | Q& T; J; @6 U1 q6 G, f, V
$C000: IRQ计数器2 P6 s0 s4 w- V8 A( Z
IRQ计数器的值存储在此处
/ g( H# s$ P. }- G. ^; d& m2 q# P$ E# I2 H3 p0 W3 l* ^8 O3 B4 y4 Y
$C001: IRQ暂存器4 E0 S4 \8 y: a& e, ^' A+ v. O
IRQ暂存器的值存储在此处5 I7 z. M* d, z$ T' @8 w1 L
8 y; \2 {# G: s( C/ B$E000: IRQ控制计数器0
9 ?4 v" ]9 ? ]; G% r( u 向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
/ I% D7 D+ F, v0 l1 ~* E s/ g! S+ d
$E001: IRQ控制计数器1
. |, x+ x$ D6 Y5 }: s" i 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)( q" S7 \6 z! |. S% u2 h
?4 Z0 c5 X, l- C' _那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:) t% n Q4 a2 _9 C% ]' t
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68
6 u& i& t; T( n' E* APHA 累加器A入栈! ]' D8 F4 N9 V4 ?1 t& {4 H
LDA #$06 设置切bank地址为$8000-$9FFF
! S2 B2 ]6 n: b3 H8 eSTA $8000 i' V/ T" n; E
LDA #$0E 将第0x0E号bank切到$8000-$9FFF
) T- `4 ]+ J7 @4 w- B, _9 R4 XSTA $8001- f1 S+ `& m) k/ q7 V/ P
JSR $8000 跳转到子程序$8000
1 t. i7 e; \+ J! cPLA 累加器A出栈" q9 l/ U3 \7 m; Y5 c& h
# @6 k) ?3 I+ W2 V- {. ^1 G/ n
为何切换的bank号是0x0E呢?5 D+ u$ d) f/ X# m) e
因为在扩容ROM后,文件PROM结构为:# m8 T3 S: C3 D5 l) g7 y* G9 F
原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
" O( | m' j V: s. h3 e
7 M. M' \0 g3 X9 L$ |Mapper 4切bank一次是切8KB,那么文件结构就是:, a, b1 z. P4 J1 D
原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
2 D/ B, G0 W! v3 F! u4 c0 Q) ~因此选择扩容的第一个空白bank就是0x0E号bank。
: U1 j' \' i, @
! e O$ b$ ]% B! F. A. N3 d
* d; {: S; j' a* ]然后去调试器找RESET中断中可以放下切页程序的地方:
4 U' q$ @ x2 w+ y! E* }: t9 E首先长度要小于等于自己写的切页程序。
9 s3 @( }/ J: q8 h可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
]; s$ H" g# K- K' r/ ]& d
P) y j% \5 i6 e" q& L) A然后跳转到$FF7C:
7 g: Z% T" c: w; C" c3 P7 t: ^. D: P- B4 h" h
/ R* l( s! ]+ k. O
$ T8 {6 t$ S T9 [1 o7 x ?7 k1 {, }% @) C9 Z
复制下可以被替换用于写切页的程序:. R6 U L2 G$ M% }9 h: h5 ?" v
先选中,再复制:
1 I0 j; K" ?. Z' F* j5 X5 c: O" E* K$ v, L
2 h& J% g1 K0 I/ e- u! s9 \4 D& D9 K# O在Hxd中新建一个文件,把复制的数据粘贴上去:
* ] e: A( k# j4 ?% M+ c5 s( T. f7 e3 \3 X
# w+ V6 u: Y% J
5 v2 A8 Z& i7 b) Y7 Q
q9 r% H% J9 m然后回到十六进制模拟器:5 C4 i) y" V$ P0 P5 }8 R! D
转到$FF7C对应的ROM地址:4 r5 e! I0 _+ l( x. Y
8 K" p$ G# ^8 x5 E) S: c! h4 H# v
然后复制Hxd的切页程序,粘贴到这里:
" S1 u5 l3 z5 V0 @3 G: d. i* {, C$ Y& x+ T# u
2 _/ f& h5 R: [; q7 k! [
8 I* }( g3 }+ O9 @) y1 O2 C2 {/ r没有覆盖的用EA覆盖:
9 ?! p! Y! a8 ^6 Q. T! u; i, x7 L# t1 b
打开调试器可以看到变化:
* `2 h! E9 m! d* x3 V# b1 a1 K j# q$ ?
然后添加$FF7C执行断点:8 U# M/ l5 _6 T' e
% q/ ?* a4 N* G, O7 M# [
# l' x9 G/ K) {* O6 R2 ^7 b8 w0 |3 l5 e4 F- x1 s8 S* ?& ~
单击运行:+ W; R; l; n! x6 t
然后程序在$FF7C这里停下来了。$ n0 X. G' j4 C/ H7 o6 J( Y
d+ `0 ~( m% P% z: l. \9 U) }2 H然后单击单步进入慢慢跟踪,直到跳转到$8000:; H- J1 H. X# a- X! k
$ Z# Y( o, Z' p' v然后打开6502_Simulator:
; _0 G% ?2 o% d6 C8 F
) T9 E7 ^; }) r( o+ [再打开我写的数据搬移程序:0 v0 D9 q( q0 Z' K7 `9 h
6 K4 g6 y* v# }8 o; j2 k; K0 z
3 [3 v N3 G# |; s( ^
然后修改对应的数据:7 t+ m _- A3 }* S
程序开始地址:修改比如$8100就修改为 .ORG $8100
6 ]2 i% O" U& P1 d* d2 a# N复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
5 h+ q3 s8 g8 H! I0 r1 p- X% q* V# e从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82* `! A+ u; o9 W3 V3 A
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91/ }0 F( D/ E7 R6 ]9 r# f
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。9 ^3 M4 u, r! h* ~( P
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。. S3 N) s* N O6 @3 h' Z: e
中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。4 V/ n9 t+ P) G3 d% l$ r9 Q3 |! M
后面的不用管。
[( u( f% ~ Y- D' F" b
! l3 W: L6 }2 l* V7 }( N m X% ^设置完数据后单击编译:
0 o/ I$ s/ M* R8 h8 J8 h- u9 ]% r. o! I
然后保存编译文件:: R s+ R5 \4 `- }# b S% O4 k
" o: @7 f0 x/ ^# Q8 A% l; g选择二进制方式保存:" C$ [& t1 v4 r. y, K
/ ]% H. U4 m- N# Q* ~$ h1 t- t
: Z: u4 q! _& i- B: t6 w
用Hxd打开保存的二进制文件:
) K. W& S, s B8 D0 O% [/ V
0 M, U5 a! j, m* h3 Y
9 A' H# d: U$ N1 d* i; U$ D跳转到设置的程序开始$8100:
! ]. n2 F$ A* ^: N7 A, S0 b I5 o) P! T# r
5 i6 ~7 t1 [3 l+ e
2 i# B, `, k+ {0 \5 s9 v6 o回到FCEUX,转到NES内存的$8100对应的ROM地址:
+ C4 k w* i& V6 A" J9 M5 V1 u3 u7 j7 [7 M
x, L7 \8 S7 E' n& |; ~
, s% d' t5 }$ ?1 b; O# M; @* q1 t' h8 p7 _8 w" L5 m8 h3 d
6 @! A& E5 a3 ^6 G2 h
然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
|: |, X( A l$ g1 U/ g
2 ~3 @5 F6 v7 B* B. Y
; Q* P- _1 h; {8 T* @( i
2 R: J& b; g! z, \+ c) a- o然后转到NES内存的$8000对应的ROM地址:
1 c! I+ J+ B! I8 x3 g% e* n* M
- q, r- m( P" W/ n/ O S
" n! k" T1 I$ `: u- b4 v5 b6 y9 v; l' q2 [" E/ l+ x$ p
+ J/ ^" }" y ?写上如下程序:
9 C/ H g" Q0 ^1 \A9 80 8D 01 A0 20 00 81
/ a' j& `, g5 h! u- _9 ILDA #$808 c# }% r Q% f- g4 F
STA $A001 可写方式开启SRAM
+ W j- e% M y3 Y; NJSR $8100 跳转到子程序$8100
2 N7 ^- K; f( o+ B. Z
' n) w ]4 [2 a9 e2 @8 S( [6 G然后把Hxd里被覆盖的程序复制过来粘贴在后面:
7 y5 W, t1 _7 T! x' }5 n7 Q' z. q
' W( O) u! A1 V6 N5 ^* _( N7 k1 q3 `
# j2 U3 D X" q7 e末尾补上一个0x60:
) H% l6 k) D2 y0 K! L) ~RTS 子程序返回
# H. K" J0 w- B9 k" [5 j6 ^5 Y) y; @
然后单击运行,ROM音乐响起,正常运行:, b {! t0 x2 j% h
- ~: f' p, @+ [' f6 f6 E
( Z3 x4 \/ t$ f- y
然后转到NES地址$7000:; }, y& ?( @ |6 k* J
, J; O0 {" W3 Q# Q; i/ t
. H/ u3 \, y/ w) `" {3 L
+ ]1 F( Z- [( w4 M
. K0 Y# a# k- r" u7 S7 T) x9 l- Z6 T, z3 _
可以看到,$7000-7FFF都被复制了一片数据。6 T) V7 B5 I/ j( ]
测试没有问题,然后保存文件:" a8 |0 m$ {) A2 ]: q
; j/ s4 S' m) G; X! Q/ ^( \
以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。
9 `# |- f# I$ q$ Q5 U5 a后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。; D" Z' Q! I( S# ?9 B
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|