|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑
' e' K, g, [) V
P8 y) w$ Q4 e/ _! y8 c$ d& E+ V[FC][SRAM扩容教程(Mapper 4为例)]" r2 r9 }0 X: y3 I9 u8 C" E
( Z9 R: ~2 m6 {' e$ Y
时间:2017.4.28
" z0 N4 Q# G1 N: F3 @- c作者:FlameCyclone
. f/ Z5 }& J" E0 r* b, ^% a" ^; x工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
' [+ ^0 U5 c! T; lROM:双截龙2(J).nes
" M' U/ \4 K/ e* p; w( ?4 J* k适用:没有使用SRAM的ROM
* t0 a4 w2 k! t; w9 z
$ n; U2 i9 e1 o5 a. K H首先用Hxd打开ROM:
4 }1 V# ]+ `& G& y ! S# i$ t; P, u: V1 T1 ?: T( t+ ~2 W
然后扩容:
$ x- s @! [; _% r9 H3 r( n J/ M: o+ R {; d6 G3 t# n
! x5 O: h. ~5 E
; [' D/ u& h+ V# d% U, L" P- g; _2 H, a9 {# f, `
# b4 d+ K$ J Q+ n, ], \
; S& u8 a0 ?( O( H3 u2 J. g* \# n
3 \4 u& }, [; U i. r, O3 D. W. I4 t2 j/ m; D# `! f
9 i& T9 l9 F3 x' }* k
先看看任天堂产品系统文件对NES文件的说明:
' u' I8 Z+ F( ^, S1 w HNES文件格式% J; x3 ]1 `6 k7 j: g! q
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。
4 ~1 ]6 @: M ~偏移 字节数 内容 6 K9 ~$ T" G1 I
0-3 4 字符串“NES^Z”用来识别.NES文件
- P! H4 n1 J2 A: O: Z4 s' @# b4 k4 1 16kB ROM的数目
+ a3 Y+ w! t; ^4 ~ Y* r! ^5 1 8kB VROM的数目
' R' W! |6 y* b( Q6 1 D0:1=垂直镜像,0=水平镜像
! \6 D0 o+ B5 Z D1:1=有电池记忆,SRAM地址$6000-$7FFF / F6 q2 U3 ]7 H2 D3 ~2 f
D2:1=在$7000-$71FF有一个512字节的trainer
6 _; {, h( W" l- R3 H+ b R D3:1=4屏幕VRAM布局
! T+ Z" ~: y' U* \! t D4-D7:ROM Mapper的低4位
& l; G: e3 n; M% W7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) 8 @! ]# u$ y# y7 i/ |/ c1 q
D4-D7:ROM Mapper的高4位 3 C' k" \0 h, F+ l$ s G4 Y
8-F 8 保留,必须是0 ) _+ h2 y6 y5 j+ { x5 I+ m
16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
/ Q7 q. E; R( z# \. {& b! d-EOF 8KxN VROM段, 升序排列
- p+ k: `5 U5 d7 h4 w f0 k$ L' g4 y* s. {6 M& v9 M9 a
然后知道这个ROM有0x08个PROM和0x10个VROM
( o* g# P) w v: S接下来扩展PROM位0x10个:& W1 X! k3 a7 s
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
J' V3 ? ~; r) I9 K3 b
8 ~5 b) ^; k j. ]由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:
7 A+ o0 n5 o, d最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
2 W0 J: J1 V" B2 w5 h: i5 A于是可以得到双截龙2的是:
+ a2 W2 ~5 ~8 Y9 v* M(8-1)x 0x4000 + 0x10 = 1C010。/ i2 P* q( [! M3 n- E
然后跳转到1C010:3 b" h2 \' s) X- M

$ \$ X8 m! W# q; L; K, S6 n1 j
5 [# A" J3 V6 R2 e. t 3 L1 ~% }) D1 m. S
然后插入0x20000字节的FF:
6 E2 w" t! n R
. Y9 d9 D' H5 I! c
& u7 G$ ~3 [& J$ j( z" L" a
1 I6 E2 f" ~* }+ e/ }( C7 i然后保存:
9 b8 K! ^8 P9 d, @
$ W: ^4 ]# c1 I! A, a% _ 3 \. f A# F* {9 g$ G( J7 O- p) k o
用FCEUX打开正常运行:# k6 B3 b$ D" j% ]! m+ x$ y
; i5 A3 G% n+ {3 y& x2 c1 _
查看文件信息:
+ e" W# A# _5 p7 }4 i+ P8 w3 y! U ( F" U+ n, |1 I
- i$ ~; }! ?1 ~5 g1 }
接下来切页:, N$ d7 }6 P" ~& u! h) {; U
先打开十六进制编辑器:. n! v- P7 S ?! }

7 {; O/ G: u9 N; B拉到滑块最后,看看重启中断
3 c$ @4 V" \! H* S7 @中断地址 中断 优先权
- ^6 _: Q5 P! g; W$ ^$FFFA NMI 中 ( ?, B2 l: z! F! M
$FFFC RESET 高
6 D3 a" x* a9 F. S$FFFE IRQ/BRK 低 5 G/ j9 Y: `7 d- T4 ~3 C* p
: g5 z0 z+ n- Z! z
RESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
4 L# b' M* R7 A4 S/ |1 ~由此可知双截龙2的RESET中断$FF65。
5 D& C5 X! Y, O; a接下来添加$FF65的执行断点:
4 [; h' A) _2 q7 ^! F7 z! L: @; C打开调试器:6 Y7 E$ e( m: C# s2 i& F7 r
) R, Z$ y8 x! F* [
添加$FF65的执行断点:
+ B& _1 f7 ]% k : y1 ^' e& P l2 d6 u/ e: d, F
, l1 M3 X5 ?$ b5 W2 P) k1 }
m) V, v' G1 e( Q2 z单击确定:
, D$ _7 J3 c( g' Y# \: X% I : x, i5 x4 p1 ~ z7 H- `7 a( W
0 ?' z6 c3 z; u4 v7 ~然后重启ROM:9 Q* y. I& P" l- r$ o+ `; `' ]
: p r* g8 ?' t
调试器此时弹出来:
, O5 j2 y$ l4 u ) y& c. @/ ]. Y6 q
然后打开Hxd,写一段mapper 4的切换bank程序:
6 g, j+ A# q8 L5 D, p# [先看看mapper 4的说明文档:
3 o5 R. m7 c+ s3 {+ q0 @3 H% W' oMapper 4* D9 h. V' a; G2 _
4 j4 N, k9 I+ R$8000: 模式号
( b$ B/ D7 s; W5 Q: S, H' K 位D0-D2:
+ N, h4 H6 O! Q& ?/ z/ b 0:选择2KB的VROM存储体映射到PPU的$0000
# [ _% l2 B, e0 j b 1:选择2KB的VROM存储体映射到PPU的$0800
! \5 H7 D; C, E- G8 \$ E" I 2:选择1KB的VROM存储体映射到PPU的$10007 n: U! J+ Z6 D
3:选择1KB的VROM存储体映射到PPU的$1400% H3 t5 g. k$ B- i; k- j+ N3 D/ r
4:选择1KB的VROM存储体映射到PPU的$1800
/ U" l* q u6 ^$ T: Z9 X& m6 ^ 5:选择1KB的VROM存储体映射到PPU的$1C003 \5 Y; ^' W& Q+ W
6:选择8KB的ROM存储体映射到$8000( d0 j5 m5 Z, m& n
7:选择8KB的ROM存储体映射到$A000" _6 u. d+ s, Y: H
位D6:$ @6 n7 l/ ]# ~6 ^& B' ~
0:允许擦写$8000和$A000" ?* x3 Y5 ^: p& I6 z8 j: f
1:允许擦写$A000和$C000
/ ?; t' k/ C- v5 A; K9 ` 位D7:
0 W1 u' z" X& k4 r( ^ 0:模式号D0-D2使用普通地址3 n3 o6 n: G% b% q( J
1:模式号D0-D2地址异或$1000
. d7 l0 P1 J* G1 ?/ W z- d) E0 ~9 G% T. C- x; C
$8001: 模式页面号9 I6 s! p/ k/ m; N. I* G2 W$ `" z& \* L
写入一个数(00-07),切换存储体到对应地址" A0 }. B; {8 j% Y/ H9 X
; Y' i. F! J* q2 m$A000: 镜像选择- b# W H( r% q' E' w; m3 E% g
0:垂直镜像" F/ \, X( E' _9 Z" B, @) ^3 Q
1:水平镜像
6 e. r, o6 A/ M( Q5 `" b2 N, f9 R5 P
$A001: SaveRAM 切换
$ V2 Z; E6 y1 x 0:禁用$6000-$7FFF
: S- E+ F. x( ^% x2 |# A, ` 1:启用$6000-$7FFF, A- A0 r1 K" b7 l0 M+ X6 x
: T. a k: X. N. N- E
$C000: IRQ计数器
- @/ S# S [1 i. K# b; M IRQ计数器的值存储在此处
* t: i8 N" ]& v; D7 q& C1 Q3 R1 t5 M: W W
$C001: IRQ暂存器
G3 O3 l; t& Y& G IRQ暂存器的值存储在此处* X: r5 s1 |5 P; a- f
. \* K1 ]6 H4 m: [- _$E000: IRQ控制计数器0
6 [! w7 s' _# ^8 M, g 向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
( K4 M9 W! P' f- e; u% i8 \3 a9 L9 z1 T5 W( U
$E001: IRQ控制计数器1
7 ?) k" X8 }* z0 A1 U c6 h% J 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)
* `5 `. F5 C( h* ?
o; @3 r) {& I: X; o那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:; H$ j% q$ [( u0 }
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 689 K6 K# Q( ^7 @. j+ \, k" U
PHA 累加器A入栈7 q" e2 m" R K
LDA #$06 设置切bank地址为$8000-$9FFF: Z7 _% ^. h4 W/ {: F4 Q# K/ O
STA $8000 H! y. u* N) ] w' L
LDA #$0E 将第0x0E号bank切到$8000-$9FFF
9 a# h6 @( z7 {/ g2 d3 h. q; xSTA $8001/ C8 P/ O' \$ B! o2 ?! M. h# H
JSR $8000 跳转到子程序$80002 a3 z: H7 ]! J$ d" M2 [
PLA 累加器A出栈* V+ O3 n' K& b4 v3 }5 p/ W
" M2 X7 o7 q1 x2 q
为何切换的bank号是0x0E呢?
2 h [, C9 j* N( x2 a6 j/ m# z因为在扩容ROM后,文件PROM结构为:
$ o+ N, ^- M. S原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM$ @3 ^; j5 T' i# \. |2 A k% ^( m
' ~$ e( E( I6 N4 U; pMapper 4切bank一次是切8KB,那么文件结构就是:% Q5 S4 X- x4 [3 G' w8 d, a
原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)% }0 h% a! S, X n' A. Q
因此选择扩容的第一个空白bank就是0x0E号bank。3 S+ A& d# q" H, ]; e# Z! P( k

% u( d- e, X2 R7 c) u
) g: G6 W; }" d4 {% C, j然后去调试器找RESET中断中可以放下切页程序的地方:; G2 y; h! Q8 s+ u, t* ]
首先长度要小于等于自己写的切页程序。
! d- C( v# h$ S可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
& ]& c5 p+ K! G1 g + B4 }! r; z0 u" w7 X+ p
然后跳转到$FF7C:9 h- O7 u$ e! S1 k P1 J
/ F, }9 p3 ^$ e0 M
3 n; i# y* f- c7 @

5 T( _7 v; [) v, n. b" ^( W
1 }: |7 H, }$ a4 n; N复制下可以被替换用于写切页的程序:( @, i% V Y* b
先选中,再复制:) `! a6 R$ C* ~

$ O: p, `5 `1 a
\$ {* Q q* C0 U: Q6 h在Hxd中新建一个文件,把复制的数据粘贴上去:+ S. C }" t2 L+ o) U6 ~
. C# E5 ^. j' m9 u, Q( f: h) ]
& k/ m4 B# @4 Y/ p: p) K$ g% s

# X; E @/ ]* \* C1 r% `
: G L8 O! @ J2 n M然后回到十六进制模拟器:
6 F i7 @( O( ?5 v转到$FF7C对应的ROM地址:
/ `; Z6 o7 {6 I. j9 v, @2 d 5 G B h" l5 U a- d: n
0 C) u4 Z5 i+ W/ e8 Z ^
然后复制Hxd的切页程序,粘贴到这里:5 o5 J( [2 f; E5 |2 |0 y; ~

8 o" O6 m( \4 O# ]" D5 o
7 d {2 F; n8 M, z' e% Q3 M7 e " G5 N, k9 a1 Q9 J! t% ?
没有覆盖的用EA覆盖:( \! ]- {2 o* t% P

4 M5 P4 }. I' }3 p' ?9 ^5 H2 v) H6 P打开调试器可以看到变化: ]3 W8 b; T( M2 ~# \, E, m
/ D) G& o- C" M$ G3 j
然后添加$FF7C执行断点:
: G9 q" g1 O" |* u$ M + w- C4 ?- K% w) p9 e& `$ o2 [! J6 h6 M

8 r2 Z' m$ a2 X+ T8 E - V2 `$ S' a+ | ~& g4 \
单击运行:3 q- @7 G! o0 V7 ^+ q" Z) D
然后程序在$FF7C这里停下来了。
3 P0 Y$ J6 K" x* ^1 f& Y 9 d2 x, @6 g0 G+ F" o
然后单击单步进入慢慢跟踪,直到跳转到$8000:/ l- V+ B ~6 N4 s/ {! P6 |+ X
4 D9 A3 t& O' Q- Q4 M( |1 {
然后打开6502_Simulator:
& ~2 j! s3 ]+ i% g! ]0 r + h* _( L; M' Y* R
再打开我写的数据搬移程序:
r' i2 h& J O; z2 C1 M: L/ T
7 i: z: \7 g) m2 R1 T# B( I
2 f- L- G8 G; v4 u }然后修改对应的数据:' V% p2 Y$ X0 D" d# E
程序开始地址:修改比如$8100就修改为 .ORG $8100
3 J6 d& q' X+ {/ \$ w9 p" O& q& Q复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
. i/ W* c2 ~# w0 U* M从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82: h5 S9 r, e: ? N0 E
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91
( L8 ^$ ~5 ^# }/ I' E: `3 \4 A. h+ J, l也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。9 k }# g5 d) t# L! [
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。% G0 k* _# M2 Y
中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。- ^7 }3 m" Z" Y+ w2 r
后面的不用管。3 T- @; o& i `7 H
, O! G' E) M! X
设置完数据后单击编译:4 h8 n- r; w/ e3 W* h. x e3 d+ }
; X! [! o5 z: |' }% `% G
然后保存编译文件:2 _- O! G5 P$ T) u }8 G

