|
|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑
" b$ }; x/ r; V [# a
0 h7 z; j8 [0 z% N8 {- P* z[FC][SRAM扩容教程(Mapper 4为例)]
* N" C" `; ^0 W. g& K5 l$ }
9 }- ^8 i7 ~6 M n时间:2017.4.28
4 f# |+ m+ ?' i: z作者:FlameCyclone1 ^2 r" l! E8 U
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator" K4 s* C4 M9 a# a* Z$ V, f j) D
ROM:双截龙2(J).nes
0 P9 @& k8 @1 ]0 W适用:没有使用SRAM的ROM @& `: n) _$ u( K- B$ h
9 ]. Z3 D9 g) E9 _, v) x7 s1 k
首先用Hxd打开ROM:0 ?# a( O/ s+ D7 q7 V
, r1 R7 L, g+ A5 \& G3 m0 o C
然后扩容:
" I5 V* S2 S# h( v" i; G
: Q- n _# f9 K3 o4 N) M8 k% I$ L
& {4 n1 Y! Z3 I- \ r# ^' D; q- ^ W# z% _5 K( w
$ @" ^) J3 }% U0 [( g7 u% J7 B/ r% v; b
1 T3 D' |, @, ^5 V' ]) e ?' \& Y
+ S3 D) w1 j% E4 @5 O g# V' R
n$ Y3 p7 j' }# a& C" M# g" a9 w8 x* ?; A6 \6 Z! S
, r4 B8 C. g8 \先看看任天堂产品系统文件对NES文件的说明:
2 f- [9 o3 Y; m! M( eNES文件格式/ E, N) x7 y- N! Q7 W. d3 T
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 9 m& [( i4 `4 F2 Y3 F9 D; a
偏移 字节数 内容
Z8 r7 K2 s& @8 p1 C, B+ C+ _3 G" c0-3 4 字符串“NES^Z”用来识别.NES文件 9 @! j& ~& o' @% X+ }4 Z
4 1 16kB ROM的数目 " g) p. u, I$ Q, H
5 1 8kB VROM的数目 $ H- A& w; \& F! \
6 1 D0:1=垂直镜像,0=水平镜像
* s$ n/ W5 B( i! G8 D) e6 g D1:1=有电池记忆,SRAM地址$6000-$7FFF ) l5 P6 k. F! O! v# [1 P, m. \
D2:1=在$7000-$71FF有一个512字节的trainer
* ^- [$ L0 Q8 f& b3 D) J D3:1=4屏幕VRAM布局
' x, A6 \: y5 p/ U D4-D7:ROM Mapper的低4位
0 N# f# S% B# a8 h7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^)
% Z# j& \5 D1 t4 }: u6 L& p* m [8 ? D4-D7:ROM Mapper的高4位 $ t, y ? z4 b! v4 u2 Q
8-F 8 保留,必须是0
: |$ s! M8 ?. U9 E16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
y% `2 Y3 ]; F' Y! C, s-EOF 8KxN VROM段, 升序排列
) b/ n8 @9 q- Z( {5 G2 I3 P( O; j" J- f
然后知道这个ROM有0x08个PROM和0x10个VROM3 r- V1 y! [, y* Y
接下来扩展PROM位0x10个:
0 D ?9 X; o9 l/ w$ V1 _先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):+ ~" }: Q1 h+ v5 b7 d3 i* f

- H3 J5 ^6 T* w" r由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:+ {/ a& }3 m1 r
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。& E, P4 h h T- C) M
于是可以得到双截龙2的是:% j! r4 J8 \3 ~. {
(8-1)x 0x4000 + 0x10 = 1C010。2 b6 t3 e, Z0 ]! i
然后跳转到1C010:% ~/ b" T( c* u) T0 ?- W6 G$ _

% c5 m0 D& k& n& K) ]: @* ~) r
% x+ C% J- t2 k# s
p: ^! l6 F. X0 ^然后插入0x20000字节的FF:) a! M: b3 b8 M4 n1 F+ Y& T% O$ K

5 n1 j% s% ~5 W; Y3 D& V # E, `, d! `& @2 f- d

