|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑
% Q0 I$ Y5 b% d2 G, q+ c( H2 _% r
' T' ?7 E0 k5 R: P5 ~4 \[FC][SRAM扩容教程(Mapper 4为例)]
3 N. B. U& V0 c6 i- j- y
9 K7 d& f! n) D3 _3 J( ~3 K/ C时间:2017.4.28
9 g' \7 x+ c0 S. `作者:FlameCyclone
7 q. x9 _( e: X, Q1 e% x" S工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator: n2 Y) _7 m4 I5 [' ~& }1 G1 j& B: [
ROM:双截龙2(J).nes
z0 R" I: b9 z8 d# G) e适用:没有使用SRAM的ROM/ J. X" @ {8 U( a/ i7 c
1 O- V! `2 `, Z5 |% T& j/ y首先用Hxd打开ROM:/ A' i7 |, a7 o, R3 e- o! D- c: t" K

3 D5 B# g" b: c9 R X然后扩容:1 I( E" p4 X2 z4 S* j4 ?
7 y* C$ l) R1 X3 q
$ N$ F5 m' r% d- a
6 K8 C/ t* A- S- m" l$ m
3 ^% x% C. n4 g# }% K' ^* L3 y# y( v; J; k1 r, b
; }4 g& d3 I7 t1 w5 u- j' ^) B/ U6 c
0 C( q1 }# i2 Q7 P) b* d' U
/ r& `* R2 N+ L先看看任天堂产品系统文件对NES文件的说明:
& T0 A* p p7 i) C; `NES文件格式
* M* }: y4 [; E.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 b/ P7 A1 Y% |5 ?$ Y4 i
偏移 字节数 内容
( g+ j6 J' y. q- P0-3 4 字符串“NES^Z”用来识别.NES文件
' s0 y* m2 O3 k- y7 r9 {4 1 16kB ROM的数目
" w% G) ?! G2 I0 Q& @4 Q5 1 8kB VROM的数目 . `. k+ `4 o; ]7 I
6 1 D0:1=垂直镜像,0=水平镜像 $ S2 F" x- c% |) Y5 B
D1:1=有电池记忆,SRAM地址$6000-$7FFF ' k( k* l4 P% R- O- r% O+ M# e
D2:1=在$7000-$71FF有一个512字节的trainer
9 x6 H6 p1 r# {. {' q D3:1=4屏幕VRAM布局
( l t4 l M1 m7 H* H) g8 Q D4-D7:ROM Mapper的低4位 7 X1 {9 r0 @0 e& }! F, A h" q
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^)
1 p! A s* H. J. l+ v& ^ D4-D7:ROM Mapper的高4位
* f, Z- q, o$ q) d$ u# I! e8-F 8 保留,必须是0
0 Z2 C7 E8 E1 C$ I16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
( c/ N: y _+ F* F-EOF 8KxN VROM段, 升序排列
$ m h5 \& m. }
5 N) m4 k2 Z, R' W' u4 j, |- D然后知道这个ROM有0x08个PROM和0x10个VROM$ h |( E* A x6 E1 E w
接下来扩展PROM位0x10个:
* R6 d; m- P# l2 {% D3 O先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
* K1 y3 o* s5 N$ g% Q- W5 a3 j8 L : K t+ P/ m5 q8 c+ d, a* Q
由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:
% ?1 p$ a* Y+ v7 h最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。5 A8 ?" |* ]/ s6 {' [/ b* D: E
于是可以得到双截龙2的是:
6 a7 c/ I0 H2 d( m, Y3 k; L(8-1)x 0x4000 + 0x10 = 1C010。
- v4 D* {: B# `然后跳转到1C010:$ w0 e% [/ f& ^/ V

4 _5 v! j8 g* H9 h% q) x3 V
8 A- G* F* _9 u- V F2 t& v
3 M) \: C" D& u然后插入0x20000字节的FF:
7 E9 r( X1 Y; s; f/ A5 o$ z9 a" D
- S1 Z/ T& |' q- F! X$ U- U6 d" f + Z( [8 m5 P* M1 R1 o

1 q5 s. P* h3 P4 z然后保存:
' V9 O5 @1 p' ~& ` " X" p. D( u4 T# N

7 f7 [. ^7 D C. w3 S3 o5 d用FCEUX打开正常运行: ^* s$ x8 ~1 I& }
3 _& P0 _( O* K- q. t
查看文件信息:
f3 k, ~7 a* M5 |( r0 Y& L
1 C1 j3 M4 a+ F0 E6 F( [+ `
0 F% [' E, j9 U( Z接下来切页:
; s0 S# B6 l, m3 l# H先打开十六进制编辑器:, m; _$ H: X. u

j5 d% G/ ^5 K: x拉到滑块最后,看看重启中断( \7 e9 C Y0 E% y
中断地址 中断 优先权
% ?- a" B$ S+ ]3 W+ D7 f$FFFA NMI 中
$ x; `% M5 g' p t$FFFC RESET 高
3 r- p6 j6 k5 a) F0 A2 S: b% y$FFFE IRQ/BRK 低 " n, c8 y( K: g

3 |! {# l: h' J2 G" VRESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。 h! K. t' ~7 V! ?$ G0 q
由此可知双截龙2的RESET中断$FF65。
( T7 f4 F& ^8 t! s9 @接下来添加$FF65的执行断点:7 Q* _0 f8 D& C2 S0 }
打开调试器:
6 K2 c1 L: w* G# m8 J }; D+ x 0 u0 N$ }) W% H8 u# L$ N
添加$FF65的执行断点:
; h* P3 Z& ~( M' i7 ^" c' K3 c" ^
y3 s: Q7 S, O+ j9 n
* s7 |, ^, U! `: V6 L$ H$ M4 V# V# N7 q3 H l* ?8 x# X
单击确定:9 k' K( [: |0 _

8 a z+ |( u* o
+ ^4 f8 u' h Z1 C* F, R# _ T; O W A然后重启ROM:9 h# ?7 ^+ }* r; D5 G0 D
7 y# h9 |$ `% H' O
调试器此时弹出来:
. G8 z( ` H y8 e* O+ |
+ I3 r4 r4 y. z然后打开Hxd,写一段mapper 4的切换bank程序:3 T0 Q. W. q$ l
先看看mapper 4的说明文档:( ?1 e: Z! t( |) U1 x. ]1 ~5 ~
Mapper 4
2 G/ j* e+ s) t5 c& i% H( D" w
2 b& r) t. j3 ~$8000: 模式号
( T8 H% K0 {) K& o 位D0-D2:
. D. R: r" ?- i" W 0:选择2KB的VROM存储体映射到PPU的$0000
" s! b( Q8 {& S! O$ t0 {0 m4 A 1:选择2KB的VROM存储体映射到PPU的$0800
( p8 I5 o% r y$ ?! \ 2:选择1KB的VROM存储体映射到PPU的$10003 m. ?5 y; h0 `" e# M9 h# A/ d
3:选择1KB的VROM存储体映射到PPU的$1400
' ~, S8 O% U6 R/ a, D( }0 l 4:选择1KB的VROM存储体映射到PPU的$1800
# y: f1 c4 G; i* H( }3 `9 f! W" m 5:选择1KB的VROM存储体映射到PPU的$1C00
7 v. z- a/ m0 ]% X( T2 h0 l 6:选择8KB的ROM存储体映射到$8000
, P- w# n' a0 d' ~2 ] s# m# G 7:选择8KB的ROM存储体映射到$A000
" A+ D$ S; ?3 V* D 位D6:1 U `- ]3 ^3 F2 v; C: v' Y4 M
0:允许擦写$8000和$A000& C' Q* H* ^. N6 p# y
1:允许擦写$A000和$C000
& Y7 p, @% E; W+ r 位D7:
# V/ s3 U1 H+ p$ h4 R 0:模式号D0-D2使用普通地址
7 K9 B( F/ Z9 r+ p 1:模式号D0-D2地址异或$1000# ^" ]! m% U; E
9 \5 D/ `$ b$ |$8001: 模式页面号3 h7 t+ Z9 ^0 O2 m+ n- K- o9 z
写入一个数(00-07),切换存储体到对应地址, g" J4 N4 j4 E2 a3 D* ]7 ?" e/ ]$ h; m& N
, I; p9 [2 b9 V) f3 D. N
$A000: 镜像选择. n: g. j0 [$ J0 G1 L/ d2 x7 a' s
0:垂直镜像
3 _% {5 f8 R- w1 S 1:水平镜像
) z, |; Z) w( A; p) K/ O& K" l, d1 e7 {
$A001: SaveRAM 切换
3 }+ S0 S1 [% B6 L 0:禁用$6000-$7FFF" f8 P5 n" t* J' t4 q9 q! b$ b
1:启用$6000-$7FFF0 ]" l5 p0 q" k2 x, W
" @. r0 ? P8 T" V6 X: b$C000: IRQ计数器
) k5 v s0 C" V' a IRQ计数器的值存储在此处
5 b) h8 q* } i7 a/ ]' H8 _% D9 z _* Z2 J" c7 h1 c
$C001: IRQ暂存器) u) j, i& D; |4 u, |) U
IRQ暂存器的值存储在此处. Q# C6 ^. g+ d
+ E- D* _5 X- Z- m. D+ H' T0 H6 ?$E000: IRQ控制计数器0
5 Q1 I( a8 O( W9 u 向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
" K/ B' I+ f: u/ ?6 m- j
8 a, Z) I, _6 W; H$E001: IRQ控制计数器1
( Q* R& O& S: {0 k& p7 u 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)0 {1 j! Z2 e/ l o
1 G/ x: `% A& N/ l M" f那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:9 K; P, X: [7 g9 h& c" e7 K) W: d7 F1 }
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68
) ^: |' _8 C$ c( {PHA 累加器A入栈
0 _) r0 b, Q8 T2 ]9 |1 nLDA #$06 设置切bank地址为$8000-$9FFF: b! s0 N; [% W
STA $8000( i0 U* ~ ^* S3 y
LDA #$0E 将第0x0E号bank切到$8000-$9FFF8 U) k& Z( I, u; ^( B) r9 W
STA $8001
( d- E, B: k) J) k) gJSR $8000 跳转到子程序$8000* v5 f4 F. p* a1 U
PLA 累加器A出栈
- G6 D( r0 @$ E! J ~1 g* K( B8 I$ ]& A |& _
为何切换的bank号是0x0E呢?
3 g; Z* S9 E7 ?因为在扩容ROM后,文件PROM结构为:
$ j7 T( K0 l, H2 J* Q8 t+ Y原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM% Y6 S0 s* y* O9 ?4 B* ~
- @% g$ D; h6 V5 ~8 l2 k4 E, vMapper 4切bank一次是切8KB,那么文件结构就是:+ V) r: r6 F4 K
原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
4 q# ]) ^$ _2 a因此选择扩容的第一个空白bank就是0x0E号bank。. M8 d! ]6 }! i' ?. r8 q

$ m* ?. ^2 [$ ]* t9 n- Y3 T) W5 i4 K# e$ j5 x6 h: F, C3 a
然后去调试器找RESET中断中可以放下切页程序的地方:: n( W$ s/ S3 _" h2 o: @6 O
首先长度要小于等于自己写的切页程序。. G0 o. ~$ b8 t0 t @" a6 j
可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)9 L% ]6 e) }- [0 T
& X& L. u U$ Z2 Q+ C
然后跳转到$FF7C:+ S; p0 A4 n" Q* X- a+ u8 I
" y4 y& L. q2 A! B2 |* T( X* M

2 b& p% f, N1 K/ J
* }9 u7 J( v/ x, _
- ?2 D- l/ \$ g' G7 _复制下可以被替换用于写切页的程序:4 [5 U. {& p3 A7 {
先选中,再复制:
! d3 [9 \6 o5 d6 H/ H1 L' E 3 e( b+ g6 O2 v/ o4 s
^% q) M+ X& T4 k4 x
在Hxd中新建一个文件,把复制的数据粘贴上去:, h1 ^8 K7 f7 `
" f9 [8 A8 B+ N5 ]* Z6 J5 m$ V

6 j }# D' X/ ]* G( P
- s' b2 _' T9 S! y8 K' V( K0 c$ [5 I$ ]$ U
然后回到十六进制模拟器:
/ {5 D5 `# q2 [; H' o6 u, O( f9 X转到$FF7C对应的ROM地址:
/ r' {' x7 o B; x, D) O: L3 w
3 K" T( n i; Y* Q
$ u* v9 M9 M8 Y9 C/ o" M然后复制Hxd的切页程序,粘贴到这里:
8 k$ w, V& ]7 H+ _+ Q, [$ |" d3 ` # X, L5 B& C& }8 p; n' p- `
0 V: F0 U6 N/ r- |+ d& i7 z

& X4 |5 u, F6 c/ H没有覆盖的用EA覆盖:
/ J- P: m5 `4 N, Q% L7 }4 n3 h ! n: P; H$ F6 I5 U; I
打开调试器可以看到变化:8 V* ] S2 K6 e& B1 h
6 H3 Q u( f# U R1 g. b
然后添加$FF7C执行断点:
& @" l; v5 C. y [8 q4 N5 V % d. H+ D' F: ?( o5 v: e6 v2 ?

3 T! _! ?9 D% w ; q. A& G. |2 r( f3 W4 ~+ D
单击运行:( C, x, A1 I) t& F- b5 C
然后程序在$FF7C这里停下来了。 m0 y& z, u+ {/ u
7 M* B3 D' G1 l3 q( [) T
然后单击单步进入慢慢跟踪,直到跳转到$8000:
( |* t4 z: O' b
7 t; K5 y$ Y5 |/ N4 i然后打开6502_Simulator:
$ o0 g5 {' a5 y) j7 Z * `5 a& s ?: Q
再打开我写的数据搬移程序:
% m4 `# P. l: |
5 J1 `' o2 e& A0 B & y1 \9 C5 J2 T0 ^+ _
然后修改对应的数据:
2 U. Q1 k2 q/ A! h! T程序开始地址:修改比如$8100就修改为 .ORG $8100
; E) M; E5 e$ ]4 }" {4 m复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
3 M9 _4 a6 k0 n3 C/ q$ V从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82
# Q. B3 ]# M8 q- r2 Z想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$911 G9 p6 Q: K) Y/ O( w% s3 W
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。$ l8 n" B& @6 E7 e
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。- h- G$ h; E- B* o5 N/ i5 U: L
中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。
! L2 R& t5 j9 ], B( }9 `# d% c% l后面的不用管。
1 F4 c# h) U2 x6 a2 I3 Z2 |/ l6 `7 a0 c: _, u
设置完数据后单击编译:
9 M( N( L% `+ ]
: A3 ^+ p0 J$ a1 e然后保存编译文件:) \" G& B0 a3 E4 O% r) U

3 t, R0 @7 N& j选择二进制方式保存:
4 v: e8 X: ?3 h( ]1 ?
2 Y/ e+ T6 }6 j& @* K
! c3 C- O8 ?7 [用Hxd打开保存的二进制文件:
B: R" t1 N0 T6 `: z ' P+ S w0 }, ?
( v. i/ j3 O _
跳转到设置的程序开始$8100:* }: Q" K# v, D' O

4 `7 @8 r# H% z8 w
+ ~$ O Y# j; S, ?; T
0 H/ u) M: N1 l4 l0 ]回到FCEUX,转到NES内存的$8100对应的ROM地址:
& u G! P- F5 e; T 1 l$ f, O. }% h

+ w+ b3 \1 _: K1 J7 D
/ u9 ]4 }7 [, h4 w- n
& ]* o. y( }; f4 u8 I" K
5 `. [1 W o1 W. [' B, w$ W) e0 t然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:5 f6 n1 `* r6 m$ l

! o f" X( V4 `2 s1 O7 ~
. u3 g/ U) l6 q) h9 }5 v 5 a. A* f/ o: M
然后转到NES内存的$8000对应的ROM地址:
l& Z: O3 }& k& M9 M& E
" q, `. x* d# C. B. F I & {" K, s# T _+ c" x( @% J

