|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 5 N" _" B, M' Y! W3 @5 d" F! Y
+ C% D2 q4 e: d. u
[FC][SRAM扩容教程(Mapper 4为例)], h7 \1 A0 G2 P5 L) z7 S1 \
( g9 Y) |3 R! N8 O" _$ o时间:2017.4.28
6 @$ g+ p2 Y( B# s. ~6 t作者:FlameCyclone1 |" _9 `5 |8 d# E# s2 J
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator+ S4 ]' G4 U, z
ROM:双截龙2(J).nes4 O4 H/ T3 G d+ d
适用:没有使用SRAM的ROM
' G' X1 k# c1 o, r7 X8 J4 B: T; _/ ?) }* E
首先用Hxd打开ROM:
* Y. A, Y6 I) K
! s- S+ n" m) _然后扩容:
& E* N7 M& t% U+ s4 s
4 p$ O3 r) P- r, H* A1 @
5 v4 H9 u5 k; z, \$ G% Y+ T( J+ F2 }
& {4 Q3 ]. S I; b- q& g& K' {- K7 _7 x; H5 q+ d) n- ^
0 [' v( a/ h4 Q/ w: A5 r8 H% B3 l7 o! O% ?5 F
5 q5 {' V U% I4 W' y
$ h! k9 d8 e# `7 q! X- b* d8 R先看看任天堂产品系统文件对NES文件的说明:
8 T4 G0 D9 ^1 V L! s1 VNES文件格式$ p- q* a" w1 X, R
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 6 a9 Y+ e I% D5 [, x A
偏移 字节数 内容
: q3 k/ U% `" m2 k' a' x0-3 4 字符串“NES^Z”用来识别.NES文件 , ~, ^! l) [* c% J* b$ @& B
4 1 16kB ROM的数目 5 |+ B) Y8 Q3 L1 C: ], u6 r
5 1 8kB VROM的数目
, l6 A3 K+ G* ?7 L6 1 D0:1=垂直镜像,0=水平镜像
" B( Z9 z0 b6 n D1:1=有电池记忆,SRAM地址$6000-$7FFF
T4 b9 w0 z- {6 M- l# K1 q D2:1=在$7000-$71FF有一个512字节的trainer
9 s4 ]: u7 X' S) V7 Q& @ D3:1=4屏幕VRAM布局 + R8 |+ U! g5 ?# d5 U' S
D4-D7:ROM Mapper的低4位 . B' t ^3 u8 M( \
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^)
& t' q" R4 d, X: E9 W9 B1 E D4-D7:ROM Mapper的高4位 " G8 g4 h6 U( M3 N4 k
8-F 8 保留,必须是0
4 @! o% C- \1 `. R: _16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
" m+ S; x, c1 L8 T: V+ ?3 J-EOF 8KxN VROM段, 升序排列 3 q2 R2 Q5 K, \
. h, T/ H/ }$ I4 W- H1 R8 A然后知道这个ROM有0x08个PROM和0x10个VROM+ _1 f S* N& ?+ G, g0 \1 G+ Q
接下来扩展PROM位0x10个:
. y. D" D2 L* g0 A2 P+ G9 C4 Z' V先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):2 R3 S4 Z6 ^; ?. n
( I) L: ]. G7 M: Z6 V% T+ R- J
由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:
: A" w9 ]. O& @7 o+ G) w1 A最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。( a( I- u4 \/ V; j1 T- d5 T+ K* C
于是可以得到双截龙2的是:
6 U2 {6 `/ o5 o(8-1)x 0x4000 + 0x10 = 1C010。. ?4 k! S8 ?- a' H
然后跳转到1C010:
! X# \5 z) y' i $ R5 M2 X) E7 H' `( t
& a- k! b( I' U {6 K# j# w6 ^

! v8 B: B. o+ [8 z& ^8 R然后插入0x20000字节的FF:
: E5 [- [% ]4 e3 f' H ) c4 ^$ H" x6 S6 r
+ [! k/ J/ K2 b# W8 i6 t# w' ~. D

: U& j w) v1 |+ c2 Z5 g! Q然后保存:: b' C: S4 h' o/ i; }+ R% _ F& A
! E9 y3 ]! z" c

K. d; v! Z" |用FCEUX打开正常运行:
. B, y- _3 g* N, G2 O: }# _
2 ?0 a. z% ]) v+ [查看文件信息:# D2 K) g( ~0 l5 H9 Y& M
( s; [5 I5 Z8 P% Z, x
9 g) m$ L0 @8 Z" i- P
接下来切页:( N# }2 c9 N( g* r+ l* B
先打开十六进制编辑器:
B* q! c8 `$ I2 z/ j( U
3 w8 _* X8 x7 z! j拉到滑块最后,看看重启中断
2 Y3 ~; W; B/ q& t/ a中断地址 中断 优先权
+ {7 z0 |" u' y5 b! h) l: R% a* z1 g$FFFA NMI 中
# F) z" s$ S) x( G) F$FFFC RESET 高 ( \2 w4 J) O) O9 C" R
$FFFE IRQ/BRK 低 % O) ]2 Z" C- ~6 N* S

) E0 w* t" c" G; K, S5 F9 C% ARESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。( {1 Y9 L( I( x7 }! m8 a
由此可知双截龙2的RESET中断$FF65。' m! a5 F! @& B( I/ j* U
接下来添加$FF65的执行断点:6 @" _/ T( H; L/ t
打开调试器:
+ x3 p, F9 T6 r* K ) G. ]& N" a% n% C& l2 x: u2 f
添加$FF65的执行断点:
- u- N3 a* o0 T D) C1 p
. S! ^" h5 V" C
" C! b- `' e! b, @) V! |8 R6 M$ @$ V) K
单击确定:' H' n9 t( u% W# t v9 N4 M s v

# Q% P9 r: A4 V, X2 P" P- O3 O% j; j; d
然后重启ROM:
) l* [- V1 C6 y- e! N 3 a4 t1 {& T* F0 h* f
调试器此时弹出来:1 g; T5 R: h5 g2 [% ]) J4 F

, I" S9 k% l# ?# T! f" F/ M然后打开Hxd,写一段mapper 4的切换bank程序:4 P. P' y1 _& U: b9 g3 U
先看看mapper 4的说明文档:
$ E3 ^! Q% C5 }- FMapper 4
0 d5 L" g9 k/ p9 d% {
& Z% y6 r( d* ?( J: Y$8000: 模式号
4 _# D; t: e, Y6 V- D 位D0-D2:$ R, |/ g# E* j% Y8 U/ B2 B
0:选择2KB的VROM存储体映射到PPU的$0000
. ^3 z! B1 h$ i 1:选择2KB的VROM存储体映射到PPU的$0800
f" {% u4 T* _5 X0 n# d/ I& ~ 2:选择1KB的VROM存储体映射到PPU的$1000
: c! n7 E4 ?; o/ L) s 3:选择1KB的VROM存储体映射到PPU的$1400
5 N W) q) T9 \3 q- s& { 4:选择1KB的VROM存储体映射到PPU的$1800
: t' k: g# R4 I; K" |/ Q 5:选择1KB的VROM存储体映射到PPU的$1C003 a# g+ W! t' \ c
6:选择8KB的ROM存储体映射到$80009 g# d; @1 z; C
7:选择8KB的ROM存储体映射到$A000
) |" R P, h' o! I O! X 位D6:6 B' x, ]; T6 J- ?
0:允许擦写$8000和$A000
$ B4 T$ @; j$ \% L& F 1:允许擦写$A000和$C000
f, Q) ~4 x4 ~$ ?9 W 位D7:
! R" {5 X2 J4 s* l- N2 Q4 s 0:模式号D0-D2使用普通地址4 s7 {$ d" E# }* f+ c2 O& d
1:模式号D0-D2地址异或$1000
$ S+ c0 }2 j" L) L/ q7 ]; L1 K; K
- @4 q5 K; f! X1 _2 ]0 _# j* @$8001: 模式页面号
6 O5 g1 M9 s5 Y- x" r! ] 写入一个数(00-07),切换存储体到对应地址
! I( W) z' i$ r0 m$ q; N+ @# o" S, z$ u/ W
$A000: 镜像选择
8 C$ x- i# {: J( i3 C$ l4 | 0:垂直镜像9 E( b) v1 w9 @4 K
1:水平镜像: i3 x; W9 N0 x- E# t6 W" T% _
' w- a7 g+ G7 B1 U0 q5 i4 c, ]$A001: SaveRAM 切换( R0 X V, G7 z3 |3 e- Z+ o% b) S( Z
0:禁用$6000-$7FFF, |2 V# a" e- d' P
1:启用$6000-$7FFF
5 J* e* j+ ^( @3 o$ ?0 }2 s* D& O8 m6 L) [9 k0 n
$C000: IRQ计数器
5 l$ j& U' @8 N1 G- s% z IRQ计数器的值存储在此处
1 U" {/ A2 n/ l
# @0 I$ J' S d1 _; A5 g$C001: IRQ暂存器; w/ r, P7 m* j' @: H
IRQ暂存器的值存储在此处
+ @. J% |- }: A0 \1 V/ ^9 e) W: a; @$ }
$E000: IRQ控制计数器0
2 p5 i5 b) O) d" G8 g 向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
, o3 ?4 j' D& m. l" i
! Q9 R9 @2 o9 R# h7 L$E001: IRQ控制计数器1
$ ]( v) }2 ~) P! |( D2 R! | 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)
) `( }' b) U* U" a* U& V6 |: e5 S+ M, V
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:2 p; I; I5 a) G$ s+ \& q+ W0 W5 ]$ J; ?
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68
5 E9 h8 E' W# O) k; F! C$ n( L$ FPHA 累加器A入栈
, j' q, J$ l% y8 x( ?* m& a# VLDA #$06 设置切bank地址为$8000-$9FFF
( A* ^& i l0 c5 O J$ A( RSTA $80008 P7 v; ?6 B9 t4 B
LDA #$0E 将第0x0E号bank切到$8000-$9FFF
/ o" Z; P' R% m6 ^6 A2 oSTA $8001( D/ w2 n, v* b% C% p/ X: N( Z% v n
JSR $8000 跳转到子程序$8000( p+ Q: L# @# E0 q
PLA 累加器A出栈
) i+ G5 }, ?& D1 s! O& `) v4 |2 S3 @& Q" P: a5 d- U2 G
为何切换的bank号是0x0E呢?& m" m) R1 |) U; m9 T
因为在扩容ROM后,文件PROM结构为:$ @; ?2 G! `5 D' C
原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM4 m! W$ d; `, Y: r
q$ v4 U* F' _Mapper 4切bank一次是切8KB,那么文件结构就是:# s- M- m/ j0 r* |) H
原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
4 i/ z) j4 K3 F" X% [; ?( G" s因此选择扩容的第一个空白bank就是0x0E号bank。8 l. g* K' J4 z' l

