|
|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑
* m2 v! \ b3 l2 S2 E0 H j$ |: W7 [5 s! D! k+ \
[FC][SRAM扩容教程(Mapper 4为例)]3 f q# R: u+ ?
& L$ H4 H' R7 x时间:2017.4.28; h' S4 x# `1 d( h0 B/ A
作者:FlameCyclone$ B% F+ E& z. B/ W* y
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
4 L: c! x+ d& Z9 u# O( cROM:双截龙2(J).nes
1 i/ q5 n. i6 F' H/ p+ V适用:没有使用SRAM的ROM+ \7 x! \7 W* K, H$ t
9 \+ K9 E, l+ D" r
首先用Hxd打开ROM:
, E5 N* h9 c& ^# P7 J ; k( |: Z! r- v- Z8 p
然后扩容:
7 s" v: n) p' S& R, C J& c# r) l$ \7 ]
5 ^; }8 [+ P: x& }. X; o, \
3 F6 g" o% v2 R' B% c" {8 S& D4 i! `; z+ R1 M
6 p' M! J- @4 P- G" y' w% I. B1 T: L r( B
& {. X" B/ v) ^6 r4 z* j/ e/ d
, v9 O" p1 L3 D4 M: t: j5 @
- Y. n2 d& i( x6 h' n* X先看看任天堂产品系统文件对NES文件的说明:
+ o6 F, Y" u5 B2 j: `/ `4 E) nNES文件格式
8 A7 q9 d& b3 A) R.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 5 k8 d7 U1 I- b5 X, `# I
偏移 字节数 内容
$ _& v8 s: J) N8 d+ t) u0-3 4 字符串“NES^Z”用来识别.NES文件 ) Q- d, G M- i2 r4 {$ e
4 1 16kB ROM的数目 8 c q# K Y) ^
5 1 8kB VROM的数目 5 D7 W4 s- k. y
6 1 D0:1=垂直镜像,0=水平镜像
' ] u0 ?6 r' g- Q D1:1=有电池记忆,SRAM地址$6000-$7FFF 5 \4 K; |6 S: U
D2:1=在$7000-$71FF有一个512字节的trainer " D2 q6 n( c* m$ o. F$ x4 {
D3:1=4屏幕VRAM布局
" o1 T8 t V+ _3 H& ~ D4-D7:ROM Mapper的低4位 ( z$ I" n, O9 Q1 |4 {
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) - n0 ^3 T, Y8 w5 ^
D4-D7:ROM Mapper的高4位 6 ?% F8 `2 `* x8 r; |6 d
8-F 8 保留,必须是0
3 ` T1 N4 L5 X7 |: O16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前 9 g5 t2 ~) M- v3 Q" f; {8 q
-EOF 8KxN VROM段, 升序排列 ; ]/ L! ?* W' C8 l, v
8 E" z, @9 m( w. n9 x& s然后知道这个ROM有0x08个PROM和0x10个VROM5 m# E+ M Q# a2 h- Y
接下来扩展PROM位0x10个:
. y0 S* i, P8 @" m) @3 e" s: s V先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
2 ^* G# Z. n% V$ R- d
3 |1 s$ a1 \# A/ o& s3 X由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:3 k9 h+ w2 u+ h$ [
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。& J) H8 _: A+ {. z2 ]. z! q9 w! c9 Q
于是可以得到双截龙2的是:
: I0 E& A- b4 M: G5 W2 p# p5 B(8-1)x 0x4000 + 0x10 = 1C010。& r. I3 t7 T& d" v6 k! u6 P
然后跳转到1C010:
$ [& `7 O5 d& q& j$ {5 x h/ i ) R4 c, \( w9 k- _0 T/ P

! v; |& ^9 O# D9 }" \
, R. x7 S; a; x. T: a! R然后插入0x20000字节的FF:
) x- E1 J# [ l7 J& X
: f8 m) v; L* y( ^5 D; _
4 V! p1 g) D: ~6 `8 u0 y8 g. R 0 X+ E$ Q* k, M% @8 h4 [0 S% B
然后保存:
( ^4 L( ]8 T; q2 z' t
0 ?) n; @2 p, F1 d3 v 8 M6 O- O- U* q
用FCEUX打开正常运行:
7 y$ R- r1 y* V$ Q7 s7 E : |$ X8 T" F; W3 ]0 J v) h
查看文件信息:
: P9 R9 x; f5 ^# { o; M/ N & E8 F: `6 t7 }) J; l- L/ y

