|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 ) s: c' a5 t1 h, j+ O6 x2 J# ^' f! E
" h5 y* r* @* x; J# Q; n2 l
[FC][SRAM扩容教程(Mapper 4为例)]
; s% O/ I; f: E0 c/ H$ S8 A) n' t% w) m% {. i7 |
时间:2017.4.289 z, |* m) X* F0 a; @
作者:FlameCyclone$ P4 Q) A% Q2 ~. t% {
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
6 `; n6 o1 \7 g2 u9 ^1 \+ ^ROM:双截龙2(J).nes$ w! p# l' u1 J- H! y( m
适用:没有使用SRAM的ROM
7 [, X2 {5 P% i6 m. Y9 t7 ?/ F u0 b: {$ G0 T. x
首先用Hxd打开ROM:. B3 A* F4 [4 S& U: N. k6 _
' ?! @: r' D: K: s+ R7 g- u, ?
然后扩容:; a7 C( r2 f0 J# e5 W1 K
6 n, z' ?. \5 f& ^
& t6 x( ^4 r& z& H8 \1 ~1 A$ [; T7 O
* m! a! v7 _# P: a- {( z1 ]
* j& S5 J: V% b7 v, B* H* p- K1 W
$ R! I* n1 `. Q T& D" w
7 N# X/ C4 W" H2 @, L/ y
% h! W+ j3 Y$ |( u9 d先看看任天堂产品系统文件对NES文件的说明:
- G/ J+ W% ~/ gNES文件格式
- f% C' Q' ^9 P; O% z6 T7 e& G$ ]7 @) y.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。
. |% X$ y+ J5 E3 x9 R偏移 字节数 内容
: j' j, {/ V4 c0-3 4 字符串“NES^Z”用来识别.NES文件
2 d% i' m- {- }+ @2 @4 1 16kB ROM的数目
7 c9 d+ W" ]7 O5 c5 1 8kB VROM的数目 ( p6 i" C0 E% \. L0 d9 G
6 1 D0:1=垂直镜像,0=水平镜像
0 C2 u( M1 ~1 o( M0 o1 R D1:1=有电池记忆,SRAM地址$6000-$7FFF $ {) [' W& H0 d. B/ A
D2:1=在$7000-$71FF有一个512字节的trainer 6 G& @& j% ~: g# a" b( h
D3:1=4屏幕VRAM布局
9 E1 J- P" q. `& }( o5 h D4-D7:ROM Mapper的低4位 5 c" {8 Q$ E+ t: N( v+ t
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) 3 S+ ?) W3 p% @( g/ q
D4-D7:ROM Mapper的高4位
+ T1 i0 R8 G# j1 t8-F 8 保留,必须是0
2 e" _4 t B/ L2 q16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
+ A7 t! B! E1 g( j; J+ I1 R( e& U-EOF 8KxN VROM段, 升序排列 1 R0 \0 f+ c# Q( ]0 {! s
! B z& D" h" M2 r& f4 q然后知道这个ROM有0x08个PROM和0x10个VROM6 A' k, k; p. r z& W
接下来扩展PROM位0x10个:
$ { S" g6 f( N4 K( t先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
2 K# {5 `2 s% f9 A( E
N2 k* J# U" p) k由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:6 n6 j& G! H7 B, \* x6 M! a
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
* g- U0 c! J$ ]% }) h3 u' n7 e于是可以得到双截龙2的是:
( L3 `2 I1 _5 \ m9 c7 W' }(8-1)x 0x4000 + 0x10 = 1C010。3 p! m5 M6 t, s
然后跳转到1C010:& H6 I# Q1 l- }& Q' x: D
+ l4 X; n A; T: p1 h2 a- B
6 O3 O0 v5 n6 w' U( p: S
: Q! S; }* T$ z% u! T
然后插入0x20000字节的FF:. u" ~, b! A8 e/ V; ]

9 t! @* I! a+ r# j3 `* e/ @$ N ) F" h; h& f* d5 O0 Q
2 @ Q& Z M2 L; s
然后保存:
7 e) _) O: r p, S2 m. V- N ( q* ?% T* l- A

' Z% s/ G0 g' m9 o) E. W+ M用FCEUX打开正常运行:
0 k" S8 t2 d- D
! l( N% m* t. T- p# W查看文件信息:
" g- i4 Y4 b) w 0 s. v5 p, g: y& t+ @8 n) i7 L$ x
- E d# N) @+ h: {3 z: H
接下来切页:2 N& T* _' t }2 G9 Z
先打开十六进制编辑器:
6 ]& b* O1 Z; G$ w- {" G( ?; H ( {3 ?- y" W$ d' Z) }2 C* U
拉到滑块最后,看看重启中断) ^6 }7 ~ @( C6 c
中断地址 中断 优先权 ' v7 U$ K. U' ?8 L! w+ X
$FFFA NMI 中
! m+ o" E M# c5 X. x4 o$FFFC RESET 高 + b8 t1 V; X) `8 S& g
$FFFE IRQ/BRK 低 2 ?( S, Q( H8 ?8 I
6 g7 x! j; B6 F1 `% r0 e/ E
RESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
. j1 ?6 s8 t- x, A9 X+ @! n. K由此可知双截龙2的RESET中断$FF65。
( k# W! i7 a" ]$ ?2 R- e4 r* x接下来添加$FF65的执行断点:7 H2 j/ I% \9 z7 I
打开调试器:/ k- D# h1 q/ G' }

" R8 @: I3 h; T* W: j; y& O( ?添加$FF65的执行断点:: O6 x! z4 \" j L
% e& h% e6 L7 X3 c1 W- o
% L. r# [: O. U. |! U; V: i
/ Y" H+ I! ]" Q4 u6 N1 W
单击确定:
8 F7 y9 h6 B- D' k * o3 m) {) V5 _- g$ V: V* G
- Z8 a* [# y1 z) [; O! R) i% f7 @
然后重启ROM:# E2 y. o0 E* C. C+ T: e+ E) v

- E2 ?- e: W- }0 F0 |调试器此时弹出来:
; y6 w8 ^3 y: [* a" o2 H
; X3 J) A8 h! a+ p/ P; Y4 V9 [然后打开Hxd,写一段mapper 4的切换bank程序:
3 Y4 [% W' x. c, f' c h+ `先看看mapper 4的说明文档:# Y( @$ A# R1 D! x% J3 I
Mapper 4
) E' e, D4 [4 r6 j3 ?: K; V( g: J; n. ^; {' l, O7 R& c9 v& c( q( h
$8000: 模式号
2 r: G- o7 t. h7 t 位D0-D2:
6 U/ o1 P2 Y; K, \1 J& i. Q4 q 0:选择2KB的VROM存储体映射到PPU的$0000
X5 \0 M Y- Q 1:选择2KB的VROM存储体映射到PPU的$0800
1 Y3 o" c0 b( b* {$ [ u$ X* W 2:选择1KB的VROM存储体映射到PPU的$1000
8 E/ M& P% r% B* p1 X' Z n5 S 3:选择1KB的VROM存储体映射到PPU的$14003 J! C; v+ y* O1 ], L
4:选择1KB的VROM存储体映射到PPU的$1800
, P3 {, Y" Z3 b: x 5:选择1KB的VROM存储体映射到PPU的$1C00
8 E1 ^) q( Y, {$ Q5 x 6:选择8KB的ROM存储体映射到$8000
' o' E$ t5 p( R6 A 7:选择8KB的ROM存储体映射到$A000: S8 ?" `- g( q- C; Q4 w
位D6:
' T2 L8 x8 i+ }$ S. A3 v 0:允许擦写$8000和$A000: _- [! k) T# Z2 [& O% c
1:允许擦写$A000和$C000
/ K5 m( w, Q5 D2 S 位D7:% S' r7 O4 T% {7 g
0:模式号D0-D2使用普通地址
; i: t8 G) q2 i2 R. N, e 1:模式号D0-D2地址异或$1000
/ B5 |( ?, C( `& J
7 R& e; k9 k4 @ k& p$8001: 模式页面号, I7 R8 f( P/ A4 d; }0 i( }1 p
写入一个数(00-07),切换存储体到对应地址2 ] e4 L" c6 ^( d9 @+ ~- D: g' {
3 U4 _ L, l# B+ K7 J+ T
$A000: 镜像选择
0 C! m8 @8 A' Y, J: l: V: [5 O 0:垂直镜像. \1 o% s5 ]* \& v# _" Y9 _
1:水平镜像* A2 u7 _" N7 B* \$ ]! x
+ X% r/ J9 g3 F3 z5 V
$A001: SaveRAM 切换
; W+ d$ l) ?" n; ] 0:禁用$6000-$7FFF
* ^; I. D; q1 r5 e) d; M# F 1:启用$6000-$7FFF* C7 w' @8 X2 A3 c* _9 q/ J* n
" m( B# X: H* G; K
$C000: IRQ计数器; X* _! z# [+ N _( D
IRQ计数器的值存储在此处
% ~; L9 f7 n% M# X, i7 S* u2 B4 d) T9 H6 @" z( ?
$C001: IRQ暂存器; l+ l3 m9 V9 n
IRQ暂存器的值存储在此处3 T$ V3 t5 B" X8 l
% y _1 O/ X* g7 m- `! i. \$E000: IRQ控制计数器04 z4 p+ Q, I4 z2 ]: D Y
向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
, b5 q* D z" f* }6 X5 V* u# D2 _& l# X1 J; I! s5 d* z1 r) ]
$E001: IRQ控制计数器1
! G- S. k0 D7 h' | f9 x8 _( j3 d* M 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)# @" Y7 Z0 F1 T3 b$ B
/ P) o. N$ Z; \7 m3 y( _/ R* j% o8 q
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:' O( x1 b" t% x- g, A" H) j4 u
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68% J) @: A0 z& i5 N- Y6 e2 @
PHA 累加器A入栈8 A, ] A) C( [7 e+ s+ g
LDA #$06 设置切bank地址为$8000-$9FFF$ N. ^$ C# W1 X
STA $8000
9 r1 V6 L5 \! rLDA #$0E 将第0x0E号bank切到$8000-$9FFF+ d2 |/ g5 v0 h; A" ^, O
STA $8001
0 r+ P0 |; ~! d" C7 dJSR $8000 跳转到子程序$8000
5 u+ v `! L, O/ f, BPLA 累加器A出栈9 s R/ c+ |% F" H
0 m7 O; @: y. W: s% i/ [为何切换的bank号是0x0E呢?
3 `9 V; V: ]- T8 G因为在扩容ROM后,文件PROM结构为:
8 q- \: x& z2 z/ W( `; J原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM) L! y1 w+ j* z4 Q! Y' x% U
* g& @0 J) w' [& n
Mapper 4切bank一次是切8KB,那么文件结构就是:" j4 A4 N' W/ ]: X; Z( ]8 W
原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)4 u0 y3 h" U6 q
因此选择扩容的第一个空白bank就是0x0E号bank。( k6 t8 x* [9 }3 @) Q
. Y! J( I. `+ S+ {1 H* d
0 V( u8 d6 O6 V$ ]5 y1 F4 D8 U然后去调试器找RESET中断中可以放下切页程序的地方:) \6 r8 Z5 C& p0 B& P* k1 y
首先长度要小于等于自己写的切页程序。% @9 W/ d3 o' ^' ]/ U! ]. G
可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)& z5 r9 w3 A$ w0 F# F1 P
6 l) _4 v2 q: N9 T
然后跳转到$FF7C:7 U/ Z7 g; f; F

# i. a3 [6 _3 w # J. P4 G, a: H2 Z& ~/ |. O* ?$ m
" d; M5 R+ i: Z/ o m8 W
2 k7 B3 f3 I+ M0 Q0 _复制下可以被替换用于写切页的程序:$ O$ m R+ F/ ]4 G
先选中,再复制:' S7 v! J2 P- U

6 s1 s( C6 l. R* ^
. K0 u' r1 B- J( _; z- O在Hxd中新建一个文件,把复制的数据粘贴上去: o- L! l3 h3 R9 v9 g& i) B

) u$ l2 j1 }/ e8 U' w9 a
# F G0 U% t) x: k9 Z ! Y' n7 h* ^' l
' u) Y9 o* b: x) Z8 D: ?然后回到十六进制模拟器:% L9 d1 I) |9 ]4 s( [8 P2 Z
转到$FF7C对应的ROM地址:. }# w, u" _5 T0 }) l$ ]1 n/ l7 L

0 D/ T9 J( B8 G0 E$ T1 M3 A+ ?
0 m3 Z) Y" X3 q( O. }. f3 E然后复制Hxd的切页程序,粘贴到这里:( e& l' G( N; y
% H& @1 |' b r/ v0 m$ o0 D: h ~

+ K3 n, L% f+ }% W2 w5 O9 m. q % y$ v9 ^1 }9 h4 u
没有覆盖的用EA覆盖:
* k, M5 \5 u( D2 k' q1 y% W
9 J% N3 h1 A* L, k; h打开调试器可以看到变化:! ~, m* W. y% [5 u2 i5 @5 I
: i- r% h2 W& ^! o' n
然后添加$FF7C执行断点:
- V- L* K6 G- M
7 u0 `+ _: G9 W5 n8 w$ O
( N9 m& X2 p) g! z; } R" l 6 v; q" V; Z0 L! ]% s: T* r
单击运行:
6 y" P6 t! S, L g; G# }然后程序在$FF7C这里停下来了。
. g* }: L; d S , U) d+ ?0 g8 `; z1 U2 c
然后单击单步进入慢慢跟踪,直到跳转到$8000:# E# j, D1 |) a% h) V( I. c
0 ~$ B' A# j9 k$ W) n2 c: ~& C
然后打开6502_Simulator:! O0 x9 V" D7 `6 Z

( `! d2 k; {1 T% O8 |+ s再打开我写的数据搬移程序:
- m v% Q0 s0 o) u B 8 w3 Y3 B h" E" Q, O7 s: u! q
8 [( E% u8 h$ b0 \
然后修改对应的数据:: k' N$ D- D+ [% B! ]
程序开始地址:修改比如$8100就修改为 .ORG $8100" o! l$ i! Q" E+ e, }
复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
4 u6 [. z7 g/ D; H# i5 N: t: u7 y从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82
: b' A9 ], w% v3 u想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$918 A& r; r/ v9 r& x1 _
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。$ S3 U$ U7 J8 p; Z/ R
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。% ^$ h. M; ]* P5 h$ p( K# s7 r
中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。6 N6 o: o7 e8 b; }3 D% }
后面的不用管。" `* ~& B( k& l& i8 K* g
+ l0 Y$ Z$ x. I; F
设置完数据后单击编译:$ \0 I A( x, r1 h8 E

( ^! Q6 L- u* o" r. Z0 }然后保存编译文件:2 Y ^' R$ ^8 u' f: J$ g" S* Y

% P5 Y7 v4 d, e) p- Z! k选择二进制方式保存: Q* V. o' c0 M5 a3 M/ G

5 r$ r! `( U, Z7 v4 ]+ H/ M X$ C
3 l' Q! G6 q, f, j8 O( L用Hxd打开保存的二进制文件:
( K+ g! R+ l, i2 c( j2 g+ |/ x & Q5 P$ q g! B/ J! @% P
! q( _- V+ A. O7 {8 |
跳转到设置的程序开始$8100:( w- D) e% q; c8 e

; n$ X! t( ` \9 d# Y$ l4 ?
( o7 C, m6 q' |# | \1 F
6 D% W- Y7 Y/ n( n8 {回到FCEUX,转到NES内存的$8100对应的ROM地址:& H% t! b1 ]) I" `6 U
# ]0 D" {- k# t8 m5 X
4 j+ Z( {: u2 Z" ]; i8 ^2 o
8 c* E/ Q4 A, w4 Y4 v! @
% x" X& L# T6 D1 L5 G# S" @! B
# e9 V4 I. p( ?+ s. _7 L1 L
然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
3 I# n6 l e, C! I1 |- n. E
6 Q6 u! w" }3 I- w, i& d3 u 4 O4 F( t; ~8 }. G+ l1 Z; p

" I5 G5 n, |$ o3 P然后转到NES内存的$8000对应的ROM地址:
% u/ f7 O; ]* z) W1 U$ M5 d5 `
( ?% ^+ k3 C# W ' W7 R3 o0 ~ {7 h
L: [+ j2 w) t$ [

, j+ [, g3 _+ [' x7 Q写上如下程序:
0 R7 k2 G4 x( R3 N- v0 JA9 80 8D 01 A0 20 00 81 5 ]3 c( f; X! J: c
LDA #$80
4 }0 C2 n- R6 d8 ~! D9 BSTA $A001 可写方式开启SRAM
' u- x- Y' N! Z- f+ N& HJSR $8100 跳转到子程序$8100
% P! m2 ~ l9 N6 Z $ I; e( k; ^* P6 ?& ?1 k! b+ _
然后把Hxd里被覆盖的程序复制过来粘贴在后面:
" I4 f+ U) @$ F
/ T( B! h1 C. s* ? 3 O: y$ G* G$ t. l9 r

3 D, V: x T; R9 R) z! F. _1 w末尾补上一个0x60:
! K6 E5 W1 L# R* m! X; }. jRTS 子程序返回
% E, T( F; f! U+ i2 [ ; H1 W: l- g; b
然后单击运行,ROM音乐响起,正常运行:5 [6 [( B& k7 p" w
% k s$ u1 g# U
5 z( C' n" R+ X* j% _6 _* J, W0 @
然后转到NES地址$7000:. o/ v' E( k0 C
0 C9 E: n% C" X/ d- h
$ p1 \8 @% o" J4 [
\" J8 X/ G+ E7 s

8 K) d2 U6 W/ N5 ~ . k/ G c( P8 n: z
可以看到,$7000-7FFF都被复制了一片数据。- A! S7 I8 n8 l3 P) l- O; X
测试没有问题,然后保存文件:
3 }) b$ y1 r- T9 T) y* e* g) }
9 a, b9 c% {4 E/ F以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。
* f/ _7 R: i9 T9 ^0 M后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。* ]9 ]1 y! X( i; h: i; e: w
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|