|
|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑
/ O" V$ Y; A" p4 y3 n/ B6 q& a8 b, d- Q) m# @5 B0 c/ V8 U2 A
[FC][SRAM扩容教程(Mapper 4为例)]7 ~5 K; r) u* t1 E4 A. Q" p
$ d4 v' `' u6 G- {; k+ W# M3 J
时间:2017.4.28 u) L, R! V) q4 }9 E: w
作者:FlameCyclone
# o( o$ g, m; I5 X& q$ w! e' t工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
5 b$ [: r, U& B; o2 D2 MROM:双截龙2(J).nes
$ P1 }) w8 p/ Q A: v7 D适用:没有使用SRAM的ROM5 a, d7 k1 [( v$ ~( B
8 [. A3 K0 u0 q9 v首先用Hxd打开ROM:& F3 R8 G" y4 ?* r2 v

: q8 p* p% y# X& F然后扩容:
7 l8 f) `! l% z: j- F5 o1 O7 ]% R+ r. U( T9 c7 ~ \
j1 d Q1 I T) v% ^9 i5 J$ ]) P
8 I! P" b- }6 i7 ^. v+ O; f, N
; ~2 [$ U" Z$ Y- q/ K
* R+ i% a/ ~$ J: ?2 F
2 \+ k( R2 M* h! v5 M" g- ~7 g# O- F" G9 o% y, j- c
1 i& O' }' g1 c" y5 `& E先看看任天堂产品系统文件对NES文件的说明:
/ t: g0 ? e' k0 {8 H3 B NNES文件格式
% s! w; _, ^6 s9 |5 u* C! J.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 - M* |- `4 W3 _, N
偏移 字节数 内容 % L$ ~! V* m) T$ \* F0 X# Q) b3 L
0-3 4 字符串“NES^Z”用来识别.NES文件 6 c' o; x* i# J1 d4 a- W
4 1 16kB ROM的数目 $ F8 @7 a: g5 ~8 B: I
5 1 8kB VROM的数目 5 v! w. m* n- n, Q8 }; e0 M2 _
6 1 D0:1=垂直镜像,0=水平镜像 7 P/ E- K8 ]! B4 u
D1:1=有电池记忆,SRAM地址$6000-$7FFF : ~. {: a2 e" W
D2:1=在$7000-$71FF有一个512字节的trainer
6 U. `* V; ~0 W1 r- L) D/ P7 I$ _' u D3:1=4屏幕VRAM布局 + m! e. u* x' c" u/ K/ J
D4-D7:ROM Mapper的低4位
`6 d7 V/ O3 {8 r o9 b( b5 Z+ E7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) . t( S% z# g, o! Z+ ^1 n
D4-D7:ROM Mapper的高4位 " C I( y G Y: e, F) N( g! E
8-F 8 保留,必须是0
5 C) k+ J9 L% B& ]1 P7 Y16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前 ' z3 ]# x: Q3 U! r& e
-EOF 8KxN VROM段, 升序排列
5 J: q9 ^+ k7 D2 E- o2 D& m4 q) _: I" X, p2 I4 V& I
然后知道这个ROM有0x08个PROM和0x10个VROM
# c9 i( p' v$ M0 W8 S5 V接下来扩展PROM位0x10个: E4 y* R' o: Z( p5 j) P# S
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
* Z% b8 o8 U- S
- ?: h) w8 L" h. G( ^由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:% W2 E% Y5 Z; B4 [+ h3 J: e7 J
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
4 e: s( l; a, E$ {* s于是可以得到双截龙2的是:
" z9 @! n3 f) K! b& e(8-1)x 0x4000 + 0x10 = 1C010。; ]! Q$ a1 _+ S8 f X
然后跳转到1C010:
- o( U2 I) }+ J: O
* V; T' P7 j# `# @1 S1 R, r5 K ) \8 c" R2 z7 Y5 J A

" a6 \3 \8 G( Y$ t; O$ O: ^( @然后插入0x20000字节的FF:+ c: X0 q+ U# j( Q: b
% z0 z6 V" R+ {# \
/ r" W: d! k& X7 T j0 y F U

( w5 l9 G5 D M% z然后保存:( j" ?( t8 N6 D. N: E9 ?# V

3 o+ n* S, a/ x( C 7 X+ i, v. t1 l1 m. X
用FCEUX打开正常运行:
! J" r6 |! T; j$ K4 E! X! s: o 5 R0 E" I' N7 E& d; A8 b
查看文件信息:
& G% C5 x; v4 c3 n& g
( T$ z0 @$ y7 n7 `! ~4 Z8 w' A
1 V% C# L+ k# n" b% p2 b0 F$ q1 _5 X, `& ?接下来切页:
0 b; Q+ _2 h# c$ v7 q: k先打开十六进制编辑器:
. U" K8 u# x: C. v* h& L' o* F
0 H3 M9 l6 W+ g! a+ r拉到滑块最后,看看重启中断+ r, S$ U- K2 B* F2 f' h5 \1 Z2 `
中断地址 中断 优先权
' A8 w+ i5 O0 W, R1 f* ^( [: `. ^$FFFA NMI 中
/ K8 F' `! S6 I! x; E6 x- ]$FFFC RESET 高 : K+ E/ z7 n" a9 r$ r
$FFFE IRQ/BRK 低 " `7 r- ]$ b* N

+ L, R3 S% n, dRESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
; R9 B7 U+ v! [ ?5 w) k( C( ] C$ q0 [由此可知双截龙2的RESET中断$FF65。, F f' a% b- p# W# Q$ V$ k& s
接下来添加$FF65的执行断点:# ^* w- r& r H) y, f5 z$ R' ~( g
打开调试器:4 b* B y2 ]7 }7 q
* b0 R. ~% x$ a+ {
添加$FF65的执行断点:
& f. u3 N( e( E- A1 {2 L5 z& y # @) I$ S8 w/ G+ x

; s3 X9 X! i5 o+ v( k0 O' Q. Y4 F! R9 V
单击确定:
4 p, ^/ W1 D, F: D% Q, W, G" {( M+ ~
) n8 r7 T: j( N0 Z+ U {! d( Z3 O8 B; o J: E! d1 e1 D
然后重启ROM:5 d# j& T* q n* T. w7 H
6 a: i# ?% z- G' F1 O; B- t) T
调试器此时弹出来:0 |. P/ t9 g O( k
7 ^ A K5 X2 E7 I9 L: o3 @& E( f
然后打开Hxd,写一段mapper 4的切换bank程序:1 M: @. s8 S# E2 a/ ~0 m+ _9 R
先看看mapper 4的说明文档:6 [* b7 `- O* K0 C
Mapper 45 Z/ M9 |" f4 z, O; l7 Q
! d, z0 d! A8 v* B5 K
$8000: 模式号
) L. ~; Y& u& {$ V; v 位D0-D2:' {$ U0 i. {( u! g
0:选择2KB的VROM存储体映射到PPU的$0000
' [$ U. w$ d6 _# u 1:选择2KB的VROM存储体映射到PPU的$0800
4 K( {& X/ f1 O, i% K 2:选择1KB的VROM存储体映射到PPU的$1000: f7 z9 U: `3 Q+ l' R
3:选择1KB的VROM存储体映射到PPU的$1400
% H3 f4 L F4 a) N; L 4:选择1KB的VROM存储体映射到PPU的$1800/ S1 ` u- M0 s- s; Y3 U
5:选择1KB的VROM存储体映射到PPU的$1C004 C4 q6 p+ O7 G; G- H, H( v
6:选择8KB的ROM存储体映射到$80008 Q8 }" p0 W$ A K
7:选择8KB的ROM存储体映射到$A000
% ^# m( a+ }) K 位D6:4 ^( C# F% M& R K8 j% j y3 j
0:允许擦写$8000和$A000
7 u, n: T& w0 J$ ? ^3 P 1:允许擦写$A000和$C0008 b2 Z& m. p& Z8 M J
位D7:
) F! r. \7 D. h' y% K 0:模式号D0-D2使用普通地址
2 f* Q8 r# Q0 B 1:模式号D0-D2地址异或$10009 \1 S% c D9 r( _
1 E) H# R: d0 ~' r. v7 i: E
$8001: 模式页面号, o8 w1 B1 Y) X) j3 g) ~/ A
写入一个数(00-07),切换存储体到对应地址
9 r( }5 Z8 }" m0 `1 p, b3 d
1 [. O) r6 W. L$A000: 镜像选择
# y7 e7 T7 K& G# s0 R. g" g 0:垂直镜像7 G# U& e5 h! T4 e
1:水平镜像
# V s# G. Z3 I& u" Y! l- k. {7 s+ M1 c
$A001: SaveRAM 切换" I7 ~4 }6 e) x
0:禁用$6000-$7FFF, Y+ r! J+ L# m. K( y( A! W# z3 D
1:启用$6000-$7FFF! z7 Y& T/ ]8 L. H
0 h* g5 q7 |. n% \5 }5 s- F R$C000: IRQ计数器
3 l) b6 F+ v# p5 u# K u IRQ计数器的值存储在此处: N) N0 M9 U- F1 b# X. z
. `4 {. V! M& u- P0 [7 @- p# i/ \
$C001: IRQ暂存器0 M$ i3 G- \5 R* k# X
IRQ暂存器的值存储在此处+ z I) A" I3 f! \5 Z4 Y1 L
9 s8 n; @( ?' S# h
$E000: IRQ控制计数器0. g) O) _0 G* A9 J
向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ% q; u1 Y. Q. V
+ j. v% c2 E2 B6 e+ _: G5 y
$E001: IRQ控制计数器1
. H) @; {. U0 p" y* W* |# @ 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)2 d3 f' D2 S7 p- e( E4 Z. {
- H. U; h% w- i! I
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:
# I2 d" H4 F7 h3 {2 Q3 y48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68! S3 q8 d8 e% U% h: O( x
PHA 累加器A入栈
& Z; z' Q8 s- `LDA #$06 设置切bank地址为$8000-$9FFF, Z/ R' `2 W( x, X8 Z8 L
STA $80009 `- W, J+ o# u Z$ ~4 u' c
LDA #$0E 将第0x0E号bank切到$8000-$9FFF2 u1 U5 t: v. U3 m
STA $8001
( y% t" z" l- V8 C1 AJSR $8000 跳转到子程序$80002 t& E( ?9 ]" A& T5 e% b+ d
PLA 累加器A出栈 @( t% R: R G; _1 k2 J8 z
7 j/ s# M* _2 @4 ?9 r4 @
为何切换的bank号是0x0E呢?
$ K: g9 l+ f+ `. L因为在扩容ROM后,文件PROM结构为:
2 P! s1 g- d' ~) S原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
0 X/ v0 v/ v, b8 n, g* I$ a) s, N2 |7 y; h
5 i: W$ C0 `' i! b" x2 J0 d9 OMapper 4切bank一次是切8KB,那么文件结构就是:. S3 ]9 |, N) \
原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F); X, D: s/ Y# }9 |5 p2 G/ n9 n
因此选择扩容的第一个空白bank就是0x0E号bank。
; I: O6 f6 H: \ 2 M% T0 G5 t% C1 K
; H$ m7 {+ s' ^2 o |) G
然后去调试器找RESET中断中可以放下切页程序的地方:
J# s/ |( z9 Z6 l首先长度要小于等于自己写的切页程序。2 R8 ?5 h/ Q/ N% L
可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)8 X0 e, a! t5 z2 H K4 L
! O4 b! n0 d1 g2 {' T9 e# `
然后跳转到$FF7C:
, O" W7 ~( b/ a9 `2 U& v3 J$ c, L" t 7 a0 G B7 M' q* O
4 d3 X! a* u+ _5 l! o; J
" D3 v2 g- y& u
! G8 A7 \& U- v5 N4 E: w
复制下可以被替换用于写切页的程序:
$ c: q9 S* q) l6 R; J先选中,再复制:6 w. t1 U9 s6 f8 K$ M
- c* U5 C# ]2 b4 f* `5 `5 P3 s

5 m1 Y$ @$ A& | T- l% {在Hxd中新建一个文件,把复制的数据粘贴上去:
/ r, U% y6 k% I- q& I( N
" Y7 b, E* q- D# w 5 p" E! d2 K6 {0 u' T1 h

! t2 H9 Y! ]* W `3 B1 {" w0 A. u3 b. j9 k9 V% ]
然后回到十六进制模拟器:
* s# f i( i$ y) r$ _( E转到$FF7C对应的ROM地址:
! `5 Z0 h6 x1 J t$ \' g- T 6 w/ d! Q; ~) o4 x+ q% |

' n2 v0 c5 H, p* [9 U% [- n, `然后复制Hxd的切页程序,粘贴到这里:
# z; I) q! C/ t0 l4 P' i0 c
( F1 G$ _6 z# {6 K2 {0 F# G
" ^% P% W2 b- j9 e
$ M! n( }/ L2 L( D没有覆盖的用EA覆盖:
+ {8 X1 x$ R' [. [
# T/ M% [7 G; ?6 l1 I打开调试器可以看到变化:
! z1 g* O7 t: D% C
5 W, E, L y; H% h$ |+ K- Y然后添加$FF7C执行断点:
0 |5 g6 m# |$ s! D& x! U
: E" @; M! D( W6 @ # u2 t1 r4 k" N

9 D: _& @2 B4 M5 r单击运行:+ g) ~, \* B, C" p+ P$ D, Z% O
然后程序在$FF7C这里停下来了。
8 H6 A% Q8 B5 w/ c- a+ u u8 j# P. K7 {% M' I) }
然后单击单步进入慢慢跟踪,直到跳转到$8000:
4 @8 g- G% Q; k! s ! j/ Z2 L3 A; D! n2 ~
然后打开6502_Simulator:
; l2 B$ V$ m* N4 |9 p
$ Z9 K G# z' S. g3 N5 [再打开我写的数据搬移程序:% F9 g9 F( p! R
% s: e( E6 l3 _& q6 l

3 W; H$ G2 |. |7 H- }然后修改对应的数据:5 X5 C& I1 {4 t* l
程序开始地址:修改比如$8100就修改为 .ORG $8100! k) X2 G8 ^4 H a
复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70% r& B o( y4 ^ C2 t: Z2 r& }
从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$827 x) b" R% I$ Q8 k( ~# ]9 d
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91* S9 }6 J6 D3 }0 @! k
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。( T1 i7 b5 U, u- ]3 n, z6 Z6 q& T
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。4 x$ t! q4 ]# ]3 o0 b
中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。
/ P) t( \& d3 l7 T+ v$ S后面的不用管。; L, a$ a1 f- ~0 w- d. ]8 G
& N$ z; J3 W/ l0 [
设置完数据后单击编译:
4 A" l1 C0 ~3 U' }* E 1 B4 X& C& [5 V5 P
然后保存编译文件:
; |9 r; G+ C) A& x
; K- r5 T6 [; n2 ^# r! F4 Q/ d3 g选择二进制方式保存:
3 ^: X+ L( x8 Z; W6 U# o5 ?+ L8 M+ ]+ l
3 Q& }% ^- m& \2 O
/ q+ F; n6 G0 T; w7 `8 y. A$ e用Hxd打开保存的二进制文件:2 b1 y: e7 s& A% [1 c

- l" o. c$ J3 T
6 ^/ M$ d9 }. l; ?% K8 C跳转到设置的程序开始$8100:
4 B, U& k o, \1 {" t# b0 c
/ k8 {4 [; g. R) C2 ]( k
% \+ f8 i3 c. ]8 h5 a1 g; z6 i " n2 l( e% `; l% ^
回到FCEUX,转到NES内存的$8100对应的ROM地址:$ Y( V! h& w6 o
6 @9 i! h' e6 o P1 |3 H* q& W

4 x. x" Q, ^$ z+ p/ A5 T 5 `8 P% e% c9 y' L( T- B3 B" N

& B M8 M" o: R \* S x
. J" s) p" E6 o' c9 Y4 ~然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
7 K N+ a0 d" |) u( Z9 {) ?
$ ~0 N) ]: r' @2 S# @6 _8 C( ?$ M 6 @4 C2 }" T' _6 M- \* m
' H: I$ Z0 l9 W! T' ~) w
然后转到NES内存的$8000对应的ROM地址:+ J4 P. F* Y' I0 t
# \; u2 q$ j* ?% D; p; O
/ [& Z3 i$ b E! q

% J# T$ E0 J# i! i
, \7 T% {% G8 i& {. t$ V写上如下程序:5 k6 x) C( q$ @: N! Z3 H
A9 80 8D 01 A0 20 00 81
9 K3 n1 l. R, R( K3 \2 d7 oLDA #$80
2 y; c! f# {; WSTA $A001 可写方式开启SRAM. h5 V1 o2 ?; a, Y6 m7 ?
JSR $8100 跳转到子程序$8100
\9 \1 V% G' K& S9 e3 s' ^5 Y$ i. S
: o. G6 o# T: y5 N* ]* C! i然后把Hxd里被覆盖的程序复制过来粘贴在后面:
2 W# l& m/ p x( O) c. N! J
: x* Y: |/ c% Y6 H1 b, w9 o
/ _. d7 m" E D6 o 4 X# B8 [: R q: B, |1 J# Z
末尾补上一个0x60:
# |; V2 r7 j0 t) L" E" b* `RTS 子程序返回
. k+ A* |0 E8 v2 @3 f- ~ * w b% E2 R' Y& W
然后单击运行,ROM音乐响起,正常运行:* o& j; ~: M" z) V! Y* k

, i1 m! e& m* T$ i1 x/ x
( F/ `7 \- G* r9 u) k6 |然后转到NES地址$7000:# u6 n2 O% z# D3 T
) Z7 Y! R( B$ G e3 @( v
9 ^" B* n/ A2 c3 H

T. q l! k( n# l& F v7 O ( f/ K! [- j% b" z9 @

3 ?0 Y% `8 w0 f0 G* P' W可以看到,$7000-7FFF都被复制了一片数据。, I) }0 A7 Y) }
测试没有问题,然后保存文件:
8 S# {7 h; h+ v: P% a) m
. @- y; b: k0 o以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。
0 ^, P% N2 I; t* `; i, T8 ?后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。; a5 Y. y& d" S, T% X) | l
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|