|
|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 $ y6 h+ p. e/ ?9 `
3 X0 k! ~9 d, t$ i[FC][SRAM扩容教程(Mapper 4为例)]% ?1 B! b- A8 I6 D( N
. m3 w" N1 }% i) s7 o( ~$ S T时间:2017.4.281 M, v# M# C% J2 U
作者:FlameCyclone q$ x) J; P, N& g
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
) \- A7 F* r+ r1 P$ H8 A" o9 o% o7 yROM:双截龙2(J).nes$ ]+ H2 {7 ]# Y+ G
适用:没有使用SRAM的ROM' m" c) N' n5 r6 @
4 Z+ e! p) V. |( g# S首先用Hxd打开ROM:) M; C \5 [( O8 s" a% v# q( c0 H

; U8 f* S% y; n: m/ j# |/ y4 P z然后扩容:+ Y( ^$ F: {$ K' _) ^- w
( Q L( I0 o/ @4 N8 [ E) ]: D! r" b' v% e2 S8 g9 T
1 M$ B& t- |. z6 R: H" i: p; [: s& i3 `/ L( e
) C& A Q" T* z
4 c& r& L+ _; S. B! Z0 `) b# W7 f: e
/ h% }* N; K$ x' I
\$ x0 P- y2 f; Q6 z' X6 O, P$ U- l" W2 [4 X% C, L
先看看任天堂产品系统文件对NES文件的说明:/ R: v3 ~$ Q0 x4 ?0 V0 J% ^1 i0 {
NES文件格式0 O! S$ B! F o
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 ' Q8 t" p) r* Q- d
偏移 字节数 内容
) }! S2 ]' W1 d" a' v: q0-3 4 字符串“NES^Z”用来识别.NES文件 5 g+ R! ]2 w( f% U
4 1 16kB ROM的数目
# j7 \2 q+ ]2 Y5 1 8kB VROM的数目
- w* U5 ^4 P% k3 c9 L \ a6 1 D0:1=垂直镜像,0=水平镜像
2 `/ I4 _6 u4 `0 ]0 e D1:1=有电池记忆,SRAM地址$6000-$7FFF
; i3 {# @, \" f# _; ? D2:1=在$7000-$71FF有一个512字节的trainer
8 o r! D j& u& k, A$ d: e- ^! d/ e D3:1=4屏幕VRAM布局
4 p! {5 X3 G- `" {6 x D4-D7:ROM Mapper的低4位
/ [: _/ @" o: } X; Q6 _( Z" o; v7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^)
3 i2 ^7 l# C, T$ P4 H D4-D7:ROM Mapper的高4位
: Z/ P/ c# j$ |8-F 8 保留,必须是0
( q! B1 a/ D- a1 }% ]1 f! y; Y16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前 , Z+ J; |9 A; Z" b4 I+ R2 S. [
-EOF 8KxN VROM段, 升序排列 & U2 Q2 I7 l6 Z( Z g1 P8 J( V
: L; z6 \3 C5 i! G1 H! O然后知道这个ROM有0x08个PROM和0x10个VROM
# T/ n8 K2 b8 H* D- P' Y; ?接下来扩展PROM位0x10个:+ R: [# W( I# U* x* T
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
' Z0 q' i* c2 N ) {1 c, m0 }" q) w6 ?# L
由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:
) f; x3 t# _1 K0 E, S/ B最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
7 R9 E. a; P5 P4 [8 J于是可以得到双截龙2的是:
6 o6 y. @& G" s# Y( t$ w(8-1)x 0x4000 + 0x10 = 1C010。2 M! U" w1 H" R( g, S
然后跳转到1C010:
+ @3 V4 ~- B. G% {8 p
% o5 u. E5 r. c; Q% L
' @" s9 f5 l2 W' x) e8 R! f0 L
4 O9 \+ M1 E1 a然后插入0x20000字节的FF:
R( S9 D, E& E5 U1 L
& ]' w2 R) r; d4 W7 t# B k
" y9 p6 \0 {% k, C
7 D! D# P/ g: x5 w然后保存:
8 D6 r, B- W' }$ {5 e r' L# [
" U1 I; g W" }( }( P& ?
# P& p& {3 Z8 l2 M' c( t用FCEUX打开正常运行:
; q4 P0 @, t2 X/ W 4 ]' P- T2 j' M7 j4 l6 @9 o8 G
查看文件信息: ?$ W( m% L! O+ V4 h

5 ~4 \- m' F6 x1 K
- B; o& p/ C$ [7 [0 F& J" c接下来切页:2 o8 ?- m/ o0 P; r+ p
先打开十六进制编辑器:
6 T# s& T5 m0 [5 t3 K x) V
; f! S' O2 c" x5 w6 U/ g% U拉到滑块最后,看看重启中断
' I& b8 ]8 Q6 L" s* r. w中断地址 中断 优先权 ; h( c, l+ W [4 q
$FFFA NMI 中
2 q3 N x7 x' v0 o/ Y( K. I4 l$FFFC RESET 高
9 _8 f( C, D; u) @3 o, w$FFFE IRQ/BRK 低
: i6 g6 B, L6 V4 b5 B" p
* x" c( w7 V; t% WRESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。6 b0 F5 T% Q& u# g5 O5 O4 c
由此可知双截龙2的RESET中断$FF65。3 u, `& n4 |3 T2 \( u
接下来添加$FF65的执行断点:
" ~: e5 M# Y* i, U* [打开调试器:
7 x" ?! j! z& e8 @9 H7 I8 ^ ! O1 b5 V6 f* i- e
添加$FF65的执行断点:3 M) ?3 q& F) _! x3 j: A& V
; k3 r0 Y1 d# i; H2 |

( ]2 i- \- v3 p5 D, }
5 y, u% h0 a9 h) ?单击确定:
0 s" Q% i: q$ ~, c! ?
) O( _; x) }8 w/ E0 K
; P* \; N7 p% ?/ [0 w, s4 N( G然后重启ROM:
' Q' x3 A1 m& {1 u9 D9 L - D7 J+ m Q. N- T0 v: f
调试器此时弹出来:, V* q8 B6 q3 K1 C8 \3 s# p1 N
2 X$ b8 R# R8 S0 m
然后打开Hxd,写一段mapper 4的切换bank程序:8 i4 [* e2 g" `& X3 t4 L
先看看mapper 4的说明文档:
, I8 s3 L# g4 U, FMapper 4
& K/ T1 s2 g* S. O7 v5 U* y) f% \3 H1 [% ~$ Q+ A7 ?$ h8 u
$8000: 模式号
0 X3 W$ t. u3 {( ~6 Z$ f9 a 位D0-D2: s! ]- N; {9 G( L6 h# }0 N
0:选择2KB的VROM存储体映射到PPU的$0000
5 ?5 z$ k# b- w& a& a* p" L3 A 1:选择2KB的VROM存储体映射到PPU的$0800
6 ]( x' L& h1 ?8 j8 c 2:选择1KB的VROM存储体映射到PPU的$1000
. t, S1 |$ e7 c' s# _4 N 3:选择1KB的VROM存储体映射到PPU的$1400
: E/ N9 Y" C9 Z Y! I 4:选择1KB的VROM存储体映射到PPU的$1800
1 ~9 V: F* P% R v# b' | R8 H% g 5:选择1KB的VROM存储体映射到PPU的$1C00
; Z2 f! L8 \/ w( L( q- l 6:选择8KB的ROM存储体映射到$8000
5 U! F; G( S, n 7:选择8KB的ROM存储体映射到$A000/ W7 Z/ r+ }* k5 s* y0 i
位D6:
S1 Y: v, N; w9 Q 0:允许擦写$8000和$A000
8 \+ A3 R8 a, }; s" o' D1 v 1:允许擦写$A000和$C000
0 p( K- e i3 c 位D7:. s7 n4 |4 l* u! I) t
0:模式号D0-D2使用普通地址/ r, V$ g8 V' r6 ?& w: G! p
1:模式号D0-D2地址异或$1000" Y, h6 g) @' [; a) ~# \7 h# X
* j: A# K4 V2 W* {6 U% w$8001: 模式页面号* x o2 W2 n2 Y/ G+ W; v& f
写入一个数(00-07),切换存储体到对应地址
- M# z" `1 _+ C0 y+ ~" b" [- } d4 \6 y6 |# `
$A000: 镜像选择
* ?' d+ J3 y! Z% ] 0:垂直镜像1 T! D: v' L6 R( c% {( |
1:水平镜像) n- z! ]8 _- i0 y) Q
: {5 {5 O3 a# v# n$A001: SaveRAM 切换( M; P0 p0 T' H& |$ w( N$ O
0:禁用$6000-$7FFF
4 Z2 w, d0 u3 ?" d& G- V 1:启用$6000-$7FFF! Z$ I! B; E) @9 {( [* o
! b2 S4 ]) e' [- X0 _4 [
$C000: IRQ计数器
5 t* A7 l- }: S8 ^) N0 A IRQ计数器的值存储在此处
5 H& i. Z( Z T# \) A; ] S# L( s4 t7 w0 Z" c% | f$ u# T
$C001: IRQ暂存器
# I7 R }$ u9 K) p: r IRQ暂存器的值存储在此处% o6 j) a {0 T3 t
- ]5 _- ?# X, _. b" _* G3 G
$E000: IRQ控制计数器0
" g5 y1 G) T' @: }* F8 @ 向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
+ z( A3 X1 E4 u8 ] r: \6 U5 F! Z( p$ u
$E001: IRQ控制计数器1* _' o+ I4 m5 |) [
向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)
, e" i$ G, @& ]4 _
3 B: f- R% r3 Q& R, e那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:
& I9 J5 T B5 v% @5 u48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68
6 f! Y; g% t5 M- ]6 P, d) W9 F* T$ hPHA 累加器A入栈- ?7 }) r* Y% ?( q7 }
LDA #$06 设置切bank地址为$8000-$9FFF" L/ C) [4 p% t# Q
STA $8000* Q9 S, z. K/ b6 h: R. t) d
LDA #$0E 将第0x0E号bank切到$8000-$9FFF
- F& g4 N2 T3 K1 f- [ l( e% NSTA $8001: q) @' S! ?2 s5 F8 h9 z, s6 R+ f
JSR $8000 跳转到子程序$8000. P* }7 r2 f+ `) X% q$ {
PLA 累加器A出栈
( S& E# o- ~9 o2 y+ j; W) j/ a- h! E3 e: S# {) q5 U! R4 _* ~
为何切换的bank号是0x0E呢?, X/ h5 S; B/ F# q
因为在扩容ROM后,文件PROM结构为:
) S! _3 x) s6 I; K. }. ~原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM" f; f7 C/ S5 _) r M
{/ Y; S/ A8 eMapper 4切bank一次是切8KB,那么文件结构就是:
4 o# Y+ H+ T. `# o原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)% T3 U( ^& d; \# r# ?! N
因此选择扩容的第一个空白bank就是0x0E号bank。
5 y( [7 ~& c" u% \3 f2 Y$ w
4 r; n \, y7 `: j
/ k; o# U5 w+ y然后去调试器找RESET中断中可以放下切页程序的地方:2 x& |& G) ^* F
首先长度要小于等于自己写的切页程序。' n* x: H) Y, z4 i
可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)9 K) H% J% `3 B6 Z4 K
8 M4 W4 Z. ?3 c# w$ p! c
然后跳转到$FF7C:/ L" Q" p6 [/ m8 @5 w
! r1 F8 b+ l8 k" p {5 z& g- X
5 @$ x: H9 S9 R) u; e# m

* [6 v2 z8 _- n7 G0 k
: A' E; i( `. }" f复制下可以被替换用于写切页的程序:6 V9 N& s* X: m# v: E& k
先选中,再复制:
$ P$ x' X6 f- M. z& v, B/ j) l 1 M4 K+ G6 E) M, o3 h: M
& [& U3 @3 y5 j" k3 |
在Hxd中新建一个文件,把复制的数据粘贴上去:% ]) W5 d& h% P. ^! h6 ~# {# t o

* m! b7 s* x* c, X / q @) N; n; v
, E8 K6 _; o) @# y8 B& ?
% _7 \: ^7 o$ N0 q然后回到十六进制模拟器:1 R8 X: b, y8 [& f1 E; i
转到$FF7C对应的ROM地址:7 o3 h2 I6 b% l9 D. g& u }: b$ o. C( L$ n

$ s! f, @. L) u M , v& C4 k0 D7 S. w: z. N
然后复制Hxd的切页程序,粘贴到这里:
; U C9 y e, H; Z# v6 k
! K9 _# T- ^; C/ ` # c7 ^; X8 _! K% u4 J2 _2 S: q
a6 f+ s5 \* O* K; ~5 _4 U3 m7 w
没有覆盖的用EA覆盖:
6 h' F, ]- m' r! Q3 X; a/ G) \7 | % w( ~. F1 l$ h6 S. o" Q5 b* @* s6 N4 \
打开调试器可以看到变化:
& |: v5 q2 J9 s: w: l1 C3 Y, _
: i1 A: ]. T a! v然后添加$FF7C执行断点:# X4 ~6 a s, Y+ f/ \' Z0 V
5 J9 z4 { J. E9 ?& z3 ]! y

