|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 3 D# L$ n: w: V+ P- b! v$ b
; q: p" v& K, L& u2 h
[FC][SRAM扩容教程(Mapper 4为例)]
; t4 L5 x; C8 z( i: O! y1 i3 O( m3 ?+ c+ n2 k8 H4 F0 V
时间:2017.4.288 v0 w* e: q0 f8 E$ s: D
作者:FlameCyclone
4 z, N# S, b) ~$ s7 @工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
+ h, t C( A: n F5 z# YROM:双截龙2(J).nes
: G/ Q( F X7 O0 Q' ~2 e适用:没有使用SRAM的ROM
, l" q) ^2 o6 s; `- n9 `" b/ h0 e1 D8 \9 P! ~1 D2 d ?+ l/ W2 i
首先用Hxd打开ROM:
0 T. ?& n& e0 W' \- f3 p
8 k+ t- ~1 a) S然后扩容:
; T$ C0 q' G* I8 ~3 |2 w9 S; E* P/ {8 i) F( P0 l
) n' Q0 Y7 N6 t. h% `* w) E0 p4 Z8 ?) C$ N, N! ^. g$ O6 R# g6 Z
4 D" t$ I8 H% y/ w/ K9 j& {6 [4 W- E. M+ M+ l* r
, P+ ~% N, X# i( ?6 b
. U: \4 v' F4 h: |8 I' b3 b
5 a4 f8 J& Z ~& s; S! z
/ `/ {6 G$ L9 J7 O( m
先看看任天堂产品系统文件对NES文件的说明:
- d6 M9 m: t% M2 i$ mNES文件格式* a( h5 c1 A( e: w3 _2 ]9 S& h
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 7 k% S5 @9 g) }- Y) H: {
偏移 字节数 内容 , V; J1 j, s% e. B& j y! v @
0-3 4 字符串“NES^Z”用来识别.NES文件
; w) O/ Z. L7 h( l0 a- f4 1 16kB ROM的数目 1 X/ w3 d8 H# r! o
5 1 8kB VROM的数目
* d9 {1 G1 N; R3 m' N3 m0 m6 1 D0:1=垂直镜像,0=水平镜像
- L' E& K8 g, r9 j1 b! f D1:1=有电池记忆,SRAM地址$6000-$7FFF
* r1 X; `6 D. _% @ D2:1=在$7000-$71FF有一个512字节的trainer
2 ^2 P9 _+ ]( A7 { D3:1=4屏幕VRAM布局 {# [8 ]9 X( [* a- M
D4-D7:ROM Mapper的低4位
9 C& `6 y+ j1 A. t& n$ T9 m4 i7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) * ]0 U. h+ x/ e
D4-D7:ROM Mapper的高4位
! k8 x( p4 g3 V8-F 8 保留,必须是0
$ G( W. a0 k% ~* ], t- U$ B" |16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
. h1 s6 _2 S) _$ I' Q-EOF 8KxN VROM段, 升序排列 * t* @+ H. P, G0 I' i
! G1 z# J0 q0 y4 Q3 v* W* q然后知道这个ROM有0x08个PROM和0x10个VROM
% A ]6 l) [$ c$ g0 l接下来扩展PROM位0x10个:* d! c6 M; v' Z6 B6 t& M
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
% O2 ?% h) e$ z4 r" a3 t1 Y) u. Z
由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:
4 N3 W. z0 e: N7 m, ?) o- M* C最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
+ ?. `4 M) ]) b) X0 E" I于是可以得到双截龙2的是:9 |" W2 E! b. @. S/ V& f
(8-1)x 0x4000 + 0x10 = 1C010。
7 W( ?: x: G7 a; X! g4 ]) p然后跳转到1C010:
9 S9 @9 A! z9 f+ `, L( T5 |, y" e8 G2 d- P' S; \) m: W6 u- t
- ~, y" m* [/ G' N2 t: ~, W$ F* j8 K1 u
然后插入0x20000字节的FF:
y3 c8 i/ `& s$ C5 m, G" \9 w" G$ h
. z2 `- e) p$ s3 D7 r" ~% g% d- E: L
+ J, c5 e2 `! j# x7 l$ a
然后保存:9 L- N, ~ y* `! m" X+ s! T& _
- q. X- {' P( C# R
* b% b: c3 b' Q
用FCEUX打开正常运行:% y8 G6 Y* s" G7 g9 B. {1 _
, u& A- e& e6 d( X! c$ T/ m查看文件信息:
" a+ G" q1 A4 o; H/ B7 W# B0 u5 t- e7 G( w- [2 W2 H E. P0 B: B# c
$ E' _: T; y) S+ V/ K5 }0 D( o2 q, l
接下来切页:; [6 E4 m1 N0 F, ?- Q( Z
先打开十六进制编辑器:
+ T$ Z& q0 i% U* C8 Y
4 M# ]' }% @( v8 m拉到滑块最后,看看重启中断" p* ^3 [ a9 m J# c
中断地址 中断 优先权
5 h7 c0 L2 c2 t) ?+ m4 x) q0 c$FFFA NMI 中
' T) D2 }5 g: ~( q0 o @$FFFC RESET 高 7 f! c5 p- W$ _2 n3 X
$FFFE IRQ/BRK 低 4 @/ n) E! l7 i- a
8 Z- L- x V: _- yRESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
7 B4 }, [) A. p' @2 ^由此可知双截龙2的RESET中断$FF65。
6 H1 M* Q- k. z. B" r. o接下来添加$FF65的执行断点:' u/ q, a4 _" K b% G8 _
打开调试器:
6 |+ Y% g( l( K- E9 D/ \
4 c% t# a0 A3 q7 Y添加$FF65的执行断点:- h# c3 K z0 u% \7 a3 @+ ]
; Q. [ L$ V0 `5 O' Q0 e
" j7 Z- ~+ c3 }' }
+ z% R% m4 V8 ]7 W( |) R& V( P3 h单击确定:' N" X3 `/ ^' ^& V# Z
: h' o( O3 M( G; u7 v
: |% U4 p& z' `# d7 r$ L
然后重启ROM:
8 Q5 _8 P) K2 p& ]- p
: h1 d+ l# i2 q调试器此时弹出来:
# j# n7 A# @! H* ?2 s7 ?' h8 ~% L2 E ^- T9 B, V% M6 s
然后打开Hxd,写一段mapper 4的切换bank程序:
; c/ ^: s5 J% N: ^! L; c先看看mapper 4的说明文档:
$ a* Z9 m$ b; f$ _; oMapper 4
$ J: I4 f4 J; z$ P5 `, \# ^. B
- Z% H8 I8 a9 Z' a$8000: 模式号+ @9 g# s( J5 X; p$ Y* m
位D0-D2:. Z1 g# _! o0 z" W
0:选择2KB的VROM存储体映射到PPU的$0000' y7 z* m# d$ N. S7 K
1:选择2KB的VROM存储体映射到PPU的$08002 Q/ q* R6 V/ w2 `6 y5 s2 x J+ b) v
2:选择1KB的VROM存储体映射到PPU的$1000% G% `; I9 ]0 ^. N. Z0 F
3:选择1KB的VROM存储体映射到PPU的$1400+ [& v1 |; t* M7 X
4:选择1KB的VROM存储体映射到PPU的$1800
* Z/ a# r2 v3 T+ e7 N 5:选择1KB的VROM存储体映射到PPU的$1C00
6 d! J7 v. N( v7 Y1 \. H. _% N n6 v 6:选择8KB的ROM存储体映射到$8000
; \+ ~: X! w( c& V 7:选择8KB的ROM存储体映射到$A000) z! d3 g' b, i2 @
位D6:
$ b C4 d a' T1 x 0:允许擦写$8000和$A000( z) c2 k) r/ ~$ O( R: Q+ B
1:允许擦写$A000和$C000
* T5 v& P3 b$ ^# ]& j" n 位D7:( ~; V, [- F6 ~( t6 ?/ f$ j, D* ?
0:模式号D0-D2使用普通地址$ N; |0 s6 L( }
1:模式号D0-D2地址异或$1000 ~8 Y D* F) m# x. h
n" ^* c+ q# {8 V3 J' N+ S) y0 V) r( a$8001: 模式页面号
0 E7 H# n+ Y$ m% b e) \* c+ b 写入一个数(00-07),切换存储体到对应地址
9 I% K+ \# k7 k- a) Q o/ s. ?; H% k; F; R. R
$A000: 镜像选择$ i+ T0 @7 W7 r, {; D
0:垂直镜像
}* [( v! P: n 1:水平镜像
2 U3 Y2 X2 n5 j; B6 a$ L/ Y, B. O
$A001: SaveRAM 切换7 L' b }" s6 c+ z
0:禁用$6000-$7FFF
" [- R- D0 }4 l9 T 1:启用$6000-$7FFF2 P% j. {; P3 p- J6 f- i3 n" _( A, E
$ @6 b7 ~3 P% A. w
$C000: IRQ计数器
0 @) H3 @; }5 \% h+ u4 k7 p/ V IRQ计数器的值存储在此处- z4 y2 M3 n. y7 K5 c
. {# m- }2 n: d* ^" n- w
$C001: IRQ暂存器1 ~5 U- v7 Y3 ]/ B/ |7 i6 h3 A
IRQ暂存器的值存储在此处$ P0 ~# q) J4 m
3 I* v r% p! Z x# E5 e+ K
$E000: IRQ控制计数器0
0 U% r4 Y: t! x% e/ e. G2 h X 向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
2 N4 i r1 L$ C. y8 u- T& t ]& F+ s% |2 [% c
$E001: IRQ控制计数器1
5 Y: l( U" _1 z, Y! d+ E0 k3 T! ` 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来). J5 b4 a9 Z+ v* p
& p6 Q i, p% v$ c+ f6 G. h( T
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:
8 T+ A7 c$ H: S# ]48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 681 P0 W" W+ j! k! a) t
PHA 累加器A入栈8 f/ c. s3 o5 G. y8 D5 t$ _
LDA #$06 设置切bank地址为$8000-$9FFF8 B* d3 _% E- c& K* S
STA $8000
! P# w) n% M4 w# pLDA #$0E 将第0x0E号bank切到$8000-$9FFF4 ?. M& T/ E, A0 [5 M. n
STA $8001
8 V: R" O) Q( FJSR $8000 跳转到子程序$8000" s i+ D4 F* p$ j5 B; U
PLA 累加器A出栈
; B% K3 Z, l d( g$ g+ a
# l! j1 |& W r) _为何切换的bank号是0x0E呢?
! ?' A' [4 D* `; v' y/ Q+ E因为在扩容ROM后,文件PROM结构为:0 X( F0 P, [0 N3 i9 @" v" E
原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM6 [; Q5 Z% m! A( {
% E- l( K1 u( EMapper 4切bank一次是切8KB,那么文件结构就是:0 |! s5 o5 s& d @. o
原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
' `" C Q, `6 m% J因此选择扩容的第一个空白bank就是0x0E号bank。3 [* _4 G$ a' W/ C! ~; H" x/ {
- E5 F) }, l$ d- L4 {) R) |0 h1 r" n. l7 X
然后去调试器找RESET中断中可以放下切页程序的地方:* {7 W7 R8 x; W* g2 Z( k. C, S
首先长度要小于等于自己写的切页程序。1 K9 J0 k9 n! U9 W% C1 D7 _
可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
5 `0 r3 P! a; K- u( Q P+ W2 R+ C/ M+ l
然后跳转到$FF7C:. I; v6 G/ l) a; Q
# ^& o% l' ~. n- z1 U- @( W- S
2 B' d( h d# {7 i! H4 w! h7 ?
2 W- U" h% s) S
+ b2 `$ A1 Q$ z3 z. h% f1 d3 d0 q复制下可以被替换用于写切页的程序:! s/ y0 U) R j( c [
先选中,再复制:% v6 B7 S; S5 ^1 M
9 E- `/ }' S; r! H3 o' T6 C
4 I0 M* Q" k4 N
在Hxd中新建一个文件,把复制的数据粘贴上去:
8 O5 ~, _! U% R. u( Y
/ ^+ s" H4 f; j9 N( f# `% U* m/ C; u( e3 y: M7 D
4 K v) b1 d3 M* R, k5 }
+ T* F! d5 Y$ Q然后回到十六进制模拟器:
" Z6 m. C. v: P. z; L5 L转到$FF7C对应的ROM地址:6 a) e5 ?7 |$ f, D) `( D
3 Y, N3 O* A4 m6 t
3 Q! F2 ]$ l8 h
然后复制Hxd的切页程序,粘贴到这里:
9 w& `! ^9 A! a4 g) U& ~! f
* h4 b' M* s! Q& Y; W6 X Z0 P+ |3 N; Z
5 i4 t/ y& {0 Y! W3 g" d没有覆盖的用EA覆盖:1 x3 n- m) _0 j! i2 z0 I9 v2 |3 X
4 i& Y: h# g7 H打开调试器可以看到变化:+ O3 m) o1 l6 R6 o+ u8 o& P
. r/ j. K! E8 v# v
然后添加$FF7C执行断点:
' z9 b& r4 L3 a3 W% _9 Q1 h v' M1 z R% W
5 f- _) b/ Q1 [" N" l( {% M* X
" O* a, N5 M3 z# [$ a5 h
单击运行:
~. w; F1 f; F: `5 m) y1 q然后程序在$FF7C这里停下来了。. } h/ u. x+ {- r* d/ \
5 {# q8 f' v* V1 W4 c% @然后单击单步进入慢慢跟踪,直到跳转到$8000:
2 B+ G: a' b2 \( _6 o
! z; |) U2 y* `; _" j然后打开6502_Simulator:
1 \+ i/ b A0 v" u g1 |8 f6 K7 A6 l% l
再打开我写的数据搬移程序:
% G2 N% t7 s$ q" q! P' b) G- h7 \: p# F8 I# j* x
2 j. `; [- c4 a
然后修改对应的数据:% F6 F2 u3 u% ^7 ^3 S) A5 G
程序开始地址:修改比如$8100就修改为 .ORG $81000 u- I3 @" R8 F7 b" Q
复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$708 y |7 Y; q+ P* V7 }% B4 G; f
从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82; W/ z; Y8 y1 W1 X; |" h: w
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91! E0 u6 b$ B( C3 M0 V3 U
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。. T- \. i4 ?8 C# `, I
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。
4 ~9 ^) K, P" N' \7 L6 Z( g1 h4 C2 T& r中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。6 n% b ~6 V, j- o1 i. L: }7 ?' K
后面的不用管。
% R! e4 Q0 ]3 P+ c2 e, R) Z" E/ |* @8 M' V0 N8 d9 @
设置完数据后单击编译:1 L L5 X5 _: p8 I+ S, S+ _
7 i5 g C5 X$ k8 {
然后保存编译文件:
; i, H& A- j0 v% l
! q# o% d: D$ o" W" M; `选择二进制方式保存:' W7 ?& l& x& x
|' W$ m# h. {) j8 U0 N& O* S/ P' p+ E3 W+ q
用Hxd打开保存的二进制文件: t, @: P: Q# x P3 Q
' }) _1 F2 W7 m/ A5 F
# Z- L! v2 E g. {# }# V跳转到设置的程序开始$8100:: b) b+ J* |" n5 D
: @ X% C2 E$ Y& R& b6 ?
: a) r# j1 }( Q0 Q0 F7 G* k8 O" c4 t: S9 a( u8 e( V& ]
回到FCEUX,转到NES内存的$8100对应的ROM地址:
- Z" L1 Y" d: j7 q+ I; r. ]/ W
9 L4 `. I( I) X0 Y
" l( `. o5 [' w5 n& [. m9 r& |4 k& x3 d. c7 t) {* y
/ }6 j+ Y2 _1 i1 k& c: d# p$ B3 v
3 I& g! @1 ^' Q: Q
然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
' ~: D$ |; [) C# l6 }: e& x8 y2 l& }! V4 ]+ x; U! c
% A; ?9 l, ]9 x) ^! G- V$ t
, n5 Q% Y2 R- `2 o/ g% c, a! V$ b; z然后转到NES内存的$8000对应的ROM地址:9 Q3 h* O+ G: j8 r
( f/ a0 l% o' O% P4 ~8 R8 \: N
% k) ]" O& ^, Y& }+ c5 M3 _
. x1 i" ~# ^. s2 Z& N1 X F0 Y% q/ ^/ U+ [5 r, q
写上如下程序:
9 U# T9 n- r/ q8 H" cA9 80 8D 01 A0 20 00 81 4 I( C1 q* G3 J6 h+ G* w$ ~6 V
LDA #$806 u7 F; j) K* f
STA $A001 可写方式开启SRAM
7 ` J. X9 P8 Q2 U8 a% QJSR $8100 跳转到子程序$8100. [! r5 E) j4 |, l; t# W" W
0 w n4 I; I1 \9 O然后把Hxd里被覆盖的程序复制过来粘贴在后面:1 C. o x4 [! K& x" e: \
8 g6 x) t1 ?1 C) b) @( E" K3 @0 q: E8 y4 \1 i2 t) `
1 ^! _1 I7 x2 ?0 O9 P6 ^
末尾补上一个0x60:
6 s, y. U! k( E" URTS 子程序返回
: M4 c: ]9 E# S
* t8 t8 M2 n% z C, v. E5 E然后单击运行,ROM音乐响起,正常运行:# ~: O2 R6 v) i
/ g; Y- d2 w I- h7 a' T/ O' r* [+ H% V( x/ I
然后转到NES地址$7000:8 ]! f6 _8 G. l
" L# | e$ L3 }6 v4 c
: t: \, [( r, L* ?: A! A
' m0 i7 l3 ^5 b, g9 y
2 Y! J- v1 R$ w- f5 |( r0 L7 [
& _) f( s, Y" s9 U; _9 \可以看到,$7000-7FFF都被复制了一片数据。 K2 [/ F! Y8 l5 \0 ^. ^8 n
测试没有问题,然后保存文件:
% b! J% F l/ j9 |7 x
% t! n, D' L% k6 a! x$ k; {以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。8 S4 G1 E) `8 p) K" m
后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。; c% [& a1 ^1 s1 f3 I$ X
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|