|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 3 k% h& ]& @3 p$ C" {
0 V* T+ y. n- b: e$ A8 O, T[FC][SRAM扩容教程(Mapper 4为例)]' O. G* t- u( S/ K% t6 h
" p. Y2 C/ Q4 M8 M& _- L, z时间:2017.4.28% H- s$ u, I/ y8 I- V
作者:FlameCyclone
% n5 e. K) J4 ^: i! X% P- |+ y工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
. H! `+ ]& a% MROM:双截龙2(J).nes% D# S- W* h' |- g% V
适用:没有使用SRAM的ROM
8 R: P q# x! ~- w1 ]4 S
" K# R8 w9 t R6 {0 U5 D% T首先用Hxd打开ROM:/ }" e$ a1 l5 S4 R9 @
& k) p0 d1 b6 ?; H
然后扩容:
; v0 X6 T" U, Y( u* |+ a% w* [2 G6 o) k3 U4 ^: Y( D
# x8 R! U+ w. z* t" }
6 K% D' P, m5 n. h- Y4 `6 a7 |7 u9 d! j. U! ^1 i
3 U8 Q" _6 H* P0 f/ L
$ W& K& `: l0 [- B, n) N& }
0 ]9 O, w6 m% A1 b8 a
7 a6 ^* H0 o: j: J4 z/ J
" S6 _) A l$ D5 t/ G S2 T1 H* H先看看任天堂产品系统文件对NES文件的说明:
7 Q! k9 A% y' C* { n/ Q. ^- qNES文件格式& l8 D( z: J6 A& Q+ |( k+ e
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 ' U3 |- t* q+ a! i) p; T
偏移 字节数 内容 5 @1 U' r3 J6 W6 G& L& }
0-3 4 字符串“NES^Z”用来识别.NES文件 $ G! n& P% u1 t) i2 v2 ^) J5 K0 T! M& H* c
4 1 16kB ROM的数目 * y' h1 i: y: i6 o$ P, l
5 1 8kB VROM的数目 ! j- X7 m8 ^! T4 N4 |
6 1 D0:1=垂直镜像,0=水平镜像 - G) N8 _' E4 }$ @" Z) o
D1:1=有电池记忆,SRAM地址$6000-$7FFF
) ^9 }3 i4 O w; }# Z8 _. T D2:1=在$7000-$71FF有一个512字节的trainer * p E, N5 T) {3 v& e8 _
D3:1=4屏幕VRAM布局 + R B) @+ d6 I, K% A) Z! H7 ?
D4-D7:ROM Mapper的低4位 * @( I' o+ H2 {. P2 q
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^)
" l F/ s' b$ N' u& y! e D4-D7:ROM Mapper的高4位
9 G- I4 S2 X/ B0 @2 D, |# v8-F 8 保留,必须是0
6 u/ |7 G+ s, ?) E4 u' p16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
: [1 m. D0 e& F) A-EOF 8KxN VROM段, 升序排列 9 S F' N7 z, n! P7 H; h7 C3 Y
7 l( @1 b% y0 @1 D8 w5 ~; t" B然后知道这个ROM有0x08个PROM和0x10个VROM1 m5 X t) P1 ]6 ^
接下来扩展PROM位0x10个:' s& Y" Y& I- U1 m9 q5 G( |9 L; b
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
1 O8 f% o! t0 O* ^: {* n % Q( n" P! X! i0 q. n) Y
由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:
% C9 X( `/ B- T5 o- L最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
3 K. A h( g; [. b于是可以得到双截龙2的是:0 b: q* {3 Z9 n; n/ N- B5 q
(8-1)x 0x4000 + 0x10 = 1C010。$ c8 o9 ~: K: `
然后跳转到1C010:, j' i3 } u! K: z5 w) F
* x& x2 E- H/ L5 r0 g5 w; J4 T

- [8 T# h# }5 P( K1 k( [! O
. N1 q& m/ ?! B7 |9 ?然后插入0x20000字节的FF:8 w+ |9 l$ {9 p* O) k; X

+ Q! z* k, E7 u+ t# X( u 3 I! K* m9 S& g$ P5 u! _
' K+ a, F' r! ]# _$ g( Q
然后保存:% d! o. r; v( {- {
, X7 G8 j0 x: R; C9 q, i; S# V; p

9 v# t" n# x! H: ^7 R5 L用FCEUX打开正常运行:
- a5 E# \& H" k- [. _- { 8 U4 y1 o4 y9 Z5 h
查看文件信息:
% D$ c# q/ c% J! T+ ?& x - u( D* x) M ~3 D

( O/ Y" G) ?+ }4 K& G接下来切页:
9 B" D0 {* ?& z5 y, I; f, A先打开十六进制编辑器:& z2 K$ y/ z: z7 v) T

/ ]1 d% t$ z" H* [' r拉到滑块最后,看看重启中断
5 A" n* L' B3 e中断地址 中断 优先权
- t: W! H" k: o0 Y$FFFA NMI 中
F) i! l, o& c. x1 S) s! q' P5 W$FFFC RESET 高
, J* {# W" G3 n' L1 {; [$FFFE IRQ/BRK 低
p$ j7 G4 d, ]* n& z. i s
. O4 n0 ?5 v) {( lRESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。- X0 P" {" X+ _' _- ]: U7 L" ]$ b& v
由此可知双截龙2的RESET中断$FF65。4 [% O8 I% F( c7 m1 |( I/ [7 R6 ]/ c
接下来添加$FF65的执行断点:. z* J0 w/ c* y4 Q- j$ W
打开调试器:
4 h; t$ L: G, l7 B, b2 D 0 ~; L: t+ x( {! c# K: a5 `
添加$FF65的执行断点:
{1 o7 n8 n" G3 O/ D1 Z
6 Y( [. p8 P( S- Z; A
+ b2 ^6 D# Z* t% L) v& F9 X j' c+ h8 x& `/ B" h+ p' n- Z
单击确定:# n0 f$ x+ q: Y
0 [1 Y" U E! `/ w4 A2 M& S* ?
; ?" @6 o* X* K- B) W然后重启ROM:$ u7 d" }& M8 z& ?
$ Y# d/ O3 u3 [( Z
调试器此时弹出来:0 H: X) T! c0 R9 l) T7 j8 Y* L: P
# @+ Z9 J* x+ R; F) k+ N
然后打开Hxd,写一段mapper 4的切换bank程序:5 L- \+ r4 s; U0 L* b8 D. W
先看看mapper 4的说明文档:: A9 x4 m: |& A+ u8 h' ~/ P1 w
Mapper 4
) Y" X d7 _/ e7 d" X t1 K
7 P' x$ ?/ q2 _! V' N0 H& @( V0 r; ]$8000: 模式号
9 S7 w: a; H# Q7 B0 R, `, m 位D0-D2:
* L1 g9 d) ~# X 0:选择2KB的VROM存储体映射到PPU的$0000' U r- W$ H0 c! Z
1:选择2KB的VROM存储体映射到PPU的$08002 P+ Q/ w/ m7 a9 \- M
2:选择1KB的VROM存储体映射到PPU的$1000
# B$ S! D. c! r& i 3:选择1KB的VROM存储体映射到PPU的$1400' h0 K8 `8 x- j* k9 s; W
4:选择1KB的VROM存储体映射到PPU的$1800
6 n- X0 O7 ]. T9 L0 L/ ` 5:选择1KB的VROM存储体映射到PPU的$1C005 `0 O. C' }+ C; D; A
6:选择8KB的ROM存储体映射到$8000
" A/ J" X8 t9 S& r/ F1 G! | {& L3 l 7:选择8KB的ROM存储体映射到$A000
: q2 L3 q* l, _" h7 I' m; D& O1 `. r 位D6:9 J/ ?/ H- [' g6 S
0:允许擦写$8000和$A0009 C, w/ E7 e, O I0 J: H" Q
1:允许擦写$A000和$C000# I7 \5 m3 H5 N$ m7 F9 r- d. V: P' J% V
位D7:
$ n! x; v. H4 x; H/ @% w 0:模式号D0-D2使用普通地址
( p3 M7 g# M+ R7 p2 k 1:模式号D0-D2地址异或$1000# L4 U" \' {1 K/ G
$ {! s# Y" L$ K
$8001: 模式页面号! C. R/ [8 L" G! r* c& H
写入一个数(00-07),切换存储体到对应地址
9 r# |; J8 i Y. P# e# a
) v' Y% n" M4 v. W$A000: 镜像选择
' o1 h5 d0 S/ S$ B3 D$ p2 T# P' v, O 0:垂直镜像
, h5 h/ h% s0 d 1:水平镜像 Z9 v; w$ X; R$ h
* ` e; C" V6 X
$A001: SaveRAM 切换& O+ s8 B7 `2 u$ S% f u
0:禁用$6000-$7FFF
' ?, n" s4 k& k& K 1:启用$6000-$7FFF3 s" X9 P; p8 A) S3 t
! t$ ~; p! X/ S% W
$C000: IRQ计数器" p( q4 i$ K1 s7 ?! n
IRQ计数器的值存储在此处
8 B4 e: {+ q" E; w3 c) d! n8 g' n/ Y7 M9 y
$C001: IRQ暂存器
% S4 [+ ?5 ~" c6 ] IRQ暂存器的值存储在此处# D9 A9 r8 {3 r1 b6 m% _
' a2 @$ B3 p* W& ^ j
$E000: IRQ控制计数器0: r# i; [7 }$ B
向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ4 T- l" m2 C6 L5 Y8 B9 Z! I$ h5 Z
, g+ g; ]) o: O3 n; \$E001: IRQ控制计数器1( y: |; C2 n8 q$ z: e
向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)
4 U7 I; K+ o- A5 e. U8 W$ c# t6 _' P# ~! e0 f8 ]' m
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:
% }/ `: E9 p8 V: ]. C F9 [48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 688 b: |0 `$ _4 j$ Q) \5 k% j
PHA 累加器A入栈
7 S1 x4 A" b# R+ uLDA #$06 设置切bank地址为$8000-$9FFF% B& J0 W4 z9 v% a! @( \0 O3 U
STA $80006 s9 S! j" U7 x& q, a
LDA #$0E 将第0x0E号bank切到$8000-$9FFF4 Y p, d/ \* u
STA $8001. q8 p, u0 b, c7 b( y# Z
JSR $8000 跳转到子程序$8000
1 K ~ g# u) ^5 T( EPLA 累加器A出栈( A5 ]6 F0 K2 p& q4 I7 O
" l( l, R( h7 \
为何切换的bank号是0x0E呢?
5 z" Z' ?5 j8 ]4 N; T因为在扩容ROM后,文件PROM结构为:
6 N4 c+ i! P" U3 b) t原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM7 n _+ ]+ Z% E3 j6 R- H ~
) Y1 [7 _% d) f: jMapper 4切bank一次是切8KB,那么文件结构就是:
: N Y `: z% A5 \) Z; O原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)& d) @( D' b% e# d4 |$ c0 [
因此选择扩容的第一个空白bank就是0x0E号bank。+ X; x7 C4 S2 y' e8 R% ~

. [4 }; [6 q% w# R+ X6 ]# s5 m6 J( P5 M5 e7 Z
然后去调试器找RESET中断中可以放下切页程序的地方:
6 a& ^* z& }, `. W! s8 e2 `9 a) G首先长度要小于等于自己写的切页程序。
! Z7 h0 s; [" Q. @, E可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
* ]! L4 H x* q 4 k( n4 ^, r% G: P
然后跳转到$FF7C:" ^ I0 F- q% S( V0 ~' g. {
# {* V* P! @: `3 V& w7 z

7 S+ h1 @: A$ g0 K ( h$ t) E' \( g
4 t1 S+ F* ]& O5 \
复制下可以被替换用于写切页的程序:2 w/ h* U/ R0 n7 Q
先选中,再复制:" | _5 E( x" D8 Q \% y+ L

1 i# f6 {4 F9 |3 i$ h $ G4 y6 A- O: Y8 d+ [) E0 B) \
在Hxd中新建一个文件,把复制的数据粘贴上去:: `7 j s/ R7 A" _' h8 l

" T) H R& X! A7 l
/ ^' n4 F8 C; S V/ _" F1 C9 E & f) p% X3 L7 x0 u, ]
9 w, O( ^2 ~2 y B- h* h然后回到十六进制模拟器:
/ S1 r: d2 I* \+ Z3 d转到$FF7C对应的ROM地址:
' `! G7 V" b3 t* U ; @- X7 I1 W( u7 Q* z. R
1 ~, l% z# w+ {9 }- r0 [7 g* D
然后复制Hxd的切页程序,粘贴到这里:5 a) d3 U2 T# z/ L( ?8 R; ~* Y
6 |8 f* |* O, H
$ S1 ^9 `* j a; l
- c, j' S- Q F* c7 j+ a6 h5 X) S
没有覆盖的用EA覆盖:/ _0 {) Z7 X8 [0 R7 R6 M8 Z

8 z! r1 z8 e- x2 L5 J b打开调试器可以看到变化:' ?# e h! y7 K% p! `. E
- e/ G4 {+ n( t f9 t! Y
然后添加$FF7C执行断点:
4 K2 ?: |* [6 Y9 B+ ^ 0 R) ^6 d2 D3 R/ m. |; u
2 z4 l- J" m, y: `$ l& Q

5 a1 u- D) s3 U) ?) [7 Z8 F: R+ p单击运行:
6 w6 Y3 i& l9 Q: v* Q1 A) j- b然后程序在$FF7C这里停下来了。: X3 A* y- |% x+ X+ j) c" I
. k5 o+ U8 W/ g" S6 _# V7 I& D2 H; }
然后单击单步进入慢慢跟踪,直到跳转到$8000:
/ m9 e; M, B: ^" V* {) ~& f6 p, d ; o9 ?) R F. }* U9 S
然后打开6502_Simulator:
+ {6 w6 Z3 p8 O0 j" ~ % c$ g5 `8 i' N! G" p) F8 m/ F5 {
再打开我写的数据搬移程序:2 |5 w- b e1 o. t- d8 t
) r2 V, N; a; c( r' x

3 A- m5 z+ O: Y# m! \& }然后修改对应的数据:
" Y% F% a; M" J7 g7 _; D程序开始地址:修改比如$8100就修改为 .ORG $8100; ~8 N2 J, _+ o7 x
复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70: r; Q0 _/ f0 h+ N6 I9 }/ x
从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82 Z2 J; D2 x A R; C# c
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$916 e) N, u& C" G* c# J: [- Z
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。
) K' K2 C( T" U) f6 v s如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。
: z: `! P3 x8 S7 d F中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。3 n' C3 e' b( s0 C
后面的不用管。' ^# F) } h1 v' ~( ~& v
4 k0 } W/ R) W7 Q M2 T
设置完数据后单击编译:- k# f: C& G2 R$ b( K

3 Q' ]- c) P2 b l5 p! b然后保存编译文件:
: z& L9 R9 X8 n) ~9 t
& t# k% J# E+ P; i; |! I选择二进制方式保存:
5 K0 j- c; g1 W5 \/ K 0 z% N6 z0 V8 J" H8 X4 e3 v
+ J& E4 ^3 J% s. _
用Hxd打开保存的二进制文件:
6 ~: x# o- j. c4 G & z M# P, ?1 n" w; h7 v
4 e$ Z! S2 E0 L( ^$ g q
跳转到设置的程序开始$8100:" w; ]1 i3 S" ?. V% Q

s( _' W! T, K1 W+ x u $ x0 x) w' [$ ]( f5 `* c