7 U! P0 _9 S* |0 |2 x4 x3 V# Z3 P) B& `6 K0 ^# [
然后去调试器找RESET中断中可以放下切页程序的地方:5 g$ D4 L, r! W c2 U7 j
首先长度要小于等于自己写的切页程序。6 x/ A2 }4 B' _: W8 z9 X8 b k U- F5 ? P
可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
, z" e- \; x- u
$ M+ Z0 p- O6 Z/ D) w, J' r然后跳转到$FF7C:
9 b$ {# M0 D3 n6 R
1 [! q+ k/ y. E ! |9 g( t& B! B. \+ |
2 r6 r, x$ w! w5 ~
: i" C; |8 K, o1 Q; M8 P6 \复制下可以被替换用于写切页的程序:; ]1 |" X. B$ x$ l* H7 N
先选中,再复制:, _$ Q4 @3 Q/ H3 M; H. {( c0 R
5 M- y n ]1 N. L! F$ g/ d7 H
H* C% T% L' C h
在Hxd中新建一个文件,把复制的数据粘贴上去:
* h2 D( @# g6 T" l6 t( Q 4 [. l' r+ Y' n% d# Z( Q

0 a" ]( k: ]5 ~' j I ' J4 n9 P# u6 }- u d
4 S9 D: H6 N9 W: q
然后回到十六进制模拟器:
" P: K( L- j; ~6 M* p转到$FF7C对应的ROM地址:
& P4 Y3 A$ Q: i3 }# [# |
{1 ~" @! ?. p9 x9 H' S7 J# H
0 i0 Y: E, G3 G9 \* N d然后复制Hxd的切页程序,粘贴到这里:
% I. j' q) D3 p
. O% D# G) S- z X4 ]+ c- t% W/ f
$ s2 g5 t# e+ P/ p) r, S* A - I0 Q) w$ F! }+ B. V3 |( E' `! G4 T' I1 _
没有覆盖的用EA覆盖:
4 u- v1 n4 D' X+ M- v/ ^/ z |7 o
1 Y H1 m. C9 Z) w" z6 `( h6 ]打开调试器可以看到变化:+ z' s S2 O* a
4 U" D8 }/ J4 [: A3 l
然后添加$FF7C执行断点:/ R& D8 u) u8 D* q& ?, _ d- N

( S' ~ B7 F% p1 G- z$ a
$ N& F e3 h) c D1 F
, S4 ~0 n7 e1 S5 O单击运行:
, r5 l: d- i3 r4 t6 f3 ]然后程序在$FF7C这里停下来了。 Q Y- b" u8 R* u
# \/ e% X( h. F5 b
然后单击单步进入慢慢跟踪,直到跳转到$8000:6 _( i: W: c$ S+ p- J
; ^! n, C4 i F& } z
然后打开6502_Simulator:! F& {/ `" F0 ]5 y6 M4 y
, h( Z& z& k8 Z$ X* k: h$ w
再打开我写的数据搬移程序:# G. b) n% h- a* Q( u

- k, x* j7 S! [& T 8 }# U5 N5 [+ s6 Y8 ?% g
然后修改对应的数据:0 g( Y( h5 Q0 ^3 W# I G% C- P5 S
程序开始地址:修改比如$8100就修改为 .ORG $8100! U2 ~2 i: \0 o2 M1 F% t+ D
复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70+ w1 S! C3 b5 f0 Q4 P
从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82
1 T9 v* J" [! ]2 s* ?5 M想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$910 V+ v' T% y; F: n
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。. b9 C) f/ H4 T6 l
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。$ H1 W) j, q' {
中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。
( M4 k0 Q- W2 F后面的不用管。
, [& s) x& |3 B2 ?4 e* H& F, |& t" r. E5 n6 O- R) d
设置完数据后单击编译:: I& c& a: I+ U3 }' e

2 V% ]+ ]7 p" k' Y: L; H! g c2 x然后保存编译文件:
3 ?1 U( s: Z8 S : x m2 C; m$ L; |# {( O& Q
选择二进制方式保存:. q& q+ L) L4 I# c& Y

- x7 ^; |7 Q) A) l6 J, @ 5 L8 p: T1 _: k5 u8 M1 J! V" n
用Hxd打开保存的二进制文件:7 o4 G: a' Y6 g) ^' n& S% o5 _
* h& T8 m9 ^* O

" ^( U9 t1 `5 Z% N) C3 k跳转到设置的程序开始$8100: Z/ i) L0 m- F# C
% n6 O: w9 }! u* e2 m0 d. d: w