! B2 r. D- ?% n; ]. c选择二进制方式保存:
, t' S. o3 g& \/ j% O3 {
2 \+ A( Y* g& H4 ]- F& P' `# m
% `: W! X; w3 R用Hxd打开保存的二进制文件:: O) E9 ~ I0 p6 R
3 F, S- s* C! k$ t7 E! q' b/ R( t

/ o" \- Y J- J跳转到设置的程序开始$8100:7 O$ d# W1 l. S% H/ c3 l

; \0 E+ m+ a; g# p" ?/ v; U, H " P9 U C6 G: g% u k4 J

& d) b; E7 s1 @回到FCEUX,转到NES内存的$8100对应的ROM地址:
: a) Z, e e2 j% `/ F! V - x( P) ?* O; Q

! H" E3 q1 i# l! l. P 6 t) Z% W0 t$ ?) |. t- g# e! Y
4 P5 w2 O3 S% @4 B1 }

5 I k) d: V8 M3 a8 ]% ~* t) B然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
$ _' z4 ]! J& o+ l# _9 n
1 ^1 `' `* J* o$ ?& v4 r . t Z5 l9 G- \

+ u0 W y4 C5 d% M, V" Z) A然后转到NES内存的$8000对应的ROM地址:: p+ a5 P1 }) c
- g' S k _* d) H( g; h- c