! k7 l* ]0 l" b d* O Y! J+ w接下来切页:
$ c6 S7 O7 a+ ]' W先打开十六进制编辑器:
3 O$ Y4 r& v" s" ?7 D
- | j* M. K+ m: t. |拉到滑块最后,看看重启中断
. T( j3 G2 u7 c/ {8 l% `# T" X8 k中断地址 中断 优先权
9 ?$ ?% J5 u/ F9 K; M$ a- `3 c$FFFA NMI 中 * f P( B; |% S9 N" `
$FFFC RESET 高
@0 g, c9 S* F. V- h; e" [) h$FFFE IRQ/BRK 低
f/ h. f" Y* b# P4 ?6 K
( {4 @) l# _! XRESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
7 \7 U3 j6 A6 Y. [! }; I由此可知双截龙2的RESET中断$FF65。' n! p) E+ E1 c5 y7 m( N- |: D8 ?
接下来添加$FF65的执行断点:
, n, w' y& P7 P2 V1 j0 t4 }9 T打开调试器:
4 Z) b, U, L6 Z* ?9 z) u9 } % `, T$ t$ P% B, a9 K* ~# g
添加$FF65的执行断点:" k) y; X0 J; R1 t

. L" N, ?7 e }% l4 ~7 {' c
2 M! b8 {- Y ~" l; ]9 T
8 b8 Z+ m j$ e6 N单击确定:' z3 R$ W) |4 Z2 m! q: x6 t- n

4 U( e8 w! n2 D2 H5 ^0 e" S- S/ {; A9 P% `. r9 U
然后重启ROM:
# m8 t* u* J6 W, `& c( G( N! f* d 0 ~) V- O) q2 I
调试器此时弹出来:# d% w( U5 k/ M# y1 ~
0 _% A3 T' Y% v! J% O9 a4 J3 C, g' b
然后打开Hxd,写一段mapper 4的切换bank程序:+ ^. v4 D# [8 m% i9 u9 O
先看看mapper 4的说明文档:& I; L# @: `7 U
Mapper 4
9 X( e' n, L9 J* W8 }' p* |
; p( Q! S1 V! v; ?& ^$8000: 模式号
' v3 ], H: Y; H, ]4 q 位D0-D2:8 x4 _+ Q9 x. I
0:选择2KB的VROM存储体映射到PPU的$0000* w! z/ Z0 }% c n' z! G. ~. X
1:选择2KB的VROM存储体映射到PPU的$0800& n8 [8 f0 E3 B3 U* a5 F- g
2:选择1KB的VROM存储体映射到PPU的$10002 p7 V$ n3 I6 N# C, N V; v4 Q
3:选择1KB的VROM存储体映射到PPU的$1400
0 N& o9 Y; \7 F6 N+ y. d. j 4:选择1KB的VROM存储体映射到PPU的$18007 O* q9 [2 B& D! K$ M2 {
5:选择1KB的VROM存储体映射到PPU的$1C009 |1 w: ]5 X% Y
6:选择8KB的ROM存储体映射到$8000+ D+ \; k- q8 Q4 Q7 S. O* o" f2 @
7:选择8KB的ROM存储体映射到$A000
0 I" T% Y* e5 a* m* T+ R4 s& f 位D6:( e6 w; p3 H5 u9 J5 r F4 V
0:允许擦写$8000和$A0002 J E$ Y, R! K; q+ R4 M5 U# }- G
1:允许擦写$A000和$C000
& d# P% R! Y$ }+ N* Z( f/ N! p5 r 位D7:' c: J0 B/ _0 d$ o
0:模式号D0-D2使用普通地址
) e# x: A& b) M2 ? 1:模式号D0-D2地址异或$1000* j* Y3 @2 y/ E
& c8 ^" V9 \! g
$8001: 模式页面号
0 i0 {& f" P6 } 写入一个数(00-07),切换存储体到对应地址" ]* [. |, e5 I/ w8 V- Q: w
4 j$ w/ m, e. z/ p; x
$A000: 镜像选择, v& u) e$ a% R$ [; m0 D7 }, a
0:垂直镜像4 r4 V9 J0 G8 L( B6 a2 F
1:水平镜像( V) W0 H) y; ~' l4 | p
x" r: }$ Y. d% }+ r( }
$A001: SaveRAM 切换& K, K2 d( a( x4 ^2 ?: S' P
0:禁用$6000-$7FFF
( g/ X+ }" J- M# @% |0 X; d 1:启用$6000-$7FFF9 Z, d" J. k1 Z) i P. e
I2 _3 _2 V2 v; p, U
$C000: IRQ计数器0 | `' Q1 s( `& {- D0 K5 R, `
IRQ计数器的值存储在此处
( \9 e$ j6 `/ g9 l8 C8 S9 Y( h6 t
0 |0 @0 k& @/ V: T6 M$C001: IRQ暂存器8 S- [$ m. I) U# ~% T4 [3 S7 f
IRQ暂存器的值存储在此处
+ H" J0 |* ]4 j8 a! Q0 O: r7 B+ R
' ^9 J2 \- `' W! p: |! O$E000: IRQ控制计数器0
( x. V9 ~/ [! Q' q' J 向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
% v* s; W+ K" Y
3 P \+ A* F9 _$E001: IRQ控制计数器1- A9 R! H. W3 G0 V3 Q* c+ M
向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)- t6 U9 g8 i. ?* j
U" @: ]8 |7 V4 A: A' ^
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:
& n3 i( w+ y( p* a2 Y48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68! V" J7 @ U$ ~$ g, V0 E
PHA 累加器A入栈: [+ T, @; t+ h: F# c4 [
LDA #$06 设置切bank地址为$8000-$9FFF% ^9 n( l5 l* c# s8 n- ^
STA $8000
4 A0 O G1 F* m2 n3 c4 p GLDA #$0E 将第0x0E号bank切到$8000-$9FFF
5 M2 _+ @$ K; {0 bSTA $8001: y( }. Z, j2 `, p- A
JSR $8000 跳转到子程序$8000
0 P, l! w. j& K' N4 c3 _+ f8 g$ xPLA 累加器A出栈
# f3 u2 j; ?" I6 K
9 N- Q1 h4 f P9 H$ u9 ~7 o为何切换的bank号是0x0E呢?7 Z9 k4 W$ D! z! @
因为在扩容ROM后,文件PROM结构为:$ ~( x# `0 f) T: z
原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
0 {* i( S+ o% D2 R* g# [$ {1 S* l, |- m+ C2 h# b' K5 _& M: |
Mapper 4切bank一次是切8KB,那么文件结构就是:& ^3 N; a8 {& w- B/ t% `
原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)5 Z1 W& O9 x+ n4 [3 ]9 \5 j; [
因此选择扩容的第一个空白bank就是0x0E号bank。
' X( w' z- }1 [6 e & L" G4 s( p. ~
" w4 Z5 A8 w, D! g8 w: v然后去调试器找RESET中断中可以放下切页程序的地方:' r9 Z5 @3 n1 X6 B; `
首先长度要小于等于自己写的切页程序。, X8 ^0 z# W2 D1 f7 s. b8 B
可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
" [' J6 k8 [) G8 n& Z & y7 u: I4 U8 k5 ?. v
然后跳转到$FF7C:
* h3 E) j/ y1 u+ t 0 ^/ f) G/ u' h7 P
; M: ^* @2 u, K) k1 M( \

$ _# i7 Z9 |! @$ g8 L% J
~! C/ x- \' y复制下可以被替换用于写切页的程序:
) T/ d- n* C% I, J" S" r先选中,再复制:
\! t; ]/ ]: v9 h7 j
7 [' i: K- e# O
, q, t+ d- g7 ?8 o/ _# ]在Hxd中新建一个文件,把复制的数据粘贴上去:
$ X) v$ J$ V0 K7 s; v- G- I 2 n0 S+ E5 b: \

7 d% ]* H; [7 _$ B* g, Q/ |/ Q' t
5 U! h$ j5 u. ]* Z' c* i: t+ [% O% W% U1 b
然后回到十六进制模拟器:, U4 s5 C- V, ^1 j2 g
转到$FF7C对应的ROM地址:: [8 a" I1 c, S6 ~4 K# R( O c
, i% c5 i' m( q; X# F

5 X# p6 I* |8 ~! e3 w# D9 x然后复制Hxd的切页程序,粘贴到这里:, h, E) _' F( ~/ j
" O7 j8 ?- F$ d: M3 T+ f1 m1 F0 ~: e

& M5 t* k7 ~, t' J
8 L( z/ q' c/ h* _9 n1 i3 z) y没有覆盖的用EA覆盖:
, G+ F+ X( S/ T5 q+ b 5 v, A U1 l3 Q2 j4 S4 l; h
打开调试器可以看到变化:/ j0 ?. _& Z, ?3 w5 G7 b

5 @ Z3 A2 x7 W1 |然后添加$FF7C执行断点:
; }3 J! Q0 r0 l3 F/ c* h3 S+ a
4 G& E+ F2 N3 w7 f I - R. i) p5 W/ c1 G
# [7 J$ j0 u7 y0 @0 f' b5 m
单击运行:1 f) J; i" _& P/ y
然后程序在$FF7C这里停下来了。
! g0 h; ], P4 D+ p
& @ J* [# z+ F7 d2 A然后单击单步进入慢慢跟踪,直到跳转到$8000:3 ~# e$ {0 X8 a6 X. k$ u
/ s& u4 d, D4 G% U
然后打开6502_Simulator:
, E2 a7 Y: t/ u0 Y9 s) I7 P8 [ $ i* K: N# f8 U$ I8 M
再打开我写的数据搬移程序:9 O1 |" A! f) W. E! Q: O

6 _- H# ?! g) D( p6 c
( u W; V3 \( _; y然后修改对应的数据:3 a& o" A7 U' ] Z
程序开始地址:修改比如$8100就修改为 .ORG $8100
1 q0 V* _' \3 ]' m. D复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70! Z4 Q+ B7 Z5 [# H- Q
从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82% V+ S' i' ~1 S8 _6 `, i0 d
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91
0 i' B0 c0 |# x也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。* a1 v( n! A$ X; D4 d
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。
+ H+ ^, H- V( R5 ]中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。
3 ~& T- G; A7 S2 F' G后面的不用管。
3 ^, x! |* J- @2 ?/ E2 P( a
5 z" [6 y" u5 E& w设置完数据后单击编译:1 Y7 d, i4 W, o# Z- [

2 k1 C# K- h8 u. ]7 R然后保存编译文件:
. b5 w7 ^- `5 E
. O! |$ K" z! b选择二进制方式保存:0 O5 S. v5 M& a: j, ~8 E- h
' k6 y) O0 f; [; O

& f, h; K7 X8 _: T用Hxd打开保存的二进制文件:2 L9 O L4 C5 h: E2 H) Q+ \

U5 U: _' W" c4 q E8 n- U3 N: m1 V
跳转到设置的程序开始$8100:- _, q7 q8 P* x

6 Q8 C9 o: m9 F3 p! c m9 f$ e; T% ?
6 D- S7 x0 K' C6 S+ X
/ e* d) @: D( @8 R' T: G9 c回到FCEUX,转到NES内存的$8100对应的ROM地址:
5 p5 U$ X; B l$ _8 I; {. D7 T, d
* p& w4 E X6 O& x U3 Y% A2 F& ] 7 t. Q6 x& l x' q

) R& b- V" K: P1 g8 Z6 \
0 T2 o, v0 Z, L7 P% Z ) ]3 D4 P( m; x) q) J% R
然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
6 |5 t2 K$ c9 l: f x3 p
1 ]7 I- V7 {+ W! K
6 J$ [! m# x1 D# h% H9 F2 d 4 L( |/ L* h- _; h/ o
然后转到NES内存的$8000对应的ROM地址:
' }$ W7 F4 a, q5 `+ g ~2 ~5 J
: K I5 f; @( P0 W9 _; w; G7 G% W
$ T) F3 Q) L" w& N! ~
/ x# M, C" }5 H; w f! X
& {: L- \: @4 z7 d4 f, o; c$ R写上如下程序:
- k) J% E/ n# |2 rA9 80 8D 01 A0 20 00 81 3 N# U/ q9 ^/ T: l; L+ F1 {/ i; K3 W
LDA #$80' F7 H8 Q# p/ p3 T2 `0 ?# y
STA $A001 可写方式开启SRAM% E. L. Q, w7 ^; o$ f
JSR $8100 跳转到子程序$81007 t3 ^, `0 m+ g, f: Z+ c

2 M& S5 G; M( k9 {然后把Hxd里被覆盖的程序复制过来粘贴在后面:& \- `: o7 P. N
% }3 D% R* h' o/ Y

2 P8 w' T& t6 H0 J' A 7 {; G7 f# Q3 h' o
末尾补上一个0x60:
8 m- y1 g3 o$ K" U5 C8 y3 W8 ~RTS 子程序返回" o% D, w/ s' `8 Y) M5 N1 U

. |0 p3 K( L% a/ s然后单击运行,ROM音乐响起,正常运行:
& C; ^7 m0 g6 Z7 _9 U, t
- i. d8 e- @ Q+ D \2 f$ I+ h : P1 o% q8 c. w7 `
然后转到NES地址$7000:& N* R( s3 I/ s

! _7 h2 A% r& s( K7 y( }) s
2 h5 F7 o7 }) u
! o* u4 ^7 o+ e! _+ x$ w/ y' q; q1 U
$ m* F1 n) |' L% b0 E! z# T" i - S8 X2 C& W9 _% j9 ?& v$ }
可以看到,$7000-7FFF都被复制了一片数据。& e" ?0 `7 C* j! S0 C* v% C
测试没有问题,然后保存文件: K6 c- q. u/ I+ o1 [5 E2 x8 j' x6 X

% n0 Y3 j R9 }, e9 N6 H H4 X以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。
& _) j- ] q* L$ s) [后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。$ ^5 h: `8 o# c% K/ c3 j" W
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|