, r( v8 @% N! c' m; k4 g然后保存:
$ ], G, z2 U/ I8 I8 I. _! ~
; {; T- N5 t3 A- L5 A' {0 f % J7 B3 N% s P- S8 d4 z3 L! j& F
用FCEUX打开正常运行:
" {* U$ D. p7 b J ' m O3 }3 W7 O+ c" I. X7 l
查看文件信息:
9 y* X% M4 }, D6 z& m1 V
; F7 p( l: ?- J! U
" g; z |1 R% }6 p% _) Z接下来切页:) z6 j w1 E1 U: ^$ ?
先打开十六进制编辑器:2 _5 x9 t" r3 N
4 L4 J8 L- S8 o- d
拉到滑块最后,看看重启中断. h+ j0 e4 }9 u4 h S
中断地址 中断 优先权 9 G+ b: E! ^* U) j
$FFFA NMI 中 " c6 A8 R; V# z# |; g: _
$FFFC RESET 高
! {! y% l3 Z! |& [ s# V$FFFE IRQ/BRK 低
, y3 W$ V: }; f; y; z- l( f
& {$ V! i$ u& ~0 K6 {RESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
0 E: r: i& r5 @由此可知双截龙2的RESET中断$FF65。3 ]( N" q, u4 H# [# i( v2 l$ a
接下来添加$FF65的执行断点:% K* |: W# a2 }: k0 O
打开调试器:
6 q) `) E8 L2 L7 v; c) l# F/ y" I
! t7 {( p% ^; `% z1 Y" D6 A, M添加$FF65的执行断点:
1 S2 u6 u5 ]3 o9 I' o+ v
7 N/ b' u# [6 l
: E0 U/ _1 b4 S7 r7 b% L+ X P4 B) a Q" ]7 @2 b
单击确定:
4 q# f; v0 R9 ^: l8 y4 ]1 {
4 r/ t$ F w% z l* h* j2 X% ] p! S
) w1 K6 z2 [5 t% O/ {3 x+ [9 w5 w然后重启ROM:
# Y, A3 v( i$ l' O( e' t/ {5 [
9 X$ d. j" K( W" V4 N调试器此时弹出来:2 J) r3 ^5 R: _6 h8 b. i

8 W8 F. |9 D; F) P5 T- E" f然后打开Hxd,写一段mapper 4的切换bank程序:
% ^ O S( x" q) T/ K: `- b) n& z先看看mapper 4的说明文档:
+ K$ a2 G1 ]2 J( V4 U5 C6 wMapper 4
f6 Q' z! m1 _5 g6 y* B
& F9 ]1 b* p' X; l0 c' K3 W$ N1 j, q$8000: 模式号" K( ]7 y! \ {7 [& F6 p
位D0-D2:0 f8 ?6 S# E' Z1 y$ I4 [/ l) m$ E0 f
0:选择2KB的VROM存储体映射到PPU的$00001 r, d' `) v& R: x* `* W
1:选择2KB的VROM存储体映射到PPU的$0800
, J7 |3 O& v( R0 M 2:选择1KB的VROM存储体映射到PPU的$1000" I! [1 `1 f* m1 R3 Q
3:选择1KB的VROM存储体映射到PPU的$1400
! T7 ~' M6 H s- \5 P9 j 4:选择1KB的VROM存储体映射到PPU的$1800) A$ \- [ R% Z
5:选择1KB的VROM存储体映射到PPU的$1C002 e& k. D, Z w/ o- d+ w
6:选择8KB的ROM存储体映射到$8000
- j; } x& y" F% z$ p7 B# G! z$ x 7:选择8KB的ROM存储体映射到$A000; V7 `+ @0 g0 f' A% R( Z- K+ U1 x
位D6:
. G2 R1 n! m, d: p7 A 0:允许擦写$8000和$A000
1 ^! e. j2 g- D4 m( H; e 1:允许擦写$A000和$C0007 P2 E7 f% @& U
位D7:
) }4 I0 K# |" [; J9 X a4 Y 0:模式号D0-D2使用普通地址, g" @) W# x, e) p d% S) D7 L0 R" V
1:模式号D0-D2地址异或$1000 {! A6 K1 p$ h0 X: _
. @4 {1 l1 m9 E: C8 w; ? P
$8001: 模式页面号: C5 E+ b, f. |, T" r
写入一个数(00-07),切换存储体到对应地址
/ @6 G2 k" k% F' ~7 T; d6 R* H/ \. s& ]/ x9 q' A9 c
$A000: 镜像选择9 d( w# N8 Y1 q- w
0:垂直镜像) T) u: I% u& \" }
1:水平镜像7 v9 W; W1 N, k
9 L1 u) r U5 K" ?% f3 m1 L+ w! Q. W$A001: SaveRAM 切换1 T% ?( K) P$ v/ `" g. D
0:禁用$6000-$7FFF
6 G6 n- t+ `! L q/ J$ l( @ 1:启用$6000-$7FFF) e0 F3 p' }. H8 }- W
' d+ n$ \( B- T5 g; t
$C000: IRQ计数器: ]5 y) _1 D4 D6 g/ F1 [+ i
IRQ计数器的值存储在此处
$ i# X5 P( Q, i( J& z' C" [- x# [& \- M/ g; _3 j
$C001: IRQ暂存器. N3 P8 D; j: p9 Q* I
IRQ暂存器的值存储在此处
3 y/ i* C3 H$ X" m' \/ Q. z& N" {6 p5 a' V
$E000: IRQ控制计数器0
/ S: }' W0 c, X$ N; y2 w8 ` 向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ2 V: t4 R$ d( p s- b+ O2 L
; H9 |+ d" K$ V! S
$E001: IRQ控制计数器1
3 x. @3 \% [3 j- w 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)8 I. `4 V5 [% Y3 p
& o$ o8 H+ A% C9 v# y那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:" a9 F; H% D5 N
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68
% w+ p4 w l8 Y/ [3 N, d* OPHA 累加器A入栈
" L. E. \3 s, l, L/ y8 ZLDA #$06 设置切bank地址为$8000-$9FFF
4 {+ d1 G6 s* S* fSTA $8000
( i. q V; i T! k. QLDA #$0E 将第0x0E号bank切到$8000-$9FFF& C: ~8 m2 n% i
STA $8001
0 K0 z! o0 Y1 M& iJSR $8000 跳转到子程序$8000
+ [1 P6 g1 P+ c. r' X1 ZPLA 累加器A出栈
# I3 F$ C" r" G" s1 [+ D3 O- `5 {
为何切换的bank号是0x0E呢?
9 F+ z5 J" M, C因为在扩容ROM后,文件PROM结构为:2 z9 `9 y+ P' X- e
原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM4 Z4 U- a. I1 o {' c
! k$ M9 z+ m+ EMapper 4切bank一次是切8KB,那么文件结构就是:
9 Y+ _9 {- w# m4 Z原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)5 I$ `* ?. _5 z2 h* D" d/ Y
因此选择扩容的第一个空白bank就是0x0E号bank。# w# X+ o) X' x4 j% w8 S
7 j3 I, i$ L3 L% e, u: T( \
# I9 H& j3 Z/ t& A- F A然后去调试器找RESET中断中可以放下切页程序的地方:
) R7 e; I: c. ~首先长度要小于等于自己写的切页程序。
2 z, p: v2 S$ Z5 E可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
9 v: T, n' E) O* A3 F7 E 7 |* B' g5 ?- D# L
然后跳转到$FF7C:
; w: l. f$ S; Q+ \* X! ?; z4 r, J & I4 \. B* a# r- p4 r

3 o5 T* Y7 K* X+ @& D ( R% _$ ?: V( `8 |3 _
% R2 T" Q2 e% x" S# k/ d2 v c
复制下可以被替换用于写切页的程序:0 Q; X# B8 [- w5 @
先选中,再复制:' v# F) b, k6 O+ n4 \2 F" p: r' \. V

+ ^! o* C" D5 m& O$ s6 _
8 D' a! c9 @2 K. U+ j在Hxd中新建一个文件,把复制的数据粘贴上去:5 X1 p/ ~( P) W3 M1 D
) p/ h, r1 J0 c7 w) u

( @3 o+ p X6 x- k . r; s8 ^- u1 W
$ R' i! J6 B& @. H然后回到十六进制模拟器:% H# h; a% B2 E' G% N
转到$FF7C对应的ROM地址:& e" p' m7 L' P1 u# e3 k: W, o7 h" e

' b8 o8 ^: ^1 D9 S2 ^ 0 }& U# w1 D1 O( M4 e
然后复制Hxd的切页程序,粘贴到这里:
, I+ _1 o6 K/ U7 y; ]/ ]
; T6 t$ Y0 p- G5 P+ q6 _
0 l+ y2 _$ S+ r2 j6 b4 v w Q+ T# m1 L9 ^5 Y
没有覆盖的用EA覆盖:
4 g# I ^7 e1 f# T$ J1 s5 Q , g& O0 s, ?) h2 Z6 X2 N# Z, I
打开调试器可以看到变化:
+ m f% S5 m" T2 a . I- ]6 @( E C' G; r
然后添加$FF7C执行断点:, u7 H7 x' |0 g

4 a6 U# `$ _$ O2 {3 j , _# a3 g( u/ o3 Q1 H E

# h) q8 `/ ~5 h* }- i! ?单击运行:: ^8 S _ Y* d- S
然后程序在$FF7C这里停下来了。
8 L) O% n! ?. T, K, W 6 b% d3 X/ N7 ~- w! o
然后单击单步进入慢慢跟踪,直到跳转到$8000:
- r" C/ C: ~' X% M# K! C
& @( l4 U7 d; ~- a9 o9 w) Z然后打开6502_Simulator:
. H" ~$ g% ~0 j }5 h$ X5 t @; l5 D s2 x+ \3 r! a" w- |
再打开我写的数据搬移程序:9 Z; L, s3 w$ d

0 K$ \" u# v" n, L7 x+ l9 M6 Q
4 ]4 R- z& G7 O7 ?3 X* k# F5 Z p然后修改对应的数据:/ L* g G3 \% {& @" F# d4 W
程序开始地址:修改比如$8100就修改为 .ORG $8100
% A1 X" J) |4 E4 a复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
+ K$ J& t! a2 b从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82
- C* e8 W$ p( t0 H2 Y想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91
; ?% y$ q+ P. Y% C! B也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。- a+ [# Z& l) a4 k$ r( m
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。- P' C p2 G& u3 a' X3 w& t. t
中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。7 Q! @5 S4 A1 y9 T, e5 l! V
后面的不用管。
" @( }/ @# q$ ]/ q2 V4 q; \) G
6 H6 n5 G2 K% u" N# y# Z设置完数据后单击编译: C+ V* g, k( |+ c* w7 D/ R; ?

[* a7 ?2 y1 e& [3 v然后保存编译文件:3 B7 m' x7 }1 t. ], @

7 P# f3 j: }6 w/ [$ z* p; i- T) R" X选择二进制方式保存: q) j/ h; v5 Q+ R8 P6 i1 I
) h! B7 K9 y: [% e( l% H* e6 n
- |, |" b+ v8 Z, o
用Hxd打开保存的二进制文件:
( p* s* K4 Y% Q3 i" T
- n1 K9 ^" q2 M9 B8 k
% \. B' F- b5 e( z跳转到设置的程序开始$8100:4 b% o4 I4 {) V& B+ ^+ v

! P- [; R" ]! m% U3 q # i0 g! p3 s9 v3 \ l8 g

* e4 x+ i, [4 C$ j! k6 V c回到FCEUX,转到NES内存的$8100对应的ROM地址:
/ a( Y/ Q$ B" ~. Y6 T! Z# k
3 P" v3 I( z3 N
4 B% P/ }* L- o0 _6 y' u2 h+ @3 |( q
# d, e; `2 |: x! K% F
( W! ~" H- ?, Q/ w' M
& q" v: C! U: L$ \) U8 p" ~% v6 W p然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:& t) I+ A9 Z4 C0 W+ C3 \
x/ }9 d' m: M% x {( c

& V5 E9 w2 ~5 S! k0 |% Q n9 j
. `9 K2 u6 ]& }( d2 Y然后转到NES内存的$8000对应的ROM地址:0 Z: N& c( C' W7 D9 L) q, G/ k

3 J' q& o& E7 p- \& E" Q
) A$ b# D: n" i' Y& X
( Z. g# n1 D; U- @
3 S' F9 O0 {) `9 F K写上如下程序:% q' Q& _/ r: u) t3 [3 \
A9 80 8D 01 A0 20 00 81 # w; O! F) h/ \+ d, A/ _7 b3 n9 n: T
LDA #$803 ~: R4 A! W4 g, B2 @
STA $A001 可写方式开启SRAM; V7 h* @7 k. n: I# E! Z5 m0 E7 E
JSR $8100 跳转到子程序$8100
! p; b! x5 M3 r" b, C. V- R | ' w) y. T2 G. [# G" ]6 a
然后把Hxd里被覆盖的程序复制过来粘贴在后面:0 L8 t% S& @* u3 t8 Q
$ K* j1 o! [8 c1 @2 X1 ~; O( t

! h, U) i3 j% d/ i2 C) d
% ^; F! [6 L9 t: b7 C' n2 s0 X3 g- B末尾补上一个0x60:% A! F' p- m5 G( g/ l2 r5 i
RTS 子程序返回
- x) l, C: d2 T# |
( ?3 n6 o) G0 S7 H然后单击运行,ROM音乐响起,正常运行:
1 R; l+ m: V, s, b d, v& L& y
' k4 X7 X5 @$ U( x1 E C# ?% Q2 k5 j 6 ~/ K5 |- u- X. ?# r/ e; x0 T& s
然后转到NES地址$7000:% N$ d4 z. G& u, H; \/ F4 S1 Q L" O5 I, U3 y
! o1 ]4 u! V+ c0 i1 X" t, i
' `* o+ o' x v8 M

* x F6 Q( Q3 X; V4 @
2 p9 m) j3 R* h9 `4 p7 h4 U0 L3 [* k & d5 R6 r4 x4 b9 W
可以看到,$7000-7FFF都被复制了一片数据。
% B% u3 v2 x' U2 G, R( W测试没有问题,然后保存文件:' c* h1 `7 v$ Y9 x
# Y7 T* s6 v& C0 i i0 `
以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。/ N) w7 G: w; Z+ ~' |6 w
后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。
" g- r7 u! `0 M5 @ |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|