|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 % G8 y; V" q" s# }- }8 H5 c
/ l7 V8 u: p ]% c4 K9 M[FC][SRAM扩容教程(Mapper 4为例)]' I5 o5 Y5 N5 Z) W& u7 Q( A
8 ~0 A0 P e+ W+ P; E- q% t
时间:2017.4.286 `5 X4 \; @1 q
作者:FlameCyclone
( k6 Z* U, r- r" v) z' i. t工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator: s, h5 m: ?% J. ]& P
ROM:双截龙2(J).nes
+ G( z# w& x& O: c& N# G' o( d适用:没有使用SRAM的ROM9 H4 |2 E# n* d/ a# Q7 A8 L) Y
/ `, a( x+ H0 G3 c
首先用Hxd打开ROM:0 Q' u0 Y. [8 t
) l6 w) ~: Y& g5 h8 F7 P
然后扩容:
/ O! Q. ~& ^ w) Z2 O3 E* r9 s. I" ?! l: c4 R& d- u
: p( ], n4 H. y B5 z1 s W2 M$ O9 l! w& k7 h- L6 C
# `2 u. {2 K# R1 k$ D9 d3 i f/ _/ H/ D
9 P, ?8 w& c' d. ^& s1 M' M4 ~+ @$ i& P% d# N
7 ^$ [& @/ N9 @- Z
) _- u5 E/ I2 a' U1 n( ^6 ]& D
先看看任天堂产品系统文件对NES文件的说明:
9 J6 b& r; `: yNES文件格式
% C% v/ i, h* [0 _3 t: H7 `$ L6 O.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 8 G7 U4 U$ ]* o, r
偏移 字节数 内容 b' T3 R7 p" j. z7 k
0-3 4 字符串“NES^Z”用来识别.NES文件 " K. a. W: V# }/ V7 [4 i
4 1 16kB ROM的数目
9 P" P* [! P( w( X2 T+ a1 |2 @( q5 1 8kB VROM的数目
x0 z1 H* D' n: k! F5 |6 1 D0:1=垂直镜像,0=水平镜像
5 `# e2 J, E9 o3 @+ c D1:1=有电池记忆,SRAM地址$6000-$7FFF 5 K" e" ]$ Y4 R- t
D2:1=在$7000-$71FF有一个512字节的trainer , Z8 R, z6 o/ f2 W+ n
D3:1=4屏幕VRAM布局
% F: y7 }+ i: I: `! A6 |% I( k D4-D7:ROM Mapper的低4位
: X$ O* \. ^- U7 |3 T& I7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^)
2 n* Z% ?% t/ D; U7 ? D4-D7:ROM Mapper的高4位
8 M3 U( q7 \. b; d7 q8-F 8 保留,必须是0 s$ z" V3 v# x0 B4 a' E' w
16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前 ! l4 C. Q* X! G! F4 M
-EOF 8KxN VROM段, 升序排列
5 ?: z$ q k& m+ v8 Q8 e0 v
! @- J4 C' E. _. D然后知道这个ROM有0x08个PROM和0x10个VROM% Q7 n2 H' i! U
接下来扩展PROM位0x10个:9 e) ~5 k" J' Z2 |- j4 a+ r9 {5 U
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):" ~5 J. z! m- j' q4 t1 u1 u

% o* G# K/ W4 r1 D由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:; q5 U, e' A7 B+ W; J
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。5 p5 j" c6 K. A6 k, b% I; l
于是可以得到双截龙2的是:5 V- h$ V: z$ l
(8-1)x 0x4000 + 0x10 = 1C010。+ O5 W5 X+ i+ f0 i6 w5 ~
然后跳转到1C010:; o6 Z' C+ j( O- H6 J2 \

: C8 n5 t# w2 ^: l4 |3 v4 b 4 C/ v2 J2 P) X3 w* R

