|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 ) G, O3 W5 O$ u( m
d: @5 T: @& c* c! z+ s9 E[FC][SRAM扩容教程(Mapper 4为例)]2 @$ v, d* a, m7 ?! O
2 i2 u6 S( q, m% `; U6 q2 z时间:2017.4.28! a) @& s) Q2 ]5 C ^
作者:FlameCyclone5 m* N# i- [1 U N! _( U" O- i! q
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
5 D8 X5 [! W$ z, GROM:双截龙2(J).nes
1 S! @9 ~5 l0 Q5 H' h" w7 H, Y4 I- j适用:没有使用SRAM的ROM8 i. F; s6 q4 ?
; G8 J: n1 P0 T. f( J" V首先用Hxd打开ROM:7 h( {- }: o2 d' O4 g9 M4 m
" [6 S0 P) i% `/ m8 y3 x然后扩容:
0 o. e, b+ h8 T8 b B0 C
; F! V. |6 `3 C' y0 A. R
x# t" K# s9 a# o+ r+ _. I8 L+ }! E0 j, G- \: o& Y. `: ~
' E% ~' C9 ~% o& F
; N& Q$ W$ c1 |
3 k" i! w) x9 a$ a8 i! r
2 Z6 ^8 ~4 c7 i/ ]8 Y0 {" |2 ^9 R
9 e. T# a+ a/ _0 ^# k1 z
- C3 Z$ Y' B$ a6 t; [先看看任天堂产品系统文件对NES文件的说明:
4 Q* j4 J3 O8 X. SNES文件格式4 B) s% M; k" [" ?/ o* U# N1 M
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。
$ S( D. ]" V N, s偏移 字节数 内容 : `) E0 j; [0 }2 U7 i
0-3 4 字符串“NES^Z”用来识别.NES文件 / A7 D2 M; P5 q+ H/ c7 A& ]
4 1 16kB ROM的数目
) A" W, o# C" v2 b9 V' S" f, Y5 1 8kB VROM的数目 ! c0 I: M5 P1 s& v7 e( b$ y
6 1 D0:1=垂直镜像,0=水平镜像 " l" H$ W* d: v" M& T, M" N
D1:1=有电池记忆,SRAM地址$6000-$7FFF
1 i' }2 `1 s. a+ _, J$ K; Y D2:1=在$7000-$71FF有一个512字节的trainer ! Y5 |' R( h- X9 X
D3:1=4屏幕VRAM布局 ) z f4 K" ^# `# H% W/ b# I
D4-D7:ROM Mapper的低4位
0 H# ?( U. ]" s0 c7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) 1 O6 v: G0 b% I( {! L; v
D4-D7:ROM Mapper的高4位
- C: m2 X- N9 _8 _8-F 8 保留,必须是0 8 K. P/ i# \6 d4 O2 j
16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前 : o% v7 f$ U& U# g
-EOF 8KxN VROM段, 升序排列
7 q8 |5 A- b& p8 T: D3 L% x+ S) S! C
然后知道这个ROM有0x08个PROM和0x10个VROM
x# @' [0 V' p' ~接下来扩展PROM位0x10个:
$ o0 L6 \/ b4 ^# b! V' P4 d& ^先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
0 \$ n- d. Q. z) Q7 {* G
/ G$ `& R9 O* w) h k4 p由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:8 @/ U8 ?+ F) H1 O& o0 C
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
+ j2 |. h3 @8 a1 B0 S3 N于是可以得到双截龙2的是:
( J5 q; N- u) T R) x4 F(8-1)x 0x4000 + 0x10 = 1C010。
* l. j8 a! k) @5 J然后跳转到1C010:/ ]5 r4 G+ D# _( W" }! J" N
5 O0 G+ @4 P# |, M: j' K
& D- \9 V+ h5 M& _9 ^2 J
. ?( u7 d9 K: q4 Y8 c然后插入0x20000字节的FF:3 r6 S) N$ f" ^% M
8 m% ^( g7 y' ~' j5 K4 _1 r; c
* B4 r# e: z' D- V: l$ \+ [* Z7 S3 d1 `
然后保存:! ^( O2 w# i7 ]+ q" s
" u6 T: ]6 I6 q S G" d
, e' _1 L& D! D1 [9 y3 ~
用FCEUX打开正常运行:' ]$ N1 |( Z$ ^- [* C
( `3 V& a& B/ F# ]' g
查看文件信息:
! ]8 W; ?" w N: X' N# m
9 E; n3 l- p1 B: _" t
! }2 B5 D6 d* u0 u! t6 u接下来切页:5 t# d7 u8 q0 ~( {2 T$ m
先打开十六进制编辑器:7 H( J2 N+ A. g/ |' ~
1 c/ ]' i7 O0 C3 {拉到滑块最后,看看重启中断" U, m' @: m: h, U/ X
中断地址 中断 优先权
|' o) [2 \9 n8 B- e7 _$FFFA NMI 中
8 t5 u5 X* G: Q+ L: m; I. ?" t$FFFC RESET 高
; W# g" N# h; d, ~$FFFE IRQ/BRK 低 e, C" k+ w# I4 x9 @. D
! J- \% T5 G9 o0 h$ ^3 f6 ~
RESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
2 H; h5 f* Y7 z# X/ E$ Q由此可知双截龙2的RESET中断$FF65。5 X9 ?2 `; F5 D0 ^! s5 A
接下来添加$FF65的执行断点:5 b+ M) a; h& S2 c
打开调试器:
' m1 [; t$ K+ E1 i# i. K F7 M; R# K
# b) w, d! U ~" d! U添加$FF65的执行断点:! c* g, H( r% ?5 N( z
, Z$ D9 P- f# F$ D' C% B- b% c* F* C/ o* s
# c" ^, x. Q! b# D+ b0 w; ^单击确定:4 Z- q1 ]8 {6 i" \+ H- p
) n: W% V. p/ ^( s/ T: \
: F' {/ J) t; U然后重启ROM:6 d( V# h/ N, \& x; {
, I8 B H! R2 ? t2 G' k调试器此时弹出来:* K' _7 U' x, \& p
$ K$ P4 ?* v5 N$ _然后打开Hxd,写一段mapper 4的切换bank程序:
/ y( G( g8 N5 r/ i8 r$ D! `+ V先看看mapper 4的说明文档:4 ^2 ^3 o$ d. y% U+ r$ @6 `4 o
Mapper 4
2 l, q6 |+ {: v2 _ r
+ ^5 s6 b1 V! b: V9 y' x' a9 r$8000: 模式号3 k, ^2 H6 g* Y- b
位D0-D2:
6 R% k; m& U/ Q- i& z 0:选择2KB的VROM存储体映射到PPU的$0000+ v/ n5 U3 U* I. ] B) ~
1:选择2KB的VROM存储体映射到PPU的$0800
) S1 y" j6 K7 U: g" B! ^ 2:选择1KB的VROM存储体映射到PPU的$1000
5 G! N6 D' ]/ V F2 q! e 3:选择1KB的VROM存储体映射到PPU的$14004 r) d+ N3 o1 G
4:选择1KB的VROM存储体映射到PPU的$18002 t" y* b9 ^8 C1 j' J
5:选择1KB的VROM存储体映射到PPU的$1C00
/ ^/ f" w' R( Q9 I3 i, ^ 6:选择8KB的ROM存储体映射到$8000
2 G1 D) S. V+ a# a1 D# j5 w 7:选择8KB的ROM存储体映射到$A000) N" A5 l0 ` w! l, r! n/ D4 d L
位D6:
% Y3 y4 w/ T6 J! | 0:允许擦写$8000和$A0003 u% M3 ^2 d* L G% B
1:允许擦写$A000和$C000
, ]1 g; s3 a8 H! h% |, n9 I 位D7:- O) V2 B- X% f0 z4 l5 z, a
0:模式号D0-D2使用普通地址8 f u n+ C- g
1:模式号D0-D2地址异或$1000
; f7 E& L% ]6 y8 E6 q
5 l7 d( w) w1 }9 ]5 \$8001: 模式页面号
) O( `3 c3 n: k# c 写入一个数(00-07),切换存储体到对应地址
# F2 T0 n( ]. I5 ~& M
0 f P* R# b( V2 }8 N: w* x' ]$A000: 镜像选择9 ~. U$ s O7 g% {* i
0:垂直镜像! R! H* `( K6 V
1:水平镜像
6 [1 l) H& \! i0 ?' S- S
# T& a: Y) x) C" ^$A001: SaveRAM 切换
( U9 Z7 C8 t. d- M! o4 U" ~. m 0:禁用$6000-$7FFF: b/ o$ `; R( u1 V9 f# m
1:启用$6000-$7FFF+ o5 d; v/ b$ I; H. ?
, F! ]# y% \0 O; L$C000: IRQ计数器6 z' c: [" y9 }
IRQ计数器的值存储在此处 a. N/ u, O3 d8 ]* r
3 p6 z0 m G+ g5 N! ]# J1 ~$C001: IRQ暂存器8 z |+ r x: [7 G
IRQ暂存器的值存储在此处" X1 ^+ c% U' m L1 a& b$ r
" E* a8 `5 Y2 h( [7 [3 S$E000: IRQ控制计数器06 _* u1 g" o l$ d
向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
1 {8 {9 I2 P6 A9 S3 y9 M8 s2 b2 j: B) x3 ?6 P
$E001: IRQ控制计数器1$ _- l. ]: A1 S0 }4 ^, ]
向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)
6 E& J6 G3 }9 W6 b3 S+ Q+ w V* K/ [/ Q( o% K
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:
6 r: p% \2 q3 R2 S- {& p! Q48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68' e; n( O3 r* G e& _1 }# t
PHA 累加器A入栈* z, S; K% t% T; i
LDA #$06 设置切bank地址为$8000-$9FFF* a$ j# O0 I( M* J2 d$ J; |+ H2 R" J
STA $80000 Q9 u0 U) F7 p0 h% ?4 M0 A
LDA #$0E 将第0x0E号bank切到$8000-$9FFF* S: j1 j; w9 i0 {& A3 G. M
STA $8001* ~# _3 f1 z+ e, s3 P3 V& N& l
JSR $8000 跳转到子程序$8000
$ e I+ N! R9 PPLA 累加器A出栈: _2 b2 ^) e- w# p4 L+ Q) |
8 @' c4 T+ n! f1 p' R6 ?3 w为何切换的bank号是0x0E呢?
, ^% k: t9 o9 I因为在扩容ROM后,文件PROM结构为:
) q% ]9 M7 j9 T8 ^! Y" Z原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
( K; w3 h; c. K; N/ _; `1 \! ~
$ L. g' T2 k1 Z L2 ]! _Mapper 4切bank一次是切8KB,那么文件结构就是:
* w& I- x0 T3 p. L! z; g. L8 @原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
+ I# ?1 m7 F. d: C5 X( f; N因此选择扩容的第一个空白bank就是0x0E号bank。
A# m. A; h7 Z4 `) O( r7 l& U% h: T+ l- R3 m1 h
: @/ I1 p- h: g' U* F8 S4 a# R
然后去调试器找RESET中断中可以放下切页程序的地方:
: U$ w4 w- I0 w! w! U- R+ [首先长度要小于等于自己写的切页程序。4 u/ e V' v2 g/ |
可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)3 k. m4 o/ A) X0 K" |3 h8 d
. ]- }+ t4 {+ z5 ^) T7 ?4 t; O3 z
然后跳转到$FF7C:9 V/ e6 `. k8 {6 N
* A( [- }+ D& S
5 ^/ Z4 A- A4 `5 ?/ T% N
3 N9 X7 B% p% W* n6 H+ b! P: p. s4 O( |9 Q k8 a* U
复制下可以被替换用于写切页的程序:
+ D. a& s3 H, @' O; r先选中,再复制:- r0 B5 ?. V$ o( h
/ e/ [5 |1 l& d" O
5 T) e" c) U1 W7 d8 g6 z6 W" m在Hxd中新建一个文件,把复制的数据粘贴上去:# W; _" O# x$ }5 u5 Y
: P4 K5 B. O: ~: @5 D( q1 ]9 Z. e J( D& \' F7 n( Y" b
$ ]4 I% U9 U' P) R
: A. T* c0 E1 T4 n
然后回到十六进制模拟器:
3 V% s& P/ c* [9 d# i \/ I' p转到$FF7C对应的ROM地址: q, Q* z3 C0 M/ @/ |
) [' `& }# ` f8 B( N/ h1 B" O9 a6 z+ |
然后复制Hxd的切页程序,粘贴到这里: Y+ M! ?! o9 H* I: `9 h+ P
- d! U9 e% Q+ d6 |# H S! [8 I. @% `% c7 V
! w- I# Z; }; n, o, O) `# d( q没有覆盖的用EA覆盖:
* W- N3 G1 M+ o# O6 f0 i$ e& d4 q% p1 h% e5 }8 w
打开调试器可以看到变化:* h3 }& A& y* X6 u8 C! j( P4 p6 R
+ N- P" ~/ Y8 x' ?9 X9 U) v然后添加$FF7C执行断点: t2 R( d8 F# H5 H. n
! H+ C# R4 b5 v9 r& a
: q1 _3 z# V$ X5 ]! Z7 }- ~
: C9 p- ?6 n3 ?. c$ W) j单击运行:2 \. g5 G% i7 W+ C( r
然后程序在$FF7C这里停下来了。 M; q) R0 ~8 y' ]# i% T" E
: q4 r2 E8 F# K0 x5 u- j7 _
然后单击单步进入慢慢跟踪,直到跳转到$8000:* m4 x, a0 \: j5 Q5 j. K7 t0 d
f! Y* }2 y! f; k j3 q6 ~$ ?
然后打开6502_Simulator:; V6 W6 T) {3 g7 e" A
) S2 t' b6 ~. ]1 d+ C: J3 x, @
再打开我写的数据搬移程序:
0 `6 F7 @. [$ m& g/ e' k! U9 h& v: ^9 i2 F# p9 D0 R, U
' `! d2 h% f! h j1 a2 ]; J6 G# O
然后修改对应的数据:) |" j4 \( |# l: ]/ `
程序开始地址:修改比如$8100就修改为 .ORG $8100
, `/ K, a b8 D( y5 I& |复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
% p& G1 z N3 r" H8 b: f从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82- T( q, l% Z I
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91
; t- }, R8 K1 C: {也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。
5 L+ n- A7 u/ [" W' t4 x如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。. ~/ [3 `0 [1 o- @' }+ T
中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。
+ m: t1 r% d8 |3 S6 q1 k0 h后面的不用管。' f% \3 j! |" L% I M
/ ~" A8 E+ d5 Y设置完数据后单击编译:, G+ K% y G+ o& L3 r+ R
8 f) K3 A8 V1 X/ q, N
然后保存编译文件:- Q9 e9 W; U3 E- b4 }% W$ j
4 x# F& ^! q7 H6 ~选择二进制方式保存:
8 a% n7 c8 Y W# R$ C; I0 t9 V
, L: R: a9 `% E+ o9 ~0 c; d$ C7 i& ~# T6 N/ W/ T
用Hxd打开保存的二进制文件:9 ^/ ], m8 I- f* E4 X. P' }
# v6 f4 {% Q: g
# {* l$ {) M9 N% G8 |5 M2 j9 D! V6 e2 Z跳转到设置的程序开始$8100:" e# |" C N% [: V$ x7 c
* A4 P1 k/ I. c2 Q
Y* h, M7 l# a$ v! j$ m$ k [6 y; P( g9 t2 L I1 I
回到FCEUX,转到NES内存的$8100对应的ROM地址:
X* ^+ v- z7 K+ _' |
# B2 l* L4 B% a; [0 H' M% g& k; Y# ~/ s8 y
3 i' B! o8 y# P; F* ]
: w. ?/ L/ @; x
6 V$ @' l6 v% ]9 b3 h Y然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:1 U4 ~3 ]9 D" _" A6 a6 ?
5 u3 S! H! U$ m9 x' y; \! Z# J8 `
4 c x2 |- s! ^0 ^, y然后转到NES内存的$8000对应的ROM地址:
+ Z; F4 n( V+ c8 k, \% a, s+ X, s, W* F8 C o: A, @2 y
$ X& M$ g! E$ _
0 A* c* d% l8 B' h1 f/ }6 R9 g1 x0 a" Q0 Q! J H* a8 O
写上如下程序:
% ^& v* |. m; S, j! ^A9 80 8D 01 A0 20 00 81
9 k7 m, S( q% @) Y. n: b+ q: zLDA #$80
' w$ ^. Z! i8 R: l/ mSTA $A001 可写方式开启SRAM
6 Q( L; R8 j) B7 d/ YJSR $8100 跳转到子程序$8100
8 G+ o; e9 X; x/ ?$ B( F: [
; s* g! m! t9 v/ w+ N然后把Hxd里被覆盖的程序复制过来粘贴在后面:0 b! X( ?0 Z7 I1 V# U% j
/ A# m, |/ q" c
7 d' a) E) |- k O# r" w. x' Y2 u0 Q; u$ F1 D$ g
末尾补上一个0x60:" R. @9 x5 U! T6 {# W3 _
RTS 子程序返回
, a3 H5 O" {# N/ J
( S7 p' g$ {) v2 O然后单击运行,ROM音乐响起,正常运行:; |6 c$ y+ o6 K: Q
7 l+ ?1 [8 P I! T2 P' r U
, A2 h' A1 i6 ~# m, Q然后转到NES地址$7000:
3 K4 B$ r6 b! ^3 W' @+ q3 h+ d' ]/ v3 z& r% B0 \* k0 E: k
, |' \/ @% b. y" [
( _* C5 w2 G7 N' t( ~) b; A
/ l- G# N! }1 P9 u
/ _ e7 q7 {2 f7 w( i+ |7 f( \' P可以看到,$7000-7FFF都被复制了一片数据。
( b/ ?5 l/ J3 r) Z- U8 r+ M测试没有问题,然后保存文件:
' H8 I/ E$ \5 U/ l" d
: `! @! i ~$ }3 u以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。- F8 f8 n. R6 {, w
后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。
5 L3 X/ B/ s2 O. h) ^ |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|