|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 1 V" M8 W: M& S7 G S
% i/ z* F- _5 k[FC][SRAM扩容教程(Mapper 4为例)]
l7 @# i D: r w9 B) Z2 ]( b3 W- u
时间:2017.4.28
. \$ m2 U5 w! l8 }/ h* i7 ]作者:FlameCyclone; y5 ~5 t3 B3 _2 e0 W
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator) R; ~" t0 ]' }4 L) j0 i) S5 w. k
ROM:双截龙2(J).nes3 D" t6 G9 n. F8 G; @
适用:没有使用SRAM的ROM' H7 ]5 Y" J( x! ? l- R
, x- I; N3 P6 W( o4 ?4 d4 \
首先用Hxd打开ROM:/ p% ~( L& q: O g3 B# n

/ \2 z; q/ Z- c7 {: z3 f然后扩容:7 ?+ k. \* [/ P$ }: x
6 K/ }4 [2 R. ~2 M1 w, w* w6 L% N. `! H, v. ?8 U6 [
9 D3 {7 _6 X7 P8 ]7 x
* y1 s+ Q4 @7 F( Y+ i( b7 |8 Q4 A7 u0 Z. Q
! J( `/ ?+ i Y- ]3 ?1 T
0 m' ~* E& D6 g3 C: h+ z( E' i) f* V) n5 e4 B _
, I6 ^5 d- R0 N( `% f# Y! n
先看看任天堂产品系统文件对NES文件的说明:; r. }- ]( r; ^( y4 q& z) O: {
NES文件格式- q! j0 b0 ~, [, y. e
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。
4 g# f2 k8 C4 M$ |偏移 字节数 内容
0 Y, Q. T, S: y- F0 E) C2 \0-3 4 字符串“NES^Z”用来识别.NES文件 0 L, L& u: ]" i. a, w/ j, J2 o; b; [
4 1 16kB ROM的数目 4 V9 h: @- _. x
5 1 8kB VROM的数目
3 B, M/ H2 b I6 1 D0:1=垂直镜像,0=水平镜像 s" H1 Y9 H' y9 j2 m' R- ^% P
D1:1=有电池记忆,SRAM地址$6000-$7FFF
8 s7 N' W1 } b0 R1 D$ Y D2:1=在$7000-$71FF有一个512字节的trainer
, x v( r6 g% k( C. [5 l; e3 m- R D3:1=4屏幕VRAM布局
( Y+ ]9 H8 s* A* x' ?! l D4-D7:ROM Mapper的低4位 - s! ]' j- X, E* k- \
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^)
+ K+ t2 |8 V0 h1 V* C D4-D7:ROM Mapper的高4位
0 J& x! V# b7 G" |6 M8-F 8 保留,必须是0 / U+ d! L/ @$ \) n% C. I' S- R3 t
16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前 8 s& `0 ?7 V g
-EOF 8KxN VROM段, 升序排列 & H) }$ ^, i7 W( E) U/ [$ I* D) ~
E- y/ L6 v' e; q然后知道这个ROM有0x08个PROM和0x10个VROM( W4 B/ ?- [% }9 c2 Q
接下来扩展PROM位0x10个:
: B) V" P: |9 ~1 o$ L/ @% d先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):0 T) V! _5 p( A+ e6 V- T

0 L$ x" m) \5 n9 `由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:. H U, q1 f' h& ]
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
4 b7 ]0 E5 `* ] X W' A于是可以得到双截龙2的是:
! ~3 r5 O( ]( C: k! u6 G(8-1)x 0x4000 + 0x10 = 1C010。
7 Q6 s9 j) V1 |1 n* h0 Z& m然后跳转到1C010:) }- @# A* h/ f" x* C G* `
; Y# U$ O, y7 v

8 e3 Y* i, F- F3 ~0 S' V& z- p - m2 |) Z9 U8 A3 P
然后插入0x20000字节的FF:" V3 d8 u" F9 s8 k