% d8 s J- `( F4 w9 P然后插入0x20000字节的FF:
) }% j' H7 v* @5 \2 B @
* }1 Q/ b5 Q$ L, k1 R! q
5 N) u! C( H% g) D/ s5 ?8 W2 M+ _
* I, a( c2 p! C, B9 |8 P然后保存:" p+ l' ~8 \" B5 \. c2 H: ^

3 A, C; ?5 D; E" J# F0 l. Y
, A7 B; {" a! O) k$ f: E用FCEUX打开正常运行:4 b5 C: L. m! K0 n& S( o4 j

/ Z/ ~3 @* \6 T" r查看文件信息:" @% F; E! E1 l; q1 t
' t$ C, C, X' r8 M
) h$ \6 H$ O' e5 V& ?; i
接下来切页:
X% @; t& |# t. z先打开十六进制编辑器:
2 ~* `; X( O/ b8 q- v' y; ? 2 U M5 O" k9 @
拉到滑块最后,看看重启中断
* U& k* {, g( F* {中断地址 中断 优先权 / X+ |: W- V7 j( T
$FFFA NMI 中 7 Y' s" M/ [$ y1 B, K- G$ R
$FFFC RESET 高 # X3 R7 G& N1 f' l
$FFFE IRQ/BRK 低 0 H& p, `" x) U% {; T' _
2 y" ~2 E7 }+ t. V# y
RESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
# k! T+ |0 H5 q7 W& f7 R由此可知双截龙2的RESET中断$FF65。0 E1 u: x1 M! r( t
接下来添加$FF65的执行断点:" s# |6 S8 y7 C0 a4 ^
打开调试器:8 r8 Y: O9 E& T7 N5 u; k3 n

% H1 D' l6 {8 r添加$FF65的执行断点:/ E2 W' m( d) S" Z+ H
7 ]7 k l" g2 h0 D

: C7 _1 r) F5 U, U! m+ D1 I/ W% {" E& U
单击确定: V& [# v* h) ^% f0 p) y: v
: i/ V- b7 ]& v' s: U( _8 l
2 O' q& v# b: V" G4 B然后重启ROM:
6 q$ \3 L: L3 `. S- V5 p
3 q8 V$ L# {4 o4 ~- O调试器此时弹出来:
3 n" a/ R7 T- I! z+ K7 c ! a9 x8 t* t. O" G" ^. D- s
然后打开Hxd,写一段mapper 4的切换bank程序:9 Y# i) R7 s2 Y- q2 v, N i) g
先看看mapper 4的说明文档:" b9 O. @/ C2 _) T7 a$ u
Mapper 4; [3 m* ]/ A* n* F
; ?# z* I4 B% j& w: e2 Y4 S7 G% o
$8000: 模式号
k2 T7 T, Q/ {) A 位D0-D2:4 A% _4 m* n* z5 I9 p+ E' ]
0:选择2KB的VROM存储体映射到PPU的$0000
9 P: F- W! o$ G# j 1:选择2KB的VROM存储体映射到PPU的$0800
& w# w5 m! W( C% |2 @ 2:选择1KB的VROM存储体映射到PPU的$1000
' e6 a+ f1 G! X 3:选择1KB的VROM存储体映射到PPU的$1400
0 d5 v1 E+ Y; T$ j2 E: E8 [ 4:选择1KB的VROM存储体映射到PPU的$1800
/ K4 ~3 ~) B, F6 C8 L 5:选择1KB的VROM存储体映射到PPU的$1C00/ `* x y9 x* G5 V2 Y/ M4 b9 F" T
6:选择8KB的ROM存储体映射到$8000
% `) t5 Q6 c2 M4 q 7:选择8KB的ROM存储体映射到$A000/ T6 A r) t3 l* ]4 G
位D6:4 q. @, q8 x. A; _
0:允许擦写$8000和$A000
* L B1 t& R7 `6 u/ h: ]9 M+ c 1:允许擦写$A000和$C0001 z2 _& X$ n L5 u# T8 o
位D7:, b! v6 z2 R! P9 i$ M' X, B# o
0:模式号D0-D2使用普通地址7 @5 H- O/ Q& z7 u( }
1:模式号D0-D2地址异或$1000! D E2 q9 ^" Q7 c( L# l: t
/ S0 C7 p; B% S6 ^+ L6 G$8001: 模式页面号% w2 j+ N! q& C e) K7 N
写入一个数(00-07),切换存储体到对应地址7 v4 L- l5 \# V7 }
' [: `* ?& X# }2 Z/ w: q+ n1 s$A000: 镜像选择' M) B% D( Z1 X E
0:垂直镜像
& `( R( w) q! u0 ~ 1:水平镜像1 d* U5 G# w: h/ o6 d0 D1 l; P
8 x) [0 w2 Z" j# v9 S0 q0 x1 w$A001: SaveRAM 切换+ o% y. U. @" o5 A" @. r% B5 x
0:禁用$6000-$7FFF* [ q x4 p x( S& \/ @" w
1:启用$6000-$7FFF
' {" G4 g" c- f$ Q0 m) V/ ] b/ N2 H" z0 x5 c! V. m
$C000: IRQ计数器 t* g$ Q8 m5 U3 W$ W* e5 I( s
IRQ计数器的值存储在此处
I7 q/ k" ?" K @: C6 A: p4 g+ Z8 |+ O7 {- P3 r: Q! W) U
$C001: IRQ暂存器$ x; B9 W# q. P1 Y% a9 h
IRQ暂存器的值存储在此处
: J% v2 R; O$ h: J! H
. z. _# a- z3 f( ~' N- a$E000: IRQ控制计数器0
4 r/ m6 C# l2 _& f, C- c' ^ 向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
$ ^! h/ I7 C: b$ t1 f) G% M$ T/ u( H5 c+ a$ C$ u
$E001: IRQ控制计数器1
; ^9 Y( l; Q7 v/ B: |. b 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)
) v/ v7 X- v; T! r& f2 G
' N# | O8 M9 d x4 p, g$ J. N- q那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:
% y2 w6 a5 h D8 K48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68
6 H2 j( t3 o1 A4 L7 RPHA 累加器A入栈* F" a* W* l: g
LDA #$06 设置切bank地址为$8000-$9FFF
; g1 R5 d1 P! ?* w4 U' C) aSTA $8000
! ?4 G/ x$ C; F+ l- ELDA #$0E 将第0x0E号bank切到$8000-$9FFF' U4 ^# N; H7 ~3 w2 g& C- V
STA $8001! \3 Q- Y$ k/ Z: r+ \# L; c/ a
JSR $8000 跳转到子程序$8000
2 P7 H- Q( u. [1 Q/ ?PLA 累加器A出栈- H: \: s" L) N3 e! v8 a) `
O Q- o& P- q" k" I! k* f
为何切换的bank号是0x0E呢?# z8 k* k: o! M# G" n
因为在扩容ROM后,文件PROM结构为:
/ q n. B, c% g. m原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
7 ^" b: w" w* T, g8 R$ {' I# ^/ c+ f
Mapper 4切bank一次是切8KB,那么文件结构就是:
+ g5 M$ G0 O. ^原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
) A' @9 A5 `5 p3 r) Y. L* H3 R0 F因此选择扩容的第一个空白bank就是0x0E号bank。
# o$ Q" A+ \2 n& @( v& ? 5 p' z" T f- H2 T
9 l+ J( e7 f4 D' m/ \
然后去调试器找RESET中断中可以放下切页程序的地方:3 G% j- {( Y# K* g- |" ?2 q
首先长度要小于等于自己写的切页程序。4 Z* i( u7 g* H3 [* u, ]
可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)6 V- Y" E; M) E& }- G% n6 \

. Z' V! c) v# e然后跳转到$FF7C:
8 G( U3 m1 P+ U n3 b( S% [ # ~/ D3 S& i$ K- \. h N
% P7 i: O& T/ \# n/ z* G

1 y$ x, v2 \5 N+ Z+ t
$ H' E6 q: _( P2 o5 t! E U复制下可以被替换用于写切页的程序:
2 {( e `6 S: J1 R先选中,再复制:, @0 \) K+ c) a3 ]

# [' l( L: U" ~
2 o" Y( i- R6 D( G1 ~在Hxd中新建一个文件,把复制的数据粘贴上去:. z5 u1 l; H4 ]; ^% @

3 R8 J$ v7 a% {& U, D
. V- D# y" [0 @4 T2 X+ L
7 H2 l/ |" k; r2 X* V) U0 v7 k% i/ U' I
# E2 d S& A8 E- [1 X; y然后回到十六进制模拟器:: _- V2 f- s5 {, ]( b% T7 V# {4 G
转到$FF7C对应的ROM地址:
; M/ ^2 w6 E7 m$ K
$ \" ]2 `$ @6 W+ @
$ d3 r* x6 V; ]/ Y/ l然后复制Hxd的切页程序,粘贴到这里:( u3 N4 |1 T% q% t" F
1 J, n& F( }: v8 A

7 a- r S! u& @) o q u; ~/ B2 q# K4 G! A' S
没有覆盖的用EA覆盖: m% I u; l8 o2 P
; g- O; ~6 Y; l+ Y
打开调试器可以看到变化:. \/ _% G; ~5 @! x" ]: ~5 H" L

, F% Z3 i! i: X9 |然后添加$FF7C执行断点:) W4 o9 g1 S2 Q7 } g

) @% q9 |6 r4 l, B2 z' n/ T! ~- W0 t7 u
4 V+ Q3 G1 j' O : L; t l6 a+ \& X' ?5 M' h
单击运行:
G3 m* i% H6 y D" H* e" G然后程序在$FF7C这里停下来了。+ a% K8 v; S! n
g( l+ m' u5 ^3 _& C( w
然后单击单步进入慢慢跟踪,直到跳转到$8000:
( y2 h. f$ `0 s! B 8 s/ y4 A' L! Q$ T% A8 D/ i
然后打开6502_Simulator:
1 c$ ?2 i0 J( F6 u0 [
7 m/ F3 r4 H; ^. t; P5 I+ B3 c再打开我写的数据搬移程序:
O; S9 o( ^( i% ] ( R# `3 N. x. C6 Z4 K$ b

4 j q0 ?& J% ~0 X6 ?; z然后修改对应的数据:1 q7 J1 J5 S$ B$ ~
程序开始地址:修改比如$8100就修改为 .ORG $8100
8 Q/ E4 e" l* V! S0 W2 \' T4 l复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
& o- s6 A w T$ e4 {' G* j3 m; d从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82
3 w! b- ]/ V6 Q( O/ B. x想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$916 |7 Q, F, f) {1 S6 Y8 @7 y
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。
; D$ _8 M( l: D, L# A如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。
. J9 a- l9 F5 q2 U& F) _中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。# n: I( u- S$ e
后面的不用管。5 N% v0 B0 }. p" C4 `. W( u
8 p9 ^# p8 A5 I7 l' t+ X0 F$ u( j
设置完数据后单击编译:: o% N# m0 e8 @

2 G8 i. y6 s2 \! ?然后保存编译文件:
$ f, _) h; z& e3 q/ `* J2 ^
: z* p- }+ v3 c7 j选择二进制方式保存:; Q( Z, }- O5 \' u5 y3 L3 i2 d
8 t2 ?7 O; S# Q! }% ~
9 Y" Q' _3 ]7 W: V
用Hxd打开保存的二进制文件:% h! h* k4 M* `# e

, j5 @$ q. X+ }: L
$ J# D4 j& Y3 }: c, P; v跳转到设置的程序开始$8100:: i1 ]- z- c3 q0 L6 ?

! L2 z: T% N" @# y 2 u1 @9 C8 | `7 _& A) p
0 J6 B; U3 T8 k$ N& I- M6 p" b6 V
回到FCEUX,转到NES内存的$8100对应的ROM地址:( v. S2 |/ |5 T; Z. P$ a; P

: s8 Z7 z( Z- U; ], [
9 B$ j6 b# C+ e- h9 T/ Z0 B8 C
" Q* N7 T6 Z" i5 o9 s- M8 ~# X& W 5 w" o) j; Q V! K

% x, c( o: R! ~6 y. h4 m然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:( S( m0 O. I- t% A1 m
# g+ m) Q' l* d3 l( a

# {& [( j& d; L7 s$ d$ x: D, ] ( s. `. e$ z5 ^! i. H: |3 S) x
然后转到NES内存的$8000对应的ROM地址:( c6 w1 W8 P) n* a3 i; e" _
3 r# n5 M) s: L% l
) i3 q! s+ @( e3 m3 B9 N
2 N; r& H4 ?4 q% s) K

; M6 C% Q# v0 a写上如下程序:, N! U3 ?. j; T! ?# ?
A9 80 8D 01 A0 20 00 81
8 J/ H: O# Q" wLDA #$80
) p# L2 Y1 H' q. G9 W/ ESTA $A001 可写方式开启SRAM3 k+ h4 w' w/ P1 p- H* S
JSR $8100 跳转到子程序$8100+ J, a+ h' C; t% M6 ~* o
( B8 F' W: v- L5 }! L
然后把Hxd里被覆盖的程序复制过来粘贴在后面:4 x2 q1 M' T i5 I2 K8 R' p

8 B2 M) {1 y. o) o5 ^+ @0 s& e. b
! G- I, J7 D: o+ m5 \ 1 i5 d9 f# _; b) h( ]/ b+ L
末尾补上一个0x60:
: r8 l+ ?1 x% ` J$ zRTS 子程序返回
( A. G/ I% [5 b, k& o1 \ & p- v4 }6 x! I! T
然后单击运行,ROM音乐响起,正常运行:7 A3 f/ {& T2 W& D5 a) \
7 s2 O6 v0 ]4 ^; _) B7 S
: ~0 P1 u6 Y1 o F. o# B' G
然后转到NES地址$7000:; d w: |+ T% Z. {

% [2 R7 ]4 a4 I' c7 E `
/ N: B0 b5 j/ x5 D) |4 X. x6 U1 e 5 D0 h+ E& F8 t; C
2 K7 {7 \4 ?- V; y
' t2 z G2 e3 k
可以看到,$7000-7FFF都被复制了一片数据。1 n; H! V* k+ V' ~* E. o8 K
测试没有问题,然后保存文件:
* q, B3 g1 L5 g8 K% ]
8 j- y8 T6 W2 i% o U" d, ?" I2 r4 n( f以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。& @8 w1 S7 v# M# K% S8 z" _
后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。
# f, t) B" H; e. F, i |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|