|
|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑
9 w* e/ m: b; `3 @) y& K( k
/ [3 Q* D5 x% J% W[FC][SRAM扩容教程(Mapper 4为例)]
( g# B) N& u0 |" W$ |
9 a {! q a, G- D# j$ ~4 ^时间:2017.4.28
9 p ?3 C6 Y2 B' m- a- D作者:FlameCyclone. t3 u4 i# f$ d! D6 v) w- d3 }
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
, U) x: d/ K( ~" R( PROM:双截龙2(J).nes
' X7 \ u0 `% Z: s适用:没有使用SRAM的ROM+ F3 F- Z* A+ B1 L% c3 X
* h) a8 {; ^: _) A0 y9 O首先用Hxd打开ROM:6 s- {) b; c q9 f1 Y$ p# u5 M$ Z
4 C% W1 ]' g! v- c1 E- g
然后扩容:: z2 D# Y! S0 Y* n- G+ S6 j
: x# H4 _4 l/ _: {
0 u; |: k- l9 i$ q8 y1 [% d' s0 }' C+ ~* t$ D" X) O E
6 D* C, X; O, \- G
, [0 E/ L$ p2 L* G! y3 a3 A
& v+ q- F0 f9 y/ z! I
# k8 g7 W {9 D0 i2 U" i/ j9 t' Z' R/ S; f- U
+ j$ u1 ^( m0 ~# J! D3 ^) ^' O先看看任天堂产品系统文件对NES文件的说明:
( O1 z" S) _9 X/ g6 P( m' \+ ZNES文件格式
7 W7 |2 u) X W8 i* E& r: i' I. i! z.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。
4 K9 j3 `2 |$ `9 V9 W- Y偏移 字节数 内容 8 S# g4 k+ i* i. N
0-3 4 字符串“NES^Z”用来识别.NES文件
6 _! B$ A/ c$ m5 Y% a7 x4 1 16kB ROM的数目
6 n, m, q0 e9 f( b; G; f5 1 8kB VROM的数目 . c! H- v0 B: D( ?: f% C6 [
6 1 D0:1=垂直镜像,0=水平镜像
2 G0 t% U) E% Y D1:1=有电池记忆,SRAM地址$6000-$7FFF
% v1 a0 w- O8 ? D2:1=在$7000-$71FF有一个512字节的trainer
|: r5 b. y9 g# }# _ D3:1=4屏幕VRAM布局
5 e/ _2 x9 k6 |. y: s: E% P4 w' X D4-D7:ROM Mapper的低4位
! W+ {, q* a! z: Y$ s7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) 9 j1 E9 A, V, O8 B5 Y
D4-D7:ROM Mapper的高4位 6 c( i& X0 m+ _2 X% G
8-F 8 保留,必须是0
# L! h6 g/ c% J16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前 . _. ^$ }; i5 K
-EOF 8KxN VROM段, 升序排列
. ], r) H9 a0 P. { ?" A3 @( J( n4 G- A
然后知道这个ROM有0x08个PROM和0x10个VROM/ r& T. T! ?- x* c6 z
接下来扩展PROM位0x10个:- k/ ]; t) W4 m( t3 l8 v
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):4 {& w# m; } [4 P

7 }* ]/ O3 u2 o [由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:, I1 k+ i8 L6 Q* ^9 z; I: P8 W. ^- A
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。' z. ~2 A, j- \
于是可以得到双截龙2的是:
2 q" T! i3 E) E1 {) N7 N# ]8 W(8-1)x 0x4000 + 0x10 = 1C010。
0 o) O7 }7 E/ z( V7 j0 D然后跳转到1C010:
4 O. ~# O* \% R3 J' T
, f' _# [8 F6 \ 1 A5 G) f% |; c q: i. t: e

5 U, W- O- Q) I0 K8 ^) c& Z5 O3 i然后插入0x20000字节的FF:
, I/ [2 I, {8 s, _ 3 O+ D& u$ u1 ^- w- r/ F, q# e9 C
4 a" w9 }$ \- U" p% [- j7 P( H: \

6 A( {7 a4 K2 H然后保存:2 x) [7 y R1 k' d% _% ^

2 J; U" f8 y, D& {3 ] 0 k6 J9 \$ `* n2 `! w
用FCEUX打开正常运行:, X, H/ m. {- O# L

8 B1 {' f2 x5 }0 S- c, ^4 ~) \查看文件信息:
5 O; [7 W1 L* V4 M9 ?+ k* ~ 7 u5 k! _' v; ?. z6 h
; @4 f6 \: p( {2 U9 a& u8 {* a
接下来切页:6 M" h7 N ^; ]) }5 E
先打开十六进制编辑器:: z& G" G* D7 l5 U+ g# ]
4 [: P: O+ i: C' V$ N( y
拉到滑块最后,看看重启中断
+ J# o9 y+ A$ @中断地址 中断 优先权
; \% j7 A& U+ ^$ q1 t. v$FFFA NMI 中 ) l% L4 J5 w3 q7 {& U
$FFFC RESET 高 ! J0 N% h" C2 U5 l. n
$FFFE IRQ/BRK 低 " f% H1 d$ G3 o8 F
& [) L) s# A# T- j; A; k
RESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
& U2 T' \& a1 q4 ^由此可知双截龙2的RESET中断$FF65。
$ W7 z6 z+ Y- p/ E" h接下来添加$FF65的执行断点:* v3 `" Z3 ?! j6 k
打开调试器:
" X& o) C7 z0 s 3 x4 Q% @) L$ Y# _; `* @/ r
添加$FF65的执行断点:# L, v" f0 B1 H0 Y. ?
! f" C$ }! t: d; @
( L; b+ Q/ @1 x8 }
, X( z* X6 W4 A0 s
单击确定:& w x0 f, r. {; {7 B
. j' I# o' g5 p' t- ^! R9 ]# m
; r8 b3 X+ \1 O/ ^- ?9 r然后重启ROM:7 `% H0 z+ X/ N/ }6 }
4 w# ], T1 L U( T! u! q4 L# r' n4 [- `
调试器此时弹出来:) U& {8 o& R# g

4 H" c! y8 z; N. C' u$ q$ @然后打开Hxd,写一段mapper 4的切换bank程序:- X$ b9 p: {( p3 x( _! }! N* [# J
先看看mapper 4的说明文档:
: i, \) M4 T9 W3 C, uMapper 4" b% x5 d) ]2 x4 o
" Z/ J- L# k- i( _
$8000: 模式号, V& n& y) E7 A, `" a) ~ Q ^
位D0-D2:
; Y4 y& M, Y5 A; a 0:选择2KB的VROM存储体映射到PPU的$0000
0 H; F5 y8 E! ?0 n+ P! s3 h! ?7 T 1:选择2KB的VROM存储体映射到PPU的$0800
1 N* ~% @3 k# B: R, t- T2 Z( } 2:选择1KB的VROM存储体映射到PPU的$1000
/ ~* }- b; K9 h' N T* f 3:选择1KB的VROM存储体映射到PPU的$1400
7 H% t3 n0 E7 R0 W 4:选择1KB的VROM存储体映射到PPU的$18008 w( P& R+ b& l; Y Z% q. ^0 w
5:选择1KB的VROM存储体映射到PPU的$1C00, Z$ `" {+ n' b
6:选择8KB的ROM存储体映射到$8000( [1 P) ?. C, B1 Z4 i, s! d% t
7:选择8KB的ROM存储体映射到$A0004 e6 H/ |% T+ q; n! N }
位D6:
, b0 l; k" C* ^8 @- X 0:允许擦写$8000和$A000. S! I1 f+ T ^0 u
1:允许擦写$A000和$C000
! Y4 n Y' Q* m! u- [. X 位D7:3 J) O3 G+ G( n' q/ n
0:模式号D0-D2使用普通地址
# }5 D9 G( A8 f& f 1:模式号D0-D2地址异或$1000+ u$ \8 T0 o3 x G' ^
i+ J% O7 U) N9 s& ^$8001: 模式页面号4 {: |7 I0 _6 ]" l! @
写入一个数(00-07),切换存储体到对应地址# M+ y' c0 H& j- D0 C( R. \7 Y" _
$ @ S9 \7 d/ x
$A000: 镜像选择' B! D6 h8 E" @* T
0:垂直镜像
, t5 A* c, h' t8 U, G. c$ o; q 1:水平镜像
( B& y+ U W# b: t' J) Y
8 e5 p! Z. B% ^$ ~1 p/ A& c$A001: SaveRAM 切换
4 @5 b$ l1 y( I: H0 B/ Z2 }3 _ 0:禁用$6000-$7FFF, ~+ L8 {5 y' w; \( ^& l
1:启用$6000-$7FFF
- V0 h4 ~8 T4 i: T: j
$ x) j& a$ W/ t) Y0 u6 V- ]- y6 w$C000: IRQ计数器
& \. [1 \7 k4 G7 Q3 Q% Q IRQ计数器的值存储在此处
+ u$ x& @$ {5 J! T, I$ d. Y9 G
- B' X0 K( y; r- e5 C, L$C001: IRQ暂存器- B8 G3 B4 ?5 t" F
IRQ暂存器的值存储在此处
5 ?4 K5 h( [: q5 `9 t
9 k* W* H3 k/ R# o! F$E000: IRQ控制计数器0
3 f$ C2 T: G8 o 向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ& ]0 }: ~! u& @' z
5 L) q' ~9 B( L% X8 ~8 w4 i# N
$E001: IRQ控制计数器1
$ v) X! d+ a; E* \7 t9 n 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)* w& Q' R" [/ \6 M0 y1 k9 Y# A
2 n B4 r( p# m1 u% q! M2 G
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:1 u* _6 K( |' w- _, p) |* l8 v
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68) |( S- Z l4 h: I, o
PHA 累加器A入栈6 E L2 k( B$ F" S- V! L" r, L+ [
LDA #$06 设置切bank地址为$8000-$9FFF( ` x k% \# w2 o5 d
STA $8000
& v) [; i; ]3 m5 u. C1 v6 QLDA #$0E 将第0x0E号bank切到$8000-$9FFF
/ ]. S8 a5 P5 d% D7 t- k/ MSTA $80018 c9 i: T6 I$ F5 R
JSR $8000 跳转到子程序$8000
* {( w% @( ^# s& M% WPLA 累加器A出栈- h7 p7 I/ l, u6 B! b
' o' w; s$ V0 g6 I' \ U) y
为何切换的bank号是0x0E呢?
+ Q |$ n" C- t$ A u9 z因为在扩容ROM后,文件PROM结构为:. k2 q- ^; I' s l) M; v$ e! _; y
原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
2 T5 V+ L0 G* {4 l7 R, O$ U. V" E% D
Mapper 4切bank一次是切8KB,那么文件结构就是:
& O! n+ V+ E( q& }5 j2 j原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)! j" ?2 d0 C5 w) y
因此选择扩容的第一个空白bank就是0x0E号bank。
- x" i- P' H$ a1 @2 ^1 q7 r
/ I& D7 P( ?2 G& f* U% G6 i2 f( r9 x+ o. s. z6 E7 |
然后去调试器找RESET中断中可以放下切页程序的地方:
& A: T! j) e; Y- B首先长度要小于等于自己写的切页程序。
* V8 N6 o+ C2 y. A# Q可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
. L2 k. R& B' a+ K0 o% z
: x; \/ L, O* z; e. X然后跳转到$FF7C:
4 d, p' C( Q1 O$ D: k8 ^; v/ d . F- @1 \% p; H' ]- N6 J' I
' t0 x1 `- E- a/ H) F# P1 |" g7 e
* R7 `; y a T' N
3 H7 |7 Y6 {$ Z1 s+ V8 L
复制下可以被替换用于写切页的程序:4 G, ~4 W e4 T, u) u
先选中,再复制:
2 U# g; [# R# v0 G$ _! R& ] $ J* {4 O4 c* f7 q: w( j
# c5 \' l, z; `& b. [+ D; O
在Hxd中新建一个文件,把复制的数据粘贴上去:- k+ z, Z8 B% J+ R5 M/ \

) h' `9 K- U( m 3 T$ ^. |- @7 p) L
3 w: M0 y- C: L
; y4 O7 {7 x v6 w1 c8 e, y W; l$ [
然后回到十六进制模拟器:
) G( f: o8 K# Y4 T+ d" k转到$FF7C对应的ROM地址:3 h3 M% a- B$ z
! `6 {' B: ]' b- @6 r6 ?2 A( i' ]

) \( i5 [7 T% [8 k0 G4 H, z1 b然后复制Hxd的切页程序,粘贴到这里:& e/ e/ r l" a# ^
9 F2 a' p. m! z! `: Z: H
y G; u3 `) E1 s' a/ k+ n9 v
5 Z8 X8 W9 B3 d. V# i. M
没有覆盖的用EA覆盖:
1 x( h; d8 ?0 X- L
8 f" [) W* w" D, c4 N打开调试器可以看到变化:
# L! t2 I+ s& c - y* ~2 g- q. P. r
然后添加$FF7C执行断点:
% L3 d' ?3 Q: e d K# ` / V* e3 ~# Z" O
. A- v9 W1 q u; N6 O7 S# M; m
; T+ q) m# @" x* z9 Q% q# L0 U
单击运行:5 g$ m2 m+ o9 l/ k
然后程序在$FF7C这里停下来了。; o! `, e. H( O9 F9 C- V2 @
7 L9 k/ S- V; f) @5 ~& k0 j
然后单击单步进入慢慢跟踪,直到跳转到$8000:" k0 L" R& i v& N# S! d% m

/ P$ E# \, \: q1 m然后打开6502_Simulator:7 ^) k& r% L3 a( y8 i

G2 l7 g* Q5 w1 @1 d再打开我写的数据搬移程序:
3 A* h4 v4 c2 ?" } 6 U+ b8 v8 H5 ?" X/ _

( ?( D7 I5 ?; ?8 a然后修改对应的数据:
* Z$ B5 a' ^! b1 p. X程序开始地址:修改比如$8100就修改为 .ORG $8100
|8 v# N8 ]2 j$ z$ z: o5 A+ i复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
! z5 o+ Z3 c' T a" t从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82
# K' d3 g. U/ Y) K; N" N$ n, ~想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91
7 p/ L/ `" Q) @1 F# x O1 R) {- w/ c8 I也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。
' b6 T7 @ r2 L" j5 y如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。
! f3 ~' _2 L9 E: b$ n7 F中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。 E N$ K* t" Y7 u
后面的不用管。
7 j7 u" w9 p- e" D
! P% I0 A1 E! D3 s3 S) @设置完数据后单击编译:7 X9 f7 L% `8 w U, w

- s2 h5 b. J" H$ P, h: n然后保存编译文件:5 l% }5 H5 L9 E" \* L; F
: N! \: p0 C2 @1 N2 e% g2 B- p. X
选择二进制方式保存:
2 J! i8 _2 w! C. n3 k7 w, |. p * _2 g% D1 N- K
" b# o4 ^! O7 B* a) n; X: K
用Hxd打开保存的二进制文件:
4 \9 q% g7 C* W/ f K' ]: t8 Z: V) u7 U- [, ~( f
S1 H# g0 x8 A5 ^
跳转到设置的程序开始$8100:
$ H( g ?! r1 ` h7 T& i . x+ g% f8 g j; x a2 y& m( m
5 z" x L4 h4 I9 ]5 a
- Q% b0 j$ s1 D% d
回到FCEUX,转到NES内存的$8100对应的ROM地址:
: @0 f2 l1 v6 g! F . y: N5 g+ ~; {9 W+ }

+ e y: h" M4 \+ A
9 ?7 y( [3 @8 t4 G+ j4 B
6 i" O) U+ P% l
8 A5 \. _' @: L2 m然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
7 i& @" ^% v2 V: k
' x+ G+ [. L; d 6 [( A4 q3 C4 f( ]. c3 X
1 C' Q, _- k, N; ]& R6 O/ P
然后转到NES内存的$8000对应的ROM地址:
) a b0 j C# q& }1 T
\; t% m$ r% @1 h, g6 l . L" K# L, ]4 M, W" c' j L! s7 y
H* K/ u3 N& g$ H: _ b L+ l6 r

- ^! l8 i: V: E% u6 @9 B3 I写上如下程序:
+ ?2 b; E. y9 s5 g, OA9 80 8D 01 A0 20 00 81 * G: y: o, w' R
LDA #$80
3 z4 `5 K/ i+ N) o: ?1 }4 I$ DSTA $A001 可写方式开启SRAM
* ~5 a) T- d5 S" q; X/ _- g& g' GJSR $8100 跳转到子程序$81002 [8 w/ \0 r5 q( I( Q. r
7 o# g/ X/ @3 W3 o z
然后把Hxd里被覆盖的程序复制过来粘贴在后面:4 Z0 o/ p, J7 f

) J0 q( e% x# w" l
) o J, p+ Q8 ]# N0 l
6 A- g2 ~* U# V* O末尾补上一个0x60:
0 j) E& Y' ^/ l, Y0 F% e+ TRTS 子程序返回
. ? q' k. Q# @% M5 F: O ! N9 j" Y' d0 e6 v
然后单击运行,ROM音乐响起,正常运行:8 b& n/ @" P) }( t1 i

k% |' Y2 @7 e & z$ |' n% V( f- F9 e7 I
然后转到NES地址$7000:
4 F7 Y' s4 w" H& u- B. _. M - p ]& r R9 E3 s8 S% J# s8 q
5 Q! d. ]- Y. |) T& O% g: R

/ Z. ]! X) w& s$ x
- x) ^7 o8 @4 g6 y/ ^' r ( p w8 `7 c5 ?/ ]
可以看到,$7000-7FFF都被复制了一片数据。
, u6 q8 f( s3 q; S2 k" y测试没有问题,然后保存文件: I: r$ y- X" m6 p8 d

% a+ n" k0 [! q" ]6 o7 e以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。+ t5 G4 B& J/ g7 w# n8 g5 z
后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。
, \; o0 C1 B5 ^, G2 @4 R |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|