8 ^( ]4 |+ R& @/ F9 B: g ) ~3 [ \# X) q0 i
; v# P* f+ Q) M9 J9 }1 j1 [3 A
写上如下程序:
9 }! y2 y$ l' YA9 80 8D 01 A0 20 00 81 2 o' a- I- J4 B9 Y! z# P( `
LDA #$806 y6 a, }/ |! x0 S; ]4 f
STA $A001 可写方式开启SRAM
, B: ^2 H9 f# x) O1 E Q6 G, `+ }JSR $8100 跳转到子程序$8100" q5 d2 p, g1 _* i
; @ Q# P9 `1 {" C ?) u# ^
然后把Hxd里被覆盖的程序复制过来粘贴在后面:: t* F# s2 L! }& Y2 j- p$ F

- v1 S$ A' P" T: T: N% ^ ' t. m! b' a& O& R' v0 e3 H% u
, N+ r, B+ e/ Q% C3 t
末尾补上一个0x60:
& N: _$ _% T! G, T. `: tRTS 子程序返回: Y2 C* s( [' C; V* k% d c/ ]
7 f3 q" b* H% i6 } `9 y: x
然后单击运行,ROM音乐响起,正常运行:+ `8 J+ ^! L, ~9 }& M1 Q1 J4 f
0 k7 @# Q0 g% [0 D! {
/ Z' c( ~8 D* J1 g7 M4 |
然后转到NES地址$7000:& d9 ^8 o3 h' _+ x ?3 @0 W m

. f& o3 d: j! k! D2 o8 f' G8 D/ q : H6 d6 I2 w$ O, |3 [. d

6 m) i g" @- J
- u8 d4 i+ _* r% n* d ; ?7 N: M3 b2 \- G
可以看到,$7000-7FFF都被复制了一片数据。
5 S4 \" z& G N- J测试没有问题,然后保存文件:8 F+ e2 ? G& l& Y n: d- D* _: {

. Y$ U$ Q2 D2 n. v0 Q1 K以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。' l6 r$ @% o, ]! ]
后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。
( ^8 N8 z% |- |8 ~ |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|