|
|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 ; a/ o/ `6 o7 n4 T! A/ N4 B" k
5 R' R$ b- }7 ?; ?) Y; y$ r
[FC][SRAM扩容教程(Mapper 4为例)]; s9 s& ~8 X2 h
( s8 `; C2 P& U, H
时间:2017.4.280 s2 M) D: w8 L9 F; W
作者:FlameCyclone
0 @% @5 w' S- B/ g工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator0 b y9 D7 W+ s
ROM:双截龙2(J).nes1 M3 q+ W' @: D3 a4 G; O5 \- `& s
适用:没有使用SRAM的ROM( g6 ~) S, _( r& G; e% L! u t
6 Z9 F$ K( x: K: e/ i V2 {首先用Hxd打开ROM:6 M N# H7 j) L. F* f& h
7 {* Q. T7 f1 h9 \
然后扩容:
0 Q* \* x7 w. N5 g& h
! Y- C% E. l* R& ~; B+ v
, i. _: a4 x- E/ q6 K' L0 `& | z* z' s$ E
) L5 Q2 p4 m" w. f
) f5 h" b) I6 s. U: w+ `
; L, U; f+ L9 @9 |! U, D: v# v/ J
* [$ v/ ?5 j! G( t4 n' F) H# A
- {/ L4 q$ j' R E3 L3 x9 ?
先看看任天堂产品系统文件对NES文件的说明:
d& W3 A6 p6 L* O; wNES文件格式
- C: v U* ~& J4 U.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 % H: K- l+ r. b2 W. h0 B
偏移 字节数 内容
) e, f( \' e0 f; j0-3 4 字符串“NES^Z”用来识别.NES文件 4 |7 n0 [$ y, m' G2 Z# h- b" f
4 1 16kB ROM的数目 % H% u3 T: Y" z$ J
5 1 8kB VROM的数目 ; L" L, l+ e( t/ V
6 1 D0:1=垂直镜像,0=水平镜像 2 A1 u6 U' r) ]. o' ^7 N- T3 c( o
D1:1=有电池记忆,SRAM地址$6000-$7FFF - g* N% k3 c( b A% ]4 L8 y: a/ K
D2:1=在$7000-$71FF有一个512字节的trainer % p! n6 _3 x M: r4 |9 H
D3:1=4屏幕VRAM布局 3 O# ^( T+ I/ ~ F
D4-D7:ROM Mapper的低4位 8 H( B. ~& |0 z5 v6 n0 _7 w3 v
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^)
( H6 y/ b# h6 k7 A1 V4 v- A$ ` D4-D7:ROM Mapper的高4位
# ~. l. H) z: M+ i7 H$ g8-F 8 保留,必须是0 - |4 x P( B" U, H" _. j
16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前 & r8 ~* X$ A7 S N6 f6 G( w( E x
-EOF 8KxN VROM段, 升序排列
+ h5 V& j! W9 C5 b. ~5 ^+ x" i& O: J7 z: X
然后知道这个ROM有0x08个PROM和0x10个VROM
+ s7 y: S' z, a- x( z1 M接下来扩展PROM位0x10个:" B" W1 G+ j9 o. P/ x+ S& I" P9 Y, |, y
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
" @- f6 c* M- R7 o 6 L7 q8 _& E5 X
由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:( B8 f% C) e* B. @
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
/ b0 N( ?9 k( A1 Y8 A. F于是可以得到双截龙2的是:* a% H/ I$ n9 i2 s& R
(8-1)x 0x4000 + 0x10 = 1C010。+ I$ M+ l7 r H% W: S1 s7 U4 ], k
然后跳转到1C010: {- s1 s4 v$ j& C8 S/ ^

5 h% g" T' t# c& [& @+ {/ z
* x8 Y2 a& x. m9 `! I7 G
+ h# A, R$ w9 p8 r0 h2 g然后插入0x20000字节的FF:+ F4 I. U' R) W3 I$ W( k! I
0 z0 O; ]7 {) w o1 Q

J! ~& Y" M6 q2 z; L ( g6 t+ n& ^- i. U4 C
然后保存:1 j, G0 ] t: p' G
: c+ g7 L) H3 b$ @0 t$ M% K5 F, e
4 {- P& O7 [/ y3 o& D0 \. j( j
用FCEUX打开正常运行:" y; z( N+ F. N# r4 H: @% b1 |# M. s
* T) i' K% s, Y5 x* A( [" e
查看文件信息:# r8 [# `# I0 [2 A- E0 l* P$ g! l

9 n: `# c1 j9 t2 ?
! T: N4 e3 A" [( ^7 R接下来切页:0 K: {: g6 U g7 i% ^ [; T# ]
先打开十六进制编辑器:2 F0 n: w$ D/ z' `, [" [- _* w
: L! ^3 [$ q/ C o
拉到滑块最后,看看重启中断% e4 |0 d8 y4 t/ |6 v
中断地址 中断 优先权
( `# `. o K3 i; w4 h3 W! ?, K' \$FFFA NMI 中
+ C; w4 j" {+ C$FFFC RESET 高
; j. V6 s( V4 |) _% A; j4 C4 u' u$FFFE IRQ/BRK 低
1 \& Z& x/ f' a3 g" Q; o- }! L2 b, o) \
1 b* s b5 c! lRESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。5 A3 `0 \* U% S$ C9 ^ k+ _
由此可知双截龙2的RESET中断$FF65。
) s2 ?/ O4 f5 S4 J) r- [) k9 j接下来添加$FF65的执行断点:
4 g f5 R* C/ {1 C8 C3 M打开调试器:
5 T5 N" H3 ]& l; r: ?
6 F! l" _2 G3 H0 Z, y9 Z* M添加$FF65的执行断点:% ^. n$ A8 Q3 N' T; S- y5 f

- t: X$ e7 Q- i' d & j; e: f/ p) c
8 H3 p6 w7 b% [4 `, P
单击确定:0 ]5 ]) O8 r& P3 G0 a- n
/ z. O3 a* f; V4 z: [- @
9 x5 p3 ?2 \/ m+ Q& W u4 j
然后重启ROM:
, c$ _! @$ T7 r
4 W# b4 J7 v& Q8 z1 R8 D% l1 n' N" d调试器此时弹出来:
# x5 e. E9 q2 j/ h ) c" v: V/ j4 u3 H6 B( b
然后打开Hxd,写一段mapper 4的切换bank程序:* G( X; n. U" e' K/ E$ D
先看看mapper 4的说明文档:6 }/ A- l, O( |9 t* R# s* ? f0 h
Mapper 4 I9 b6 \& t3 z
/ V% s$ ~5 C& K( {
$8000: 模式号& B8 R* o2 I+ |% t9 r
位D0-D2:1 y0 a: x9 T# ^; B8 A
0:选择2KB的VROM存储体映射到PPU的$00000 u; v% i0 Y! M+ S7 ^% f- u
1:选择2KB的VROM存储体映射到PPU的$0800" N9 @5 a2 E" }7 \
2:选择1KB的VROM存储体映射到PPU的$1000) o- H" J( \' g4 O! `3 L
3:选择1KB的VROM存储体映射到PPU的$1400+ f' J4 b! y8 c7 G3 N8 t- |
4:选择1KB的VROM存储体映射到PPU的$1800: m* D/ ]. Y8 K
5:选择1KB的VROM存储体映射到PPU的$1C003 ?2 z4 L+ e3 D i# I
6:选择8KB的ROM存储体映射到$8000
* |" l8 T8 C! d. T# }, ]; {# A 7:选择8KB的ROM存储体映射到$A000; H( @6 i4 q9 S3 b( Z
位D6:
2 u! |; z- @0 t' p 0:允许擦写$8000和$A000
. b8 }8 O1 y7 v 1:允许擦写$A000和$C000
7 v# y0 f9 p) f$ K0 H 位D7:% b2 W* q! r7 Q4 Y3 e- f$ Y9 Q
0:模式号D0-D2使用普通地址+ h( a+ @3 L! W7 p6 u7 d5 h
1:模式号D0-D2地址异或$1000
" i! P: s. a# ~: h8 H1 w8 F
5 K3 |- k# e2 w' M" s$8001: 模式页面号" b0 U8 r6 M2 p- A- B+ j
写入一个数(00-07),切换存储体到对应地址- A: m& `( b+ U* ~) i8 W4 h
( T ~5 B1 P6 G
$A000: 镜像选择
' O) s+ b, j# _ 0:垂直镜像
' a* N8 y0 {) E1 E; Z% U 1:水平镜像
3 r* m9 v* ?- I# c# }
X! T2 c3 F8 C% U. f. H$A001: SaveRAM 切换# u4 |( I' U4 g( O" e
0:禁用$6000-$7FFF- E9 I. y; M( m# e8 {$ t+ D
1:启用$6000-$7FFF1 n( V, K, V! i' H1 ?
' {/ ~; E. v- M3 L$C000: IRQ计数器0 \& |/ E! K# L3 h6 m
IRQ计数器的值存储在此处) y. a& ?) K5 t! X5 }* P
; L: E0 ]# I" q2 T9 a, f
$C001: IRQ暂存器6 C( }$ i5 x: c n
IRQ暂存器的值存储在此处
; V0 Z" D) {3 H R$ X
4 p, n* ^4 W( C% ]; g$E000: IRQ控制计数器0 |" Z6 v9 C4 V" A. U" p( J8 q
向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ+ }! ^; h& z2 E7 s/ q! p
& `/ _& k% a; {; R* I
$E001: IRQ控制计数器1! [' P5 f4 ^, O3 |, u8 e
向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)
* j" J3 Y% o2 g
( i4 C6 L, }; `那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:2 _' I: b% }8 K
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 689 X/ w- h) }# R; _4 @& d2 T) W( N
PHA 累加器A入栈. f. g1 y/ I4 Q- m
LDA #$06 设置切bank地址为$8000-$9FFF
7 b5 c" {* I0 G+ a( g. L) _STA $80007 s# ?) r, c& I8 P& d1 e
LDA #$0E 将第0x0E号bank切到$8000-$9FFF' i n/ M/ Q3 T! d
STA $8001& `+ b1 L5 ~7 x. t1 M9 x* \+ G" t7 F0 e
JSR $8000 跳转到子程序$80002 W+ ]$ F' M2 C$ j
PLA 累加器A出栈
: i3 B n# T% O, m' @
7 c( g0 A5 ?$ P; E. A q+ l' T( E; z% u为何切换的bank号是0x0E呢?$ e1 c8 ]% S! _7 g) I4 F
因为在扩容ROM后,文件PROM结构为:
5 {* j6 E4 \- |$ B2 O2 G, h6 D原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
5 P! o2 Y$ _" [$ Z
5 V! F$ R% b6 [% P# l1 i4 b8 pMapper 4切bank一次是切8KB,那么文件结构就是:
; Z. z6 u2 y5 O7 D$ R! w原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
2 E/ M3 N; @9 j4 H因此选择扩容的第一个空白bank就是0x0E号bank。
# [. W9 }# N* o" ] ( x) }+ w9 A$ W" o* i) r
) g9 F! t- G+ X/ Q
然后去调试器找RESET中断中可以放下切页程序的地方:' O1 c$ f$ z6 I0 b
首先长度要小于等于自己写的切页程序。$ m( r* {* \: _4 |
可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
8 z: g/ b2 d7 H& T6 U ! C+ B3 k5 W2 q1 l3 i
然后跳转到$FF7C:
v; I6 m1 j" V' A5 J4 a
5 g8 w3 N( P W- o
( H0 R- J# }" V7 c' V# o& m* }) n - S6 A" P% s G, I# y& ?4 r, W* [
7 c5 w$ w9 ?+ a' y H% c. d, X复制下可以被替换用于写切页的程序:* w. ^- V/ W: ?
先选中,再复制:, q6 Q9 v/ t: l8 Y* e

: f) y* b$ s+ r4 l& w % ^) g! c T* W' k
在Hxd中新建一个文件,把复制的数据粘贴上去:
% i# B7 Q6 x) w2 S) p6 { 4 P& u3 n5 c/ d6 ~

}/ T: ~ [, e1 Q, P$ `1 I0 O; P 6 s# S8 I% |$ w% E' y( f
# q$ s3 u2 Z2 L5 J+ E$ @
然后回到十六进制模拟器:
, T5 H) E D! z转到$FF7C对应的ROM地址:) @& m: z; ?( U- y& |8 y( T

% z5 }6 h2 d; f4 L, S. U0 v7 Z( Y
! e9 Q# v" z& f' w然后复制Hxd的切页程序,粘贴到这里:
4 r. j. R3 K- @' Z! Q; j
( }; b( v4 O. y, L, d
. h2 v- r6 S5 F# ]) b
0 |; Y% ?0 _0 Q2 G6 i4 N没有覆盖的用EA覆盖:: G3 b, {- F) q r5 j
$ d9 x$ @/ S ~( h. w
打开调试器可以看到变化:
) e$ K6 J& s2 P7 G& K( _
2 v" P/ {1 k, D( s! M& M: u然后添加$FF7C执行断点:* ?+ r8 n; C4 D3 X
4 i1 ]$ u* L3 `! `( }

: {/ o0 q5 K' V& C 3 `2 L/ H6 Q- i) @) o. p# x! Z. T
单击运行:& \$ f& y/ [( h4 J% e
然后程序在$FF7C这里停下来了。# Q0 F8 g; }8 R8 U
" U/ g& L# k7 [2 v
然后单击单步进入慢慢跟踪,直到跳转到$8000:
0 n- r/ R- T8 r) b+ O0 J+ }
2 i' N) {+ G9 B" S3 m% j3 F- |然后打开6502_Simulator:3 x; S1 a% J: @1 [

/ \# a: R0 d, U7 K+ u& c' ]再打开我写的数据搬移程序:
+ y Y) b5 j* c Q& A5 z! @, f 2 _; n8 m% Z% X' R- ]; F
" u# L5 g$ A/ p8 v0 R9 D4 J+ M0 m
然后修改对应的数据:
' V* Z% ^1 i; U程序开始地址:修改比如$8100就修改为 .ORG $8100
1 t* l1 H# R4 ~6 g复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70; ?5 E/ |& m& e. m e' j# _
从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82
4 N% t/ j ^4 f; {9 y! z5 F1 I6 s想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91
. w5 X* [" m# M' H9 g也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。
' _" r/ ?# d" P+ U5 g如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。
, c* U+ c# h6 O* Q- I0 A中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。1 d, h' Z7 y9 ]/ @2 t, q% F$ c
后面的不用管。
( Z6 j! T% }" ~+ d W8 Q* c
/ B3 m3 H" ^ m8 L2 e+ h设置完数据后单击编译:4 f$ i* }% h% p3 H/ W
' z0 C6 t9 y: z, J6 v! x' o4 p! Z9 n# m
然后保存编译文件:9 n0 L8 i' A1 L3 n2 X; L
" E) H4 m5 q5 D
选择二进制方式保存:
# X) \6 [% w- i8 Y- c7 P5 ~
7 {& b* D$ l2 ?6 l& L
2 q5 \& p5 U( Y. e Z& v用Hxd打开保存的二进制文件:; k: o6 x1 j( c+ k$ v3 V g6 v1 J

" H' Z8 ?) Z/ f8 c! o$ Q
# ~3 A3 N' y: C- a7 I; }跳转到设置的程序开始$8100:
& _# T' K2 {4 J" I% }, [ r% N
5 \! Z1 V2 r! K ! T3 b3 V; c3 t8 t2 B
- y. r' [! M/ c* q; e( _- l
回到FCEUX,转到NES内存的$8100对应的ROM地址:
( G7 T) i& U, M : t2 x3 s' N/ d0 G% |' W

5 C9 H6 `+ v, y7 p' z, ? " N2 a5 V2 Z5 N7 d
* ~+ M* |. \1 [6 S" S% k

+ o1 N6 }, g2 I/ M) H# z% W然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
1 {+ y% r% `2 _5 c' [ 9 p1 Z! ]0 Z8 t
$ [4 q+ X \" ?% K! _' c

' k3 T% z p* ~4 T9 t* U# x" @然后转到NES内存的$8000对应的ROM地址:
! @- J; j. A- ^0 G- m) |# w. F
) _1 e8 N0 Y9 S: T4 u$ M) p 5 ] W( [* {( y. l v5 h
% n7 C. A1 t- F0 }( y# _
7 o; U, j2 L6 ^. ~! R7 C a3 C
写上如下程序:) t/ U9 T+ u- x; g
A9 80 8D 01 A0 20 00 81
`+ u. ~9 c4 F) d t1 C' O l. GLDA #$80
: c4 g6 ]$ g/ ]- g* N, {STA $A001 可写方式开启SRAM
$ |. T% r* i% @6 v. ?JSR $8100 跳转到子程序$8100
! } n8 `- i3 x% P3 F0 |1 l2 n , X5 w' R7 R' M8 g4 h5 z0 d
然后把Hxd里被覆盖的程序复制过来粘贴在后面:
5 R8 v- m2 k; W 9 H+ h* F& z$ Q5 R
: Q9 a: u' P0 m: n P7 X
q, L3 K: H9 e# d
末尾补上一个0x60:: I! S1 {( S" t2 ]5 W g& z- q% h
RTS 子程序返回! \& T" X4 {4 c% c7 L
+ \$ l! {( }$ r1 {# t% Q" k/ `4 @
然后单击运行,ROM音乐响起,正常运行:
3 M; ~ t! A9 V* w- C% I
X; S \2 F2 ]) n
! Z1 A/ l7 u; j4 v5 D* m% |$ e8 |然后转到NES地址$7000:- |# f" ^) p* M: u2 G# z8 q+ i
' d% y4 D& P% J( c0 ~

# _8 f* O5 @) c & ]" a' a, f9 L/ _, m) P/ a, S

0 `) D+ m' y; z* z% | y- _/ _ 3 {0 i) N* c, V+ G/ j- _0 S/ y
可以看到,$7000-7FFF都被复制了一片数据。
( Z3 k" a$ m& v$ \8 L测试没有问题,然后保存文件:
% ^( C% N" y- }8 |5 ~- S! g( e 8 v6 l- L& y! ]
以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。7 C/ S2 i5 ~, e
后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。
4 w6 X A* h- [" | |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|