! }, N; ^% \, M5 P- a% W1 z" @- g
' F. R% S1 v) c4 ]0 U$ y+ Q
% N) a, W+ L* w# ?然后保存:
1 }! _) n2 V0 [0 y # ^3 W/ {( i3 f% R/ J

2 i, P8 Q& f1 r" Q$ U用FCEUX打开正常运行:
. q" z9 t0 i5 a7 f$ j+ F1 g 8 y; [* [7 l5 e
查看文件信息:
2 b& [# J: m% q$ E * r. H2 K' a+ H9 ~( U/ J* `( c

) c! F- P7 a+ y6 Z接下来切页:
. `" `% ]% q( X/ y6 j) J. U先打开十六进制编辑器:
% w# M" c5 O' q( q
4 i- S+ J$ H5 A# P8 Z拉到滑块最后,看看重启中断5 y4 C* ]1 ~9 W
中断地址 中断 优先权 9 X" k; `6 X U% C" z
$FFFA NMI 中
! B6 h' e/ n. ?; `$FFFC RESET 高 : y3 O& ]5 ?# k; ?0 a* n, }
$FFFE IRQ/BRK 低
3 p- W8 h; w2 f 2 Z" |. f; }, l: l: p
RESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
0 u" W, @: `1 M7 ?$ D, Q9 i由此可知双截龙2的RESET中断$FF65。
; p2 L8 O7 d. N# e( v9 D- P T+ V接下来添加$FF65的执行断点:9 s+ Z; L, [% p" N; U G
打开调试器:
# c8 j% t" H+ |* P/ V & Y2 N, U: B$ w; d4 `7 @/ U
添加$FF65的执行断点:
. ^! ?9 w v3 l: K$ i 1 d( y1 f; X& c9 ]: a

