|
|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑
[/ |% m7 }& C3 x! G! W
2 [& k y' Q# k1 i$ d. s[FC][SRAM扩容教程(Mapper 4为例)]3 p9 e) b! a9 E
) D+ B5 B# r# Z+ m: Q时间:2017.4.28
. t Y0 ?; w, V' G作者:FlameCyclone. t# j# L' N: w4 E& C+ C' d
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
. u6 O/ Y# k# E% p/ [ROM:双截龙2(J).nes# k M% r! U3 G5 l" w6 u
适用:没有使用SRAM的ROM2 w. P; \ m* x/ l& P
- f1 P5 O* |# O& C6 M' i首先用Hxd打开ROM:
/ k D6 Q& m, x 5 U; }$ u) \9 m* C9 S( @
然后扩容:' T G# ?0 W' j% r# S. _. ?8 A1 w
( F0 l i2 L' W* M) }# `/ ]6 C/ X2 P) L# y l
# c& B; f$ N. u( @
) F8 R, y/ O+ m+ ?
- I- V4 w: v- J6 s" ]$ [2 b* Z0 m
7 P4 @4 P* b( X! Y' U6 ?5 p: V* E1 S# N H6 {3 R9 j' ^( F
2 A8 Z& o* Y+ _: L0 z& y9 }
( N( a3 S( w! [& O先看看任天堂产品系统文件对NES文件的说明:! \. _8 C4 V" X1 C
NES文件格式) {- R& ~1 U8 E; l
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 $ X& ~$ P Z" w- B. z
偏移 字节数 内容 ! \8 k4 z; J. v
0-3 4 字符串“NES^Z”用来识别.NES文件 ' Y# Z$ A( _/ u- E
4 1 16kB ROM的数目
$ Z; k9 H7 p! r5 1 8kB VROM的数目
6 y4 ^2 T8 y9 i% f) z6 h0 N6 1 D0:1=垂直镜像,0=水平镜像 6 G/ ^2 ~+ \0 H1 l7 A' j
D1:1=有电池记忆,SRAM地址$6000-$7FFF & }3 m' p a; `1 v$ k5 {3 s* E6 u
D2:1=在$7000-$71FF有一个512字节的trainer
+ W: d3 G: P8 B( E% E) ^ D3:1=4屏幕VRAM布局
* R: j% Q) q B% g$ B" |/ ~+ @- e D4-D7:ROM Mapper的低4位
: r1 c9 z" L+ d4 h! u8 U3 y7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^)
) b! l- V, c8 G" m0 ?: S D4-D7:ROM Mapper的高4位
5 ~/ }3 L, b. A# w& ?2 [6 R# r8-F 8 保留,必须是0
4 s5 ~& u5 N7 d) T/ e6 X16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
+ a; H2 N$ M* T& d& W, y6 x9 m-EOF 8KxN VROM段, 升序排列 2 {6 j8 P9 T+ F5 F2 p( Z8 E- k0 V
, N! y8 k) c" Q: s4 g
然后知道这个ROM有0x08个PROM和0x10个VROM
- `. W. D2 R; S3 i- j接下来扩展PROM位0x10个:
( f/ v+ Z4 h: ?# D* J( l- w先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
6 Q6 m% Y M" C) i8 g) Z4 ]
5 S1 {7 M+ K0 q4 G {! t2 d由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:
9 \1 y& Y$ x/ [% L& V! f0 [9 ]) Q4 q最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
9 o% h G; a9 M- A% N2 h于是可以得到双截龙2的是:+ ^3 f" V7 \3 K/ l9 F3 Y5 M+ }
(8-1)x 0x4000 + 0x10 = 1C010。! V$ H% L8 Z u8 ^' Y/ A, f
然后跳转到1C010:
; [/ x# V& ]- E1 ] $ d1 M. n4 d* }, a9 o, r# ?

: B" r9 L3 r9 a/ I! y+ o- \3 O" z 4 M. B' A+ Q* b b+ I% j8 Z
然后插入0x20000字节的FF:) ?9 H+ I3 F- Y

. h6 D4 }2 b- Q: N9 I3 R( m X8 G3 a4 z1 }7 U* T4 ^

: a) ?: y) a7 U% R* z" S然后保存:# {7 @, A3 w# A T9 F" w! ^& S

/ z3 s4 {- t4 L$ h4 r }" `8 E
- R( B$ z+ I/ @1 z用FCEUX打开正常运行:3 F) O% w* n- V

" c( ?* X& h0 s; P3 y查看文件信息:& r) _( |- i2 d8 e/ S. Q K. B8 W

9 d" d1 p' l, n) C. o: A
! A* o( L8 }5 k) K9 z D接下来切页: A8 j7 X; y# M) }! K
先打开十六进制编辑器:+ e! e# X! U" R1 d
0 o" O# p. L1 a' X w
拉到滑块最后,看看重启中断
* P; C+ n/ i6 d( V# c1 d# x中断地址 中断 优先权
" M. Y- _7 u, A! i/ t& t! [$FFFA NMI 中 ; H# t. H e6 | W8 Y
$FFFC RESET 高 7 M' |7 w2 h# I' c$ B# f) h/ C
$FFFE IRQ/BRK 低
- g$ Q* ?+ R$ i( C
' j8 i* k/ m8 \. O% XRESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。$ g" V% \$ K1 R7 q+ d7 a0 r. w
由此可知双截龙2的RESET中断$FF65。2 I+ f9 b( f2 h, c, m( t& O3 p
接下来添加$FF65的执行断点:
* d M3 Y, J- z8 C1 P' V打开调试器: c! I% n' T8 [3 M" R0 B& J$ ]; c
) Y5 q r3 R; J3 E
添加$FF65的执行断点:8 }2 @0 s8 _, O6 \$ r+ ~
) h7 D; H2 O& J0 C4 K
. J$ V- g' [/ _" b
) w% y& a6 u) d单击确定:+ m: x# V* m% j! U
1 D; y9 ]. m& h% l% a# s
" h8 |# j3 N; u1 a! Y. F( K2 w+ S; S1 P
然后重启ROM:
! i/ f* n+ I! D! E
2 m% H0 A' s( B调试器此时弹出来:" k( }9 ~3 u0 Q+ d7 D
8 U, J9 N3 d7 s8 `- ^$ L6 q. C
然后打开Hxd,写一段mapper 4的切换bank程序:# b, [4 d& z' x6 v7 ~: q" ]! w$ j: t; x
先看看mapper 4的说明文档:
2 ]& o7 T' V. U* g7 S. cMapper 47 \! F! D9 [: _( J0 G1 X( P8 {- D% L
+ m3 f9 K( T5 m/ k G$8000: 模式号
, r7 N% B! A! j) P/ Q3 q 位D0-D2:
4 k$ Y2 J: W/ E/ j: Z 0:选择2KB的VROM存储体映射到PPU的$0000
4 ^7 o, k9 S9 \% S 1:选择2KB的VROM存储体映射到PPU的$08003 R3 l" q+ d8 T
2:选择1KB的VROM存储体映射到PPU的$1000+ W$ q6 Y5 C. F
3:选择1KB的VROM存储体映射到PPU的$14008 V( s. O+ Q3 X3 j
4:选择1KB的VROM存储体映射到PPU的$1800( q9 t1 N5 a8 H
5:选择1KB的VROM存储体映射到PPU的$1C00
- j- U- w1 C7 y7 Q/ @. L" v9 @ 6:选择8KB的ROM存储体映射到$8000% e3 c' p4 s* q
7:选择8KB的ROM存储体映射到$A000" x a. \, z0 H5 }% ~( C
位D6:0 q/ ?. n" n h# L8 Z
0:允许擦写$8000和$A000
7 p; G7 Q( W$ ?+ o# F7 V7 f0 `, e 1:允许擦写$A000和$C000
" U8 r1 `) E1 x2 m0 u 位D7:
1 C8 P$ `2 _) ^ h9 ?9 U* \ 0:模式号D0-D2使用普通地址
% G$ x* n+ W( O0 [ 1:模式号D0-D2地址异或$1000- n2 Y4 {1 u3 o
1 v+ \1 w4 h, F) L# q1 r$8001: 模式页面号
4 T1 |& Q# ~. U& j 写入一个数(00-07),切换存储体到对应地址
. ]5 l8 W& M! t) m, P5 L+ Q
+ {1 G1 f4 n) p- T( e7 R7 W$A000: 镜像选择) {: B; @! T4 @9 Y0 |5 t4 w
0:垂直镜像
6 I- C. ^! ~+ x 1:水平镜像$ b+ w1 f4 J! A) M2 e9 z
' M: C. o$ A, o- X M# B
$A001: SaveRAM 切换
1 q6 a3 J" p+ B 0:禁用$6000-$7FFF
z. \0 W( `$ e* B. |' G( C7 b9 L 1:启用$6000-$7FFF
8 r! m/ O$ t( K- v6 w* o
6 e, k0 [- Y2 T$C000: IRQ计数器/ Y/ |9 X6 Q( \) W, [
IRQ计数器的值存储在此处
" _: f: d: v6 e9 ^! U% K# l' k4 X3 Q$ i8 ]! D- D
$C001: IRQ暂存器! y1 K5 x5 Z& d0 M
IRQ暂存器的值存储在此处5 e1 |$ C! L( F- O5 V
' s8 |8 }! h. ~$ _* o" \# F
$E000: IRQ控制计数器05 K( R3 F- y- ?/ z* H
向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ3 z& }/ I( A, }" `
) w0 o, o9 b# s; d& a' F
$E001: IRQ控制计数器1
" `' x; \7 G3 ~: l& ^ V( s 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)/ E! T5 o* A7 F1 N
7 h# A7 v5 y" y3 Y$ w# T2 Q那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:* S( ~, w! G7 d6 Z
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68
6 O7 e# d, t! D$ M* |. a8 [PHA 累加器A入栈1 s2 ]. r/ ?' W- h2 [( x
LDA #$06 设置切bank地址为$8000-$9FFF
1 A# J4 m6 U+ RSTA $8000# f' C" T& ~" _, E$ E, |+ l) L
LDA #$0E 将第0x0E号bank切到$8000-$9FFF. s7 P& H5 p ~6 D }0 \# O
STA $8001 l+ b! A0 N$ ]( Y- L
JSR $8000 跳转到子程序$8000
7 y% a0 }2 Z4 p: iPLA 累加器A出栈, y4 M P5 p$ A ^$ {
% T. W8 t7 u1 z/ O7 P为何切换的bank号是0x0E呢?
9 P" U& }, D9 v. I因为在扩容ROM后,文件PROM结构为:9 m' [4 b, V; g& t z" C
原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM. C7 ` P% R0 ?$ [1 @3 L! h- L) P z
3 _: Q9 e% Z sMapper 4切bank一次是切8KB,那么文件结构就是:+ F' g7 p6 H/ g7 U* g d
原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)6 K# w5 n$ n7 `& g4 h
因此选择扩容的第一个空白bank就是0x0E号bank。; i, s" Z+ [: K
7 y8 k! s6 B2 V6 b; g
+ e% S8 c1 {; j7 B7 r3 @- X
然后去调试器找RESET中断中可以放下切页程序的地方:
7 K+ ^7 _8 l }3 m首先长度要小于等于自己写的切页程序。
4 r# G4 n3 A+ p- _可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
( z0 Y0 m; G% F7 w1 p; T- E d& G " D- `( {* r" W1 |4 R8 L
然后跳转到$FF7C:8 \) |) W( S' j) Z6 S H# y) ~6 z4 q
. l0 b; e# ]/ R# g9 B

, r- e/ e% [- `0 r" R
3 M S5 [- d- l
2 q- M4 j3 k+ i1 [% s复制下可以被替换用于写切页的程序:
5 z1 I% s/ N/ m [先选中,再复制:
& o" Z) b4 z4 u& _; |% ? H. j4 V + E7 V6 I f( {- L
@5 |/ K4 _$ L6 h Z
在Hxd中新建一个文件,把复制的数据粘贴上去:4 X2 U+ I. f7 P/ w+ k- p6 k

# {( H" _# w8 r+ M! i m
" G1 U) q9 C+ W8 F $ Y7 q B* w E- Z. C+ ]6 P
4 P! N% }" Q- {: H' i3 Y然后回到十六进制模拟器:
- @* P; r% Q+ @1 @: k! l转到$FF7C对应的ROM地址:
' \. F: k5 S" w/ e+ K- j
& k# u! w0 |, B2 c. A" j& Z$ | 4 F3 n7 [( D5 k
然后复制Hxd的切页程序,粘贴到这里:
: r$ \& x: X6 K: E4 L x4 r0 u a5 z ) f. P3 R- l# Z! k
0 w* k3 u! ~8 E7 w+ e& b

0 A2 T, @8 `9 x3 d没有覆盖的用EA覆盖:1 A' P# L: z# N

2 t9 }+ f. [' |" Q, ^& I打开调试器可以看到变化:; _* J |% p# |. [! t- O
" X0 y' e/ i& { {, R
然后添加$FF7C执行断点:$ m7 i+ W4 {- P8 L9 T

* c/ q' ~2 Y% Q( P
D% C; w6 P* n) c: Y: G4 @ 6 K( [3 v; S! c; E: L: q: v4 A
单击运行:
+ P& I/ ?9 @( ]; v& d3 w; v然后程序在$FF7C这里停下来了。
0 h4 D& Y- O6 k
8 e* n1 g8 z' b7 z+ E- @3 S然后单击单步进入慢慢跟踪,直到跳转到$8000:1 e" Z2 W: @. V! X; Q5 x

/ Q/ v: A* K, l9 `8 P% Z然后打开6502_Simulator:
0 J% B/ X8 j" ^
, j+ V2 p1 s) Y \6 Z! @再打开我写的数据搬移程序:
; i8 f/ T9 S \9 z$ ?, ?, Q
3 t* A7 K' O- r+ Q, v 0 N2 {) Q: P1 P
然后修改对应的数据:$ O$ y. @( w, f' H* w: H$ y3 P1 q
程序开始地址:修改比如$8100就修改为 .ORG $8100
% ~1 \7 w8 u! ~1 y) G) B+ P& i6 I3 [复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$706 O m* K# {' u0 k6 u2 _3 |/ h
从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$821 S0 w# Z j) Z7 u+ [5 ^& Q6 K
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91
- P/ Z- f* R+ q' x! ^, }) k也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。
& O+ U9 F ]# I/ G' F3 H, C如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。
g3 B1 n$ _$ [3 q$ K6 |中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。$ @! B& ` T W7 p* d
后面的不用管。* h% L7 \% w# G. K1 p' a! n7 S' x
7 k7 y, q9 Y7 k5 s a$ V4 x设置完数据后单击编译:! W7 E1 P" Y6 |0 i: o# z" E

m! T Q! V2 e0 U/ G4 K7 a: L然后保存编译文件:7 t4 _5 I l% z& h" q7 a2 C

0 b4 `4 o' \6 O! ~选择二进制方式保存:+ u/ F3 t- d) X

* P/ C: b2 z3 S( T
3 S: ]! |$ j2 K# O. a9 o3 f用Hxd打开保存的二进制文件:
5 E$ d# W, G8 ~* D7 k1 o ! x1 k( s, i* E
4 |. C/ q4 |8 s' B. n7 k5 b2 V
跳转到设置的程序开始$8100:
# F. s0 D, c0 r $ j9 Q2 N- }/ B& T- ^( M+ E

% Q& O2 `& P, W( m. z( c" G) q
: K% i( H( V& C/ s# s W9 M回到FCEUX,转到NES内存的$8100对应的ROM地址:$ A! y2 y" \2 H( p0 [

: D" @$ y8 |" A( G- C4 X. V0 t9 g7 S % \( W4 a# @+ w; A* ]

# Q# @/ D T: Z6 s6 J$ E; Q( N 9 [0 {& @+ U2 N' ?

) i1 Y3 z. ]' L. j! x然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
) z) I$ `) N% d 4 a, K2 g8 L! }) W4 A

6 {8 v: M1 j0 l$ u0 `' f
, D! o3 M. c2 B* {6 z9 P& P- t) Y然后转到NES内存的$8000对应的ROM地址:
2 D" Y5 F# M' r4 G5 @- E9 M) W
6 S1 X$ h. d3 o4 l3 G
- l1 ~3 B) L& g+ T; Z2 I& _( Y5 b
. o2 R' V \5 M7 b4 H4 M ( f/ `4 `- p0 I( E4 Z
写上如下程序: [. g g- s9 ]9 [
A9 80 8D 01 A0 20 00 81
- y1 F% j: K' A' |# b LLDA #$80
c1 { r5 w! @$ t$ ]4 h2 wSTA $A001 可写方式开启SRAM$ w6 a, M6 ]5 g2 v
JSR $8100 跳转到子程序$8100
3 ?# m) g$ N0 N
% o( B( e( N2 p; `$ |然后把Hxd里被覆盖的程序复制过来粘贴在后面:& E. ]' g, |; |. h1 s

. Y2 j* D) d! x# J' L% z# \# \3 I/ k
& U( R7 v3 T0 P" ~4 `0 O ~/ } k+ @4 P; \+ m3 `8 y+ T0 I" R
末尾补上一个0x60:- R' y6 \8 W" \
RTS 子程序返回# U% R7 O1 s( K' E" P

4 v$ ?: P" \+ q! a2 X# W8 Q- K然后单击运行,ROM音乐响起,正常运行:
4 ^1 [: k7 n9 X& H2 H+ o" ]
- H4 d; i- M4 K, Q4 I6 _
! A$ i$ ] t S7 p0 l然后转到NES地址$7000:
7 _4 R/ @8 V" u+ i' z. B9 J
' y7 ^( X* f: [0 A
( X) R# d* d4 k& b . e1 D2 G3 ?( x

; L) h7 K h" ] _4 c1 g6 J5 j 2 K) Y$ _9 k! f4 W9 c" j
可以看到,$7000-7FFF都被复制了一片数据。+ w- v2 J) b; n/ X& D. ~/ }5 s
测试没有问题,然后保存文件:( j; V- e; [& p" _2 l0 a! I

1 D; C5 M+ f( f# r- G- ]以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。$ h/ I8 L8 y" d% ^8 M
后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。; ~" A( Z2 ]8 R9 X, R
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|