|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑
) m$ h" o! e9 ^) V$ h% k
" j& x. M: x% {( H9 O: ^% V! Y[FC][SRAM扩容教程(Mapper 4为例)]& T5 ]# Q4 }) i. k
6 X+ d( D( r7 H
时间:2017.4.28
9 O4 r% d, _8 Q2 ?3 R作者:FlameCyclone
- C0 ~. H; J5 A- J工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
/ F6 t) W \( u4 s# |; kROM:双截龙2(J).nes9 K+ V8 `: d) N7 k Q5 R( T
适用:没有使用SRAM的ROM
) n7 X* y8 Z8 ?8 [( O7 A7 T( k: |7 K) \* A! I
首先用Hxd打开ROM:
) O0 `8 ~' q+ G& _5 m& m7 f Z2 ^) m: G% f3 M
然后扩容:! i6 B& s" R5 i: o/ h6 d
* v- j5 B# [* d: W' O) `; j- b' y/ X4 K% j$ Z* \( z+ f
: b& `" J$ O6 P4 H" z7 G# a3 C% x
) M; J5 s1 a6 O: D! V. m1 {$ E5 O, a# J2 `
; L5 k% ~4 n7 ]- v+ J( d( U0 Z
7 V+ ]6 k/ P0 k: r( ]
( g4 D) ?; C( L. D! s* f+ A# F1 Q$ ^% @& |' E
先看看任天堂产品系统文件对NES文件的说明:: \" K2 |: M/ Q" [: I) ?" n
NES文件格式8 t6 d( s3 U& C4 x0 o- m) @4 J
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 1 W$ J! O: |/ N2 i3 ` q- w5 B5 r, s
偏移 字节数 内容 $ m8 E5 ]: ~# Z3 x3 J& E3 k: e0 I
0-3 4 字符串“NES^Z”用来识别.NES文件
' j& u, Q, U) K5 N4 1 16kB ROM的数目
. z$ t, R2 J4 t U( j5 1 8kB VROM的数目
" C0 a% G4 D" d# n6 1 D0:1=垂直镜像,0=水平镜像 3 c) y. Q8 a2 D8 u
D1:1=有电池记忆,SRAM地址$6000-$7FFF , G) J# @* X; W, |8 g* ?
D2:1=在$7000-$71FF有一个512字节的trainer
" } H, k3 M$ p, O/ N D3:1=4屏幕VRAM布局 5 E' K+ u' O6 B9 s c7 `. V) r9 M
D4-D7:ROM Mapper的低4位 6 t" G% i8 K8 L, Y Q5 P
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^)
; P/ H; h" u- u( V2 m7 }; X' z D4-D7:ROM Mapper的高4位 , r% Z+ u7 Q7 J! x/ }3 t( V
8-F 8 保留,必须是0 ) G/ n; o+ v& D
16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
8 ^9 `; F* X) E5 P; ^% S-EOF 8KxN VROM段, 升序排列 - r8 e3 P) J( |
8 @4 \8 m3 m! R: H然后知道这个ROM有0x08个PROM和0x10个VROM2 z& m4 Q) p: I) M \1 Z
接下来扩展PROM位0x10个:0 }+ t% v* g. ~& T# l2 A- ]
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):/ E# K7 y+ v, X) z" L
: W4 ]0 a) _' @$ d8 _& {7 r
由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:" ^* P& r$ k6 e) `
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。! B: H, C1 [! m2 s1 B/ ]0 P5 |. a! L
于是可以得到双截龙2的是:, S8 H9 J( }& _: y) I" u. T2 q" i% g, S
(8-1)x 0x4000 + 0x10 = 1C010。! I: @' y, z7 F! o- B
然后跳转到1C010:
! |- ?' j; ?4 }* }& b3 d. Y6 U1 }) F
" h. p% {" P( g; X1 R. b0 P+ I
D' M; n. t& s& n2 ?7 ~6 C# y然后插入0x20000字节的FF:
9 L" m0 {9 X. p! u: V; b
! V% I7 X- v3 N$ v: j+ e3 P8 J9 v9 R9 A8 T) q7 {" A3 h3 n* i5 r8 O
: i0 S/ |3 K1 V! J! q4 l然后保存:
1 O' ?2 M' s( u
8 C% e# k9 X1 j
( {! x- f& ^4 f% e) T- |4 n% E用FCEUX打开正常运行:
) z7 O1 O1 [7 `5 B0 W- w, u' }' |' F$ k
查看文件信息:4 o& N) t5 M% y1 H
$ ^: F9 c, `# {
% e" I; `& E! l d A接下来切页:3 j; k, L s+ ~
先打开十六进制编辑器:2 J1 J' N2 w3 b' T. r- T' r( S
/ k% }+ z/ d* r: a$ x
拉到滑块最后,看看重启中断/ w& g* I, p4 x
中断地址 中断 优先权 ( G4 M% }5 ~: ?$ G, R. s) Y
$FFFA NMI 中
# t- ?# H/ p/ W: J% d* Q3 H5 m$FFFC RESET 高
9 }1 j2 T$ }* h8 H% U: z2 V$FFFE IRQ/BRK 低 / `' E3 N' k0 d5 ?
: r/ r0 d' m: W& ]& [, Q* A( y
RESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。6 Z I0 l+ b+ v% f& R$ H
由此可知双截龙2的RESET中断$FF65。
" ?6 I2 H7 M. j3 s接下来添加$FF65的执行断点:
7 x( h: X7 R" r& R0 p打开调试器:
1 G( W) o5 G* |0 X6 k( Y! A/ Y# q. E, X" n
添加$FF65的执行断点:
, N! p: h/ I- n% v
]2 _$ \+ A1 J7 m7 f b
0 ~: |, i) b0 N l, P2 @
/ X; u0 w+ F, p" z8 @& R& e9 \9 Z单击确定:
0 ]# F% t' q: @2 M2 k7 j, E3 j: L6 U& j Q3 g; ?8 ]
. a# y" ? k8 j0 u# A6 o7 p然后重启ROM:
8 F7 c+ e w/ j% I: q( ^
" u3 ^0 _. M; x; z, i调试器此时弹出来:
1 u/ G; n/ z2 l f8 [5 q+ C, K! L3 U" r; c
然后打开Hxd,写一段mapper 4的切换bank程序:' ]) x4 n! A7 {% J4 Z3 T5 E% M
先看看mapper 4的说明文档:
8 W. x7 Q# I6 |" H* k9 A2 i( eMapper 4
2 ?- a: w/ A1 i" z1 [
! y( G4 a$ B* O$8000: 模式号0 A4 y- V$ L; J- ~) h0 H. D
位D0-D2:' o" g. q9 U) _) T
0:选择2KB的VROM存储体映射到PPU的$0000
* a: S; L: T/ g* a/ { Z" l5 S 1:选择2KB的VROM存储体映射到PPU的$0800
$ F% P0 I+ L- ` 2:选择1KB的VROM存储体映射到PPU的$1000
( L4 m8 R+ A. ]: Q; _) L 3:选择1KB的VROM存储体映射到PPU的$1400# s/ y/ G0 C' x; s# x% T/ A
4:选择1KB的VROM存储体映射到PPU的$1800
" K. w$ h: H; [; h3 p 5:选择1KB的VROM存储体映射到PPU的$1C003 B0 k4 K8 j# v+ Z/ Q1 a/ H
6:选择8KB的ROM存储体映射到$80005 H2 r5 v8 y1 b+ Y& {
7:选择8KB的ROM存储体映射到$A0006 E- M! z a1 @- a: E3 V& J
位D6:
0 p" Y, H* q4 P$ S4 A& f8 ? 0:允许擦写$8000和$A000$ k9 b2 \' h' h# E. X) Y
1:允许擦写$A000和$C000+ j* u$ m( O1 P! ?
位D7:4 n" j3 | u- X2 L; X. d$ V: |+ i
0:模式号D0-D2使用普通地址# Q2 C- [( ~) d- h7 @
1:模式号D0-D2地址异或$1000
) j3 T2 A0 D' L }: R2 `
/ d4 C6 x9 i- m4 P5 U( ^3 ]# N$8001: 模式页面号
( S: e1 w/ j$ D: o- P6 \6 a0 w8 ? 写入一个数(00-07),切换存储体到对应地址7 G. v* C% @ d
$ G8 Q1 i0 y- H* U: c. T$A000: 镜像选择
# w9 U$ E' {9 s 0:垂直镜像
9 _$ n9 M3 O, L7 y" n- J 1:水平镜像
+ @( J0 Y0 x* y& z: Y: U |" f* Q) h! M& e
$A001: SaveRAM 切换
5 ?+ i+ l& b4 [' a: v 0:禁用$6000-$7FFF
$ B: U* L1 V2 p 1:启用$6000-$7FFF! c8 M; h8 B" Y6 a: V
! `. ^/ T. v- G$C000: IRQ计数器
3 U9 A, L4 P9 P1 I+ Y' ~ IRQ计数器的值存储在此处
* N) s B# S7 v
# ]# w9 F# {9 y; F9 }8 Z0 Z7 d8 ^$C001: IRQ暂存器
7 N+ L& n0 a4 Y IRQ暂存器的值存储在此处
8 W8 g7 q% i, ?6 x
# d4 E& H* ~3 h' Y& \1 _ Z$E000: IRQ控制计数器0) x8 X: K6 ?$ t% O
向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ; o/ W; h5 k8 [$ S. p4 e
& P) o7 s3 E! E$E001: IRQ控制计数器17 p3 b$ I) W# z
向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来) ~% q% L/ t/ A9 ]% o% A
/ v% M5 [8 P8 j- X `$ q q4 m# k* v
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:( O3 f# l' B- u0 y. R5 D" Q3 D# A
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 681 J1 }3 C- r. x2 q* e/ I+ l
PHA 累加器A入栈% N1 N: W5 Q9 N, \
LDA #$06 设置切bank地址为$8000-$9FFF9 Y" W! x: j* z I' v4 ]( K+ P
STA $8000
; F8 T: ?% R' R6 gLDA #$0E 将第0x0E号bank切到$8000-$9FFF
' M; u. x$ m4 |STA $80018 u* Y& `( s1 I' ]$ b8 F% M/ O7 L
JSR $8000 跳转到子程序$80005 ^& S' f2 p# ]+ s+ J2 X" }
PLA 累加器A出栈
\1 q% ?3 k+ E6 D, h: L6 A* W( }4 o1 J, z
为何切换的bank号是0x0E呢?4 I; [2 K4 P4 C4 D! V0 q$ C
因为在扩容ROM后,文件PROM结构为:
& k$ }( l: M3 U4 I( C7 m原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM" b2 i% N9 ?1 u$ D. k5 d& c& m; W
( c. y$ F( L& I# UMapper 4切bank一次是切8KB,那么文件结构就是:0 c9 M1 l, K) I3 I, E
原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
8 i: {9 E' Z! }* F- z因此选择扩容的第一个空白bank就是0x0E号bank。6 F* I% h& X0 M6 ^0 d" ]* g, p* }* ]
- ]. u2 x: b3 f6 K% [, @" r# ? K4 [1 l; }1 c7 P
然后去调试器找RESET中断中可以放下切页程序的地方:
$ q Z# n5 C8 ?( m( K首先长度要小于等于自己写的切页程序。5 J3 G' r+ q/ I* L' i
可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)9 J3 ? ?4 |. S' R" s
% t$ h& P. o. S) e
然后跳转到$FF7C:
* b; J5 S5 P1 Q; ~8 o' F* g+ R5 C! X/ A- O% G4 p n
% D. F+ i1 I& E9 \7 K! k4 g5 g$ h1 L) V- _& b/ I2 i) U) c
6 `5 D% ^7 E' @/ ]& ~* f复制下可以被替换用于写切页的程序:
3 Y4 N7 k. X$ B6 S5 `7 W6 Y/ K先选中,再复制:
6 M5 p* y, Q( I
0 d9 u+ {9 E0 Z3 i& W1 q, p/ o% u6 P2 l& Y2 M
在Hxd中新建一个文件,把复制的数据粘贴上去:1 t6 Q L* B8 ~! ]0 i& N
; C; ~. G3 }9 E7 ~4 m5 l. q
0 x9 |* q8 P9 D; s0 L" Z0 t
" }. `. U2 e1 H4 s/ Q* e5 b2 z3 H8 {. b4 W0 a
然后回到十六进制模拟器:: n/ U* ?# t9 i0 W: ~/ E
转到$FF7C对应的ROM地址:5 ~0 \/ G; }! P6 ^7 D7 F
* ?; c$ F5 G0 ^/ r1 A' v% Q4 R( j. s. n/ N) _9 K
然后复制Hxd的切页程序,粘贴到这里:9 b6 u2 R% x8 z0 x5 }, G
' p S* Y$ L/ ? s6 k% Q
0 e7 q8 |, ]# i' O! }8 y0 u% Z. V1 X9 t/ t+ ^
没有覆盖的用EA覆盖:
! b/ `0 Q* M7 q( {! x+ I! y6 c; ^% x* X& R/ `
打开调试器可以看到变化:
0 ^- c# s( Z6 O
& j, o) W$ z! U然后添加$FF7C执行断点:
; z/ Q, O) ?/ X% V, M+ j0 l/ ?: c/ K* `8 V3 l y7 I
, i3 _$ U& u$ Q) B6 }9 G
9 Y8 Q5 d: r9 E+ E2 X单击运行:) N5 N) Y d3 v6 s
然后程序在$FF7C这里停下来了。. N4 ]# Q7 m t3 d+ B8 _8 l) C
: \ L- E. W( O' L; k
然后单击单步进入慢慢跟踪,直到跳转到$8000:
/ K) z4 h) w& F* n. B; g# x7 B: @! N s2 I4 G
然后打开6502_Simulator:2 E4 j! i5 \8 O, H' Y' |" J
9 I/ t$ I, {% ^+ J% P. R1 w再打开我写的数据搬移程序:
, Q' F" t! M5 T
' ^" [" L9 u7 B) ^ L' x" V1 ~. H3 N; u3 t- R" _9 Q
然后修改对应的数据:6 N! X; l- _1 m% H0 L
程序开始地址:修改比如$8100就修改为 .ORG $8100
. j/ ] m/ u& W: W复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
0 c$ B6 ~! t* _) S/ E7 P从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82 [0 y0 S" Z, q) K @. e* {
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91
4 G1 _% C0 [8 q4 v a8 \% s1 e也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。
, Y" G" M* E$ }如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。0 R+ A9 X4 Y; @$ q% l7 s9 A: r. q
中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。
V" L2 t4 e% ?* X后面的不用管。- `; b7 ~: A; Y0 E- x) m' ^ \
) w( H6 K5 \, v6 B3 D- c3 r0 [
设置完数据后单击编译:2 S% A. n0 L. r" M8 D
$ \* c& _ p- |然后保存编译文件:7 J" y* ?' D' U8 W# L
4 g. d* E! O+ D y1 R选择二进制方式保存:- d( h2 P, b& q1 D5 r
' V/ z* l7 o4 X# d1 p: L9 q+ q$ k9 Z% r6 p1 U
用Hxd打开保存的二进制文件:
( c, q% V9 ~) u5 V M8 W* b s" G$ a. y7 w' y3 r G0 `- {
( y; g6 R# g* M$ w/ N
跳转到设置的程序开始$8100:
- m1 G7 \" F' C% t
o5 m% t. R: ~
1 H& a% V- a, N& D
) l4 E8 O9 L& l- I- `回到FCEUX,转到NES内存的$8100对应的ROM地址:
5 N7 z5 W9 @3 W; \0 d0 [4 v8 o: @" x& l
& a8 a* `% ^! A) j% h
2 G3 p1 \8 _% R" W8 p+ R# v6 b# H$ _
4 n0 q3 i2 Q4 `+ L: W$ q
' _( z+ U8 r8 ^1 j: q5 a% d
然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:# A& r% p. O! s0 u4 H0 F- m/ R
3 S4 K" d: V1 Z( R$ @- b- A
4 u z/ I7 r& W. q$ X- _
7 p1 \$ |0 [3 p/ I
然后转到NES内存的$8000对应的ROM地址:+ W+ c1 w& _9 r4 c" b
" Z, X/ s/ m0 a: q0 X* O2 {4 N6 J9 A
+ w4 s! L. R/ I* L
1 I! b# B. M* _, H7 @
" `$ D3 r3 y/ e写上如下程序:
5 ` M- r" m1 Y: IA9 80 8D 01 A0 20 00 81
4 j5 r+ x: e3 Q8 W# W' R3 V: A. S% ZLDA #$806 j* [" `( [* O* r3 w4 Y
STA $A001 可写方式开启SRAM# [; A4 F9 L2 V6 C* o% r( Z
JSR $8100 跳转到子程序$81004 S6 l; ?1 Q2 W$ e! ?: e3 m
5 G% c5 k. m4 G; }; i8 a6 R3 W) P1 ?' k
然后把Hxd里被覆盖的程序复制过来粘贴在后面:* A$ T1 Z) {9 } G2 T
, M) i% ]0 L) G& p4 B
F2 Y5 B& X$ k& @0 \8 |' v2 e1 p1 F$ g6 S
末尾补上一个0x60:
j& S; O0 j& j$ n1 WRTS 子程序返回% \0 }6 z/ p3 m3 c _( s
) h/ Z) P9 [$ k0 S: P
然后单击运行,ROM音乐响起,正常运行:& j: E$ a5 ?% c$ j- a
0 [: h( f4 y: P K& D1 ?
Z5 f( v6 ]+ W( T# I3 Q然后转到NES地址$7000:% D* |* Z& V" @/ k0 `
% T# f; f4 K) J# T$ ~
* v9 Y6 }; ]* G0 Z A G; L: a7 o8 c
2 ]$ M) \" y. l7 u/ c6 X2 ]; i
Z; |" I" o2 p( t1 P1 w6 ^8 b
可以看到,$7000-7FFF都被复制了一片数据。
% T8 z- b1 u! c测试没有问题,然后保存文件:
$ _* g6 ?& [ i r6 G/ d' u a4 A0 l4 B: L
以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。0 h, H1 C- k8 @5 i- r; x
后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。/ S& G& N% L/ F# d9 @" d
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|