0 t8 r( k! r# b# `$ Q $ U4 ?9 @1 L9 Q q# @
写上如下程序:. q* N J& n5 H
A9 80 8D 01 A0 20 00 81
; G% h( w6 H! E, T6 C& U" }LDA #$803 W* h9 J4 n/ f) l2 Q$ I' L
STA $A001 可写方式开启SRAM9 ]) w* J/ k- @
JSR $8100 跳转到子程序$8100
5 E" ?7 q& V% D/ S" `- v
+ [% ^+ k" ^+ T t" E7 O然后把Hxd里被覆盖的程序复制过来粘贴在后面:7 U6 b( A& F0 `) o, U6 \

2 K& e; n9 t- Q6 ^' P: J4 T
- ]1 h5 M" N% z0 [ ( i& s. [; \7 z% |8 E) h3 |' I4 _
末尾补上一个0x60:7 `; x: s, Z/ j; X6 w5 ]
RTS 子程序返回
$ N- L! @9 {0 }! Z% B# t
% Y; f% a4 D" D然后单击运行,ROM音乐响起,正常运行:0 `' ~6 W3 @ ^6 {! m8 e( V4 I" J

( h2 G3 J0 e, I5 @
) A) j$ h, ?7 y9 x4 \4 }# I" P然后转到NES地址$7000:- j3 d' L6 P: ]0 @

6 f( f1 C" d* s& d+ T- ~( k 9 H$ L: B/ Z& c" I
. ]# ~( u. \) u5 k

7 P# H, Y! L0 I; B" z+ k : D. E4 M' y! i6 c
可以看到,$7000-7FFF都被复制了一片数据。
2 p, t3 d" `1 e9 J9 i8 E1 Z$ f测试没有问题,然后保存文件:) I/ _7 }: a7 A& E( l ?& O

F8 {5 A! g' d( l以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。
8 A/ r6 l9 Z: b) k I后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。
& r; C) c# I. R: y" Q. p5 V( O |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|