( ?$ B6 Z* d; b3 Y5 `+ u8 g% |. O4 M" ^ [
单击确定:
- t2 _$ k* l4 }- ? 6 G8 n* N8 o8 T+ }9 p
4 g+ j# K' S6 g: D7 y/ W5 P [
然后重启ROM:8 H- n+ c0 M- l/ R: P) z: ~
3 @1 b' _2 w6 c/ r w4 L& E3 j
调试器此时弹出来:) ]& H0 K |3 m$ F9 ~* W' N3 o9 a0 x

+ P3 J& |1 H6 B5 \9 N, g' J% f然后打开Hxd,写一段mapper 4的切换bank程序:
2 t/ `0 F* Z) }* T! d先看看mapper 4的说明文档:
5 n# p9 i% z' J" Y4 b9 c) rMapper 4
! q: q* U" E& M- _- P! A1 R
. A6 x4 v8 K/ j! G$8000: 模式号
& Q8 }1 X& [2 [ Y& y: c3 N 位D0-D2:% _( C8 x* g% {0 K0 H; b% @* [
0:选择2KB的VROM存储体映射到PPU的$0000
$ ^# z2 R/ v! n o; i" ^! y6 I( C 1:选择2KB的VROM存储体映射到PPU的$0800$ u3 x; c* _6 p
2:选择1KB的VROM存储体映射到PPU的$1000
8 I# T6 o# t* Z ?- ?# J 3:选择1KB的VROM存储体映射到PPU的$1400
3 x+ J. g9 U7 k9 Q( r+ C9 D 4:选择1KB的VROM存储体映射到PPU的$1800$ F P! p5 B! b A! }
5:选择1KB的VROM存储体映射到PPU的$1C00# r7 t% U* l5 Y: L! C1 C
6:选择8KB的ROM存储体映射到$80006 U, W) v# V' x2 G
7:选择8KB的ROM存储体映射到$A000
/ Y, o( H0 J& g7 q h 位D6:
+ c% y; y& Y e4 y1 L% B9 U. u7 v1 P 0:允许擦写$8000和$A000
' }2 [/ ~- B) j' ~; o" U 1:允许擦写$A000和$C000+ I: O& a) ?+ B' S% L
位D7:5 Y* ]+ d6 j* J# y
0:模式号D0-D2使用普通地址
+ b X# f, ~- M3 M 1:模式号D0-D2地址异或$1000( |! ?1 {: a+ P8 X- G' h
- H1 U( c: z# W) P
$8001: 模式页面号/ |! q5 _. z5 C) Y1 z2 p% I
写入一个数(00-07),切换存储体到对应地址1 \% ]0 Y7 ^* L9 L
* z* E. i- j+ N; x. K
$A000: 镜像选择
G, ~8 m# Z& X6 d$ q( L$ H* Y 0:垂直镜像
$ j9 q6 |5 L @% m% Q, s 1:水平镜像2 {% v S/ [8 q7 K
9 C* n4 W/ p M$A001: SaveRAM 切换
; n: j. M: C( C! b 0:禁用$6000-$7FFF
6 c/ D+ m! f2 W# g6 O; E 1:启用$6000-$7FFF' ~* A2 n* B/ @( ?7 B1 V2 @1 x
L: K" m" f- d5 D$C000: IRQ计数器) y, v+ g" }; j1 \5 n: C
IRQ计数器的值存储在此处
. s& \; v# D8 _9 ?7 y
/ c. G" o9 ~0 p7 u$C001: IRQ暂存器
8 z' i" O; g0 K v% T& _ IRQ暂存器的值存储在此处: f' m2 }0 C. z( w
$ {0 _4 v I8 ~1 ~ p7 |
$E000: IRQ控制计数器0
, n% P" V0 ^( P9 l& I8 o 向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
) i; g7 X2 c$ h5 m7 {3 t" t% ?; K* Y. }
$E001: IRQ控制计数器1
/ J% z1 L% J/ {7 w- V- [ 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)
! A# s+ o/ c5 k; U. k4 s, e# [+ N: z" u
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:
+ J: r; M# G2 T) D+ q48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68
) r% S$ p3 ?1 e, ^+ Y' i( @/ BPHA 累加器A入栈
0 o: n' ^/ M- f7 ^9 s. ]. ULDA #$06 设置切bank地址为$8000-$9FFF
$ R8 E) |0 n; U# [3 iSTA $80009 b2 h; X b' `' ~$ N+ H( N
LDA #$0E 将第0x0E号bank切到$8000-$9FFF4 b& n; b0 Q6 t4 y' {
STA $8001
2 D+ H J; v' T& I$ sJSR $8000 跳转到子程序$8000
8 A& `! ^! s; k; }& Y# f9 A* ]PLA 累加器A出栈
1 X% k$ j3 q8 n' I; m. ]$ G% K5 q9 X" i6 V$ G
为何切换的bank号是0x0E呢?
; y8 ^/ [0 f. P8 A6 W6 J" K# p1 f因为在扩容ROM后,文件PROM结构为:# T& Y; W4 [" D' N/ t: |, q, J% m
原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
7 T. j8 R- c0 J( H+ B* j" C; ^" p- L( _
Mapper 4切bank一次是切8KB,那么文件结构就是:) p0 Z- l: F0 f | X& I B: L3 h: o
原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
7 o* g0 ~* \# l" j" @. A因此选择扩容的第一个空白bank就是0x0E号bank。
o+ S% d5 U* b I6 F5 m: Y" F * Y3 R7 ]$ T9 H. a
5 @$ [+ q( ^. {% ^8 p然后去调试器找RESET中断中可以放下切页程序的地方:
2 K$ t4 K+ E1 c6 x! T7 I7 F首先长度要小于等于自己写的切页程序。
+ i) y9 i3 p! l3 n可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)/ m: }+ a0 }! B
! V" D+ g& Y& N, j# N5 |! ?- p
然后跳转到$FF7C:
# p* m, y* B8 Y3 R+ J 8 _% @" k T- u8 n" c

/ v9 h; r6 K6 N1 o0 m
6 V$ @7 P5 I) E/ X0 \1 ~) a: S8 s% M5 t7 e! s
复制下可以被替换用于写切页的程序:
8 B+ y' ~6 [9 U$ \3 R先选中,再复制:. l ]9 e& \, M! Q9 I
& {2 C7 h8 g, B% L# }

9 c% U$ w: @7 a, C在Hxd中新建一个文件,把复制的数据粘贴上去:
% U0 \( S1 D4 q. j 4 u$ Z* K3 m2 H9 ]. u3 p ~5 k8 C
! [( N+ V6 N+ t! f7 R" U

% J7 q9 X. V( u$ B f% u7 l: y' G s3 J4 w
然后回到十六进制模拟器:
1 g, }& h1 r" v; s* ], r转到$FF7C对应的ROM地址:
) h5 d8 e, {1 F' ~; S ! C8 a0 N9 d# o. K, o+ t& R2 ?. l

' \, w6 n7 E- z然后复制Hxd的切页程序,粘贴到这里: \# f& `. E8 l( _- R

4 ]' C- f1 t+ d/ u
9 @6 P( h* }( y+ h ( e" Z5 B4 U v: I# E' g9 t
没有覆盖的用EA覆盖:; k: Z! H+ K. A; F: w' i
& C% Q& Y" a# A% ^8 ]
打开调试器可以看到变化:
+ m8 j2 |9 }% T; ] - @: z, R$ ^, \2 [5 L2 e5 @& F
然后添加$FF7C执行断点:
! F6 \* A& X8 t) O9 h/ f H
. U# b# s6 y( ?* |" w 0 l9 C/ Y- f) F9 W$ g

: ?* r" w+ j" I) w' Q, m6 U单击运行:
! @ b( `+ j$ Z' c然后程序在$FF7C这里停下来了。
/ b Q5 E9 X, M ! b- ]( U6 d/ u, q
然后单击单步进入慢慢跟踪,直到跳转到$8000:, W4 \) {' R- |/ P0 M6 J
6 g; e3 J' I' a9 n5 K/ W
然后打开6502_Simulator:
! @& |# h7 m. K
, d J, j' S; Q; V再打开我写的数据搬移程序:
3 A8 g# X1 T' V' O: V
( g9 m$ S1 l" ^; m' }
x; y( Q9 R3 H( I- g) b L然后修改对应的数据:
" n9 v) [3 ~0 f8 L- }程序开始地址:修改比如$8100就修改为 .ORG $81007 }% M; ]( S. ?
复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
( A0 r. U {5 A( B6 } U从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$823 p8 X( g5 S' U$ ]5 A; [4 ~# o; t
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91
$ c* J0 f H1 t$ f& F( g O也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。
* D3 W x1 V# N( z9 K, x3 |如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。9 I- ^; g# l4 I* @3 |6 Y- W
中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。
/ l# Z7 `, _, {! x后面的不用管。% s. j& t8 V! _, L, E# _
6 ~, c) n0 O1 B/ W& J& \设置完数据后单击编译:% B! e! r& a% G, I4 p

0 t. ~& U# G6 H' q6 [+ m8 n然后保存编译文件:; N. \5 Q. r& a a
( `: O) c6 J4 E( J% b
选择二进制方式保存:
7 ]- m7 U4 t7 X7 ~ 2 l/ I: l1 H: q7 C

& [4 k+ v |: C* K) ^用Hxd打开保存的二进制文件:
6 G* r% l2 q' d1 ?7 }2 u( ` % G* |# @" a6 m! G7 I
4 a3 ~+ I# r( I; i6 z( \ K
跳转到设置的程序开始$8100:7 y( x& e8 X* J4 J

3 R, [5 [8 G. Q # k9 C/ u: n* P; l
! ?9 q9 w B, t/ a4 Q4 }
回到FCEUX,转到NES内存的$8100对应的ROM地址:
5 u3 e6 N1 v8 F1 t
0 g0 x0 x9 d- P! _ : N: |" e$ Y4 P$ b: I% k8 V

# m0 ?- e" p$ ~8 k0 j
( |- v2 M. \+ a, D8 I8 Z4 v
! r5 v1 q7 r- N6 r9 B8 A8 P6 {$ ]然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
8 N( e( `: E4 k' ^2 S4 p ~
5 }) V* g3 G" e1 t Y
% W$ m5 _+ k; w) A" V4 y. e6 \ 0 D$ }0 H' a0 ~. R" ?# c
然后转到NES内存的$8000对应的ROM地址:) {2 T6 i& |1 D1 M x# N- G% V
( D: V. X. }6 ~ c8 h% F
6 W! o0 I6 C4 E: P5 g; F2 k5 {5 s
1 C. q, s; S8 a7 Q1 h

* d) p: v- b# x/ D3 \2 f写上如下程序:7 W' b% m0 W# B- I B* K
A9 80 8D 01 A0 20 00 81 6 ~8 I( [2 E5 A b9 x+ F& }
LDA #$80; V8 u5 U- ?( l9 ?9 ]: g" o: J
STA $A001 可写方式开启SRAM* I2 e3 h7 g6 E+ [) ^
JSR $8100 跳转到子程序$8100
6 `* ?2 M' ~. D0 l W3 L9 y J0 T/ n: `; O
然后把Hxd里被覆盖的程序复制过来粘贴在后面:
' |7 e! z8 P: u$ U
3 m5 N; g# g) ^% ?9 B 7 n7 D. q. o/ ~1 U, s l
/ S$ f4 p, y" M0 H; F
末尾补上一个0x60:
& p$ a! z3 x4 N/ E% I: {RTS 子程序返回
7 N( k! j d7 P% P
" c. s6 E" ]& a% U, w然后单击运行,ROM音乐响起,正常运行:
: d1 c- E5 s: [5 Y: a% s
; x5 ]: B; Q7 v* o g1 G, H+ |. b* U r7 i# W2 s
然后转到NES地址$7000:+ R5 B$ y& w5 n8 h9 ?3 I+ g
+ {* p, i. g# h+ X+ y! q! l
% r6 `, e/ B( v
0 U& i) `7 k; G3 C4 g8 U3 }

% o" @) k7 G; L& J2 d) w
3 z; J$ V& O) ] ^( X& i7 E可以看到,$7000-7FFF都被复制了一片数据。0 M* l* m1 @* O
测试没有问题,然后保存文件:
8 C4 F+ G; `* C
% N, B1 T& V, P以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。5 J7 y) B2 E! P; d
后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。
/ i0 Q7 ]& p9 S/ ] |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|