9 [' P' r5 K9 f& u3 j! h r 2 C7 ^: X0 {9 f* i0 M
回到FCEUX,转到NES内存的$8100对应的ROM地址:" H4 L8 e! y" Y& q
/ C: U: R2 O7 R1 J
5 M9 n4 L- w7 O! N/ O2 K: R

+ j" p4 L; F9 A" f! u
1 f$ }7 K# a% f' U- B+ P 7 S: [7 R+ W; C4 R1 ], O$ X' o
然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:1 i2 ?+ N$ [( v7 u {8 F. S% R+ n

4 \3 A' Q9 k; `/ u
" u) z2 b& g3 |3 } - N- ]3 _4 V) w* g$ u& @, L; T
然后转到NES内存的$8000对应的ROM地址:
5 b2 j+ g# ]! s+ l6 ]5 K! J 7 V* ?1 q+ V5 k
. R9 |2 d6 f. e

* @; d$ N0 W* X( `7 [" e. {. W( I
4 N4 g' C1 V8 j! K+ l4 m0 |! J, i* F写上如下程序:2 U8 k7 O0 r. Y: J
A9 80 8D 01 A0 20 00 81 % g: M! c1 H! v: @. i U
LDA #$80# G) `( O, ^; [
STA $A001 可写方式开启SRAM
( g* i/ `/ y% h( EJSR $8100 跳转到子程序$8100
7 ^. `4 p1 M! Z
( w- ?& q4 c) v* a& j. L" a t然后把Hxd里被覆盖的程序复制过来粘贴在后面:
3 x! ?8 q' P/ s2 ~! R ! o0 `( f: Y4 f: U1 I7 ^

$ L- ~* c& z' F% s% }1 [
4 @& ]# m$ o/ P- g$ ?, u末尾补上一个0x60:/ |' w; q) O5 A/ N% J! {+ D3 _: ?, L
RTS 子程序返回
( C; Q: _$ r j- q0 j0 } 9 w/ p* s2 H$ X' h6 |
然后单击运行,ROM音乐响起,正常运行:4 i; `$ A$ n: j) i

$ u' @% A6 i+ N) ?6 A( ~7 F5 Z3 ~1 r % y4 {3 ?4 [2 t1 U
然后转到NES地址$7000:
8 Y H. o$ N1 Z$ X/ O- T: J. t; u" b & P( l& z* |) y( e8 `5 b2 N0 e
' Q) A% v8 Z3 @& G( A& M

8 }$ ^2 m' c3 k V, b1 z/ Q , s6 N4 n8 y! u6 N% V0 K

# ~) ?7 } [% {# k' N& o3 u可以看到,$7000-7FFF都被复制了一片数据。
6 N8 `5 c# V4 }测试没有问题,然后保存文件:5 q( b7 J% P, p! S) Y5 E

2 x; M: W$ s' f; A* m' Q9 l以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。
# g3 G7 o6 Y0 L. N+ z2 e3 {; A! o3 t后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。
9 k! p6 ^+ a7 K |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|