' E* @; I& B8 O/ D3 w( O2 u0 Z ! l7 q, e. z9 D1 Q
单击运行:
; x$ Z; e% r V, f' M# x- h/ [. P然后程序在$FF7C这里停下来了。% N! x2 y' e- {3 Q {) m. j

. H4 X7 {. L5 l% U然后单击单步进入慢慢跟踪,直到跳转到$8000:: G" H# z0 `1 G: x$ A& U

. c+ v" r( d3 k, u4 R+ c然后打开6502_Simulator:
2 T) E$ T" l3 @# r
, n, d5 e' a6 J( V; {再打开我写的数据搬移程序:
G/ L+ p- r _, D( [6 _ ; N2 t7 q, Z$ w- S9 B8 r

5 [( W0 m1 x3 ~) H. e( C% e然后修改对应的数据:
) j* j* M: u A/ q# N% ?程序开始地址:修改比如$8100就修改为 .ORG $8100
' U2 d' ^9 c* e1 X& s6 t/ b复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70% {# t) l6 F8 h; M
从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82& O- u1 B* ~6 r; |8 _- p
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91
% E/ u" N$ g# D i9 w0 h也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。
' G0 E) M, e/ P$ A+ c如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。
9 \% P2 ~% H' Q中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。
- z; P _$ g- e1 a7 |1 o后面的不用管。2 D% B. G% w" f! ^+ B3 ?. U7 i' B
7 r1 t# M# O; ~- g1 A设置完数据后单击编译:" B/ X5 E3 }3 ]
9 @# S# q$ G l
然后保存编译文件:" w6 X* F- Y3 X7 R' r' r

: H/ j8 @7 u+ I9 P- y8 C选择二进制方式保存:
7 v6 V. A5 w9 q5 J4 g/ M5 k & {$ e- d5 k' G7 q

- R* Q' e; F, F% I2 D4 _/ T用Hxd打开保存的二进制文件:6 I$ }( A" T" }

, V# O+ F3 \& ] % c: L/ K5 |6 n% M( D3 I
跳转到设置的程序开始$8100:
u! X$ t: V% S. p, k8 H3 ^0 }6 V% ] 2 [+ _2 {" X) T o, s% {5 P( Q

* z" d8 [! Y4 }# G 6 x& n# v4 F( ^' w
回到FCEUX,转到NES内存的$8100对应的ROM地址:
3 {' y" J" y/ v+ J5 M
% Y: H9 Q% Z( g2 `
) Y; h/ [; \1 s( z1 l8 J; h 8 f0 W3 Z: D* M- m* ]/ T4 W0 g
0 ^$ e0 E" ]2 `
% g9 e2 M e7 v' o4 g& }
然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
6 d& v1 R/ P# Q4 h7 J4 K0 ~ 8 `- c* w8 ]+ ]

" J' H0 Y2 @5 A% t
" g7 x' e4 T9 U1 o2 \0 c. p% N$ U$ J& t然后转到NES内存的$8000对应的ROM地址:
9 E3 l* \( Q1 U+ F
) Q( \: Y8 B8 ]3 X7 }$ m 5 D( q+ S; v5 ~6 t% h- a% K
$ ^% p% j0 o6 F3 q
& U1 d q1 g/ |7 X# O# A$ S
写上如下程序:6 m: \7 D. t. P% `3 ?
A9 80 8D 01 A0 20 00 81
" X0 |! c5 _( u: KLDA #$80
0 ^0 O( r: R+ m+ t: D# W, BSTA $A001 可写方式开启SRAM/ F) f4 j9 N' `- ~5 m
JSR $8100 跳转到子程序$8100
" f' E6 D7 V1 X1 N5 Z1 w8 e' r 2 v5 H8 {+ @& a$ j( z5 K
然后把Hxd里被覆盖的程序复制过来粘贴在后面:
; I& T6 n( k$ g
- V% ]; F1 k6 W" N" O+ k+ F7 O ' c( _7 ]9 ]) E3 y

" h+ j5 ?. ^/ A末尾补上一个0x60:1 A8 ]0 {. S+ T0 h: {
RTS 子程序返回# F$ L6 u+ J& o* S
' ?5 v n/ m6 }) \1 L. \; O+ W
然后单击运行,ROM音乐响起,正常运行:. _0 `8 V1 T% q7 }7 o+ |) j: h
U; _( b1 H# w) T

0 O5 O# b# g" i# T2 I D然后转到NES地址$7000:
& I% C) O3 d' ]+ ]# B. b- j3 t
; Y( \/ a" X0 \6 I. e, }/ M % v/ U: z- I) [' w# d

0 Z5 x9 V0 f/ _* V% U7 W
: D5 R; A: I' {
# ~% L7 p9 I3 [2 F' }1 m3 M可以看到,$7000-7FFF都被复制了一片数据。
8 ~% o4 {$ [2 C: Y P9 ^; X& W测试没有问题,然后保存文件:
; W* z. R# v0 K2 }0 R* k + K5 v& x" M: `8 ~
以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。# E2 G) P; g8 _; x
后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。 H- o0 a* C# ^7 @; b) x* [" A$ I
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|