( W% }# t" h" |$ p4 [, C; Y回到FCEUX,转到NES内存的$8100对应的ROM地址:
' H7 B0 N9 y" n& o, Q
: R; p# \, c; U A1 p/ H 2 c! i0 N0 e* {1 I7 g
0 A' e1 p# j. E6 ^" M. j6 g

2 o, A6 l8 j8 ^! m3 Q& w1 h
" E: t% P5 Q) D+ x5 \8 }然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
9 Z n( d, \) R2 K& s
6 a- O$ T0 g4 y
' g9 b% Q8 {# M& K0 a( g
: G9 {6 e) k. A; V& ]# y然后转到NES内存的$8000对应的ROM地址:
# N3 U9 j9 X# ]8 k0 r% [- p; G 2 d9 @) s A$ C4 W

# h2 [( D& @4 V
$ b& ~. C4 [0 k2 X- n! }$ ]% n ! j6 N8 o# h* V ~; M' I
写上如下程序:9 b5 \5 h* c# A# P+ y
A9 80 8D 01 A0 20 00 81
# v5 U8 ?% l8 OLDA #$80
( V1 _4 o8 N4 LSTA $A001 可写方式开启SRAM& P K8 M, ]/ \" k
JSR $8100 跳转到子程序$8100) `0 O( J2 R* D7 o
2 h$ Y& J/ Q9 T2 |7 ~
然后把Hxd里被覆盖的程序复制过来粘贴在后面:
: {* x" q) f: O5 Q , `1 p# b( k6 z3 z' w" m1 a8 T
9 e* p3 I, W9 ]2 r
. L8 K. H1 S" w8 ^
末尾补上一个0x60:
q5 l5 s3 u) ^. c6 LRTS 子程序返回
4 N7 ~0 ^& g1 ~4 `# B
+ y; E$ V' V' [6 W& U% f }然后单击运行,ROM音乐响起,正常运行: U4 Z" r9 r6 R9 Z/ D7 R
9 f+ n, k% T" z3 t5 u- Z+ j+ X

4 i8 O$ @. p" h然后转到NES地址$7000:4 o; O3 |- ~; v g! y" \0 ]
$ w8 U7 [8 O5 S7 s

6 N/ r' z! X6 f/ z% c
& N: j0 U( d! o) t ' T5 r% l# b8 X" _
: C% i! o& t' P( q: T
可以看到,$7000-7FFF都被复制了一片数据。% Z6 m/ t0 S l2 X
测试没有问题,然后保存文件:
, \4 Q# I% S9 D" b+ p& j- Y ^ - q7 v. q; |( r$ f7 g6 K
以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。
/ O( W q' V Z- m后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。
/ |# k. p. X5 K% }" Z6 @ |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|