|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 ' Y- S& X- G, y: d
$ h5 o0 p: t0 i7 R p[FC][SRAM扩容教程(Mapper 4为例)]9 B5 ]; k$ \0 u2 S: ]# r3 j
- w: n& t* l* P' }. k4 N时间:2017.4.28
1 u6 I5 i- D9 b. \7 t! m1 k作者:FlameCyclone+ x0 Y6 `/ A _$ @+ ?( f
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator* q/ U) X! o u# m, g$ L
ROM:双截龙2(J).nes( l$ m$ l/ k3 ]1 L
适用:没有使用SRAM的ROM
- O6 B) d; h- k& H
' W6 b4 v/ Y/ M p; D首先用Hxd打开ROM:
* r/ ^: J: q2 n
8 L1 Z! w; }9 P& z3 T然后扩容:
* ?- y, I7 q" B! F! R% ]
5 G7 j D+ W+ f& G4 K$ F+ _1 `' I$ x7 a2 J* ?
0 f; m# n3 a2 @5 g! A4 v
7 f T$ u0 s- {; Y K o
& Y' b' o' v1 `/ U8 J% J+ I! j6 {& {
5 [7 r3 g* n& [4 w/ [5 H) M1 C. d/ e: ?; N( W# M% w
6 R8 e0 b" p) T/ O! S* G先看看任天堂产品系统文件对NES文件的说明:7 h( s K1 m+ \1 d. l- V% V
NES文件格式8 k% Y8 f1 ~$ U- Z5 m& _8 ~
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。
" Q7 i1 X& s9 x G8 F7 |偏移 字节数 内容
- {2 V J2 A+ j, c$ L D" ?0-3 4 字符串“NES^Z”用来识别.NES文件
7 ]+ T' m g5 q' k: u/ X/ ^, v4 1 16kB ROM的数目
9 x6 _/ k7 v/ b) B5 1 8kB VROM的数目
. ?7 q7 L+ ?+ [- y' i4 H% N6 1 D0:1=垂直镜像,0=水平镜像 % J. T! h( w b2 D, c* V1 m# \' f
D1:1=有电池记忆,SRAM地址$6000-$7FFF . C2 z, M% ]$ V. D4 r
D2:1=在$7000-$71FF有一个512字节的trainer + @2 K* h, C% x. e+ E
D3:1=4屏幕VRAM布局 1 M# Q' H& e# k$ i% K6 Q
D4-D7:ROM Mapper的低4位 2 r. {1 g0 h' p/ u' b
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) ' F$ k! g, i3 D) ]7 p: ?; j9 i
D4-D7:ROM Mapper的高4位
7 @- m8 C+ p/ N; s# {9 G8-F 8 保留,必须是0 : k0 m& B" ?# w% i5 c7 G z( k
16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
8 s% v4 H" g7 |% w; w$ K* [-EOF 8KxN VROM段, 升序排列 , q) Q: m; s( |, M& b! S
( h; Q5 W6 ?; p6 B9 o
然后知道这个ROM有0x08个PROM和0x10个VROM+ X: y: }4 i* Z) S6 Y
接下来扩展PROM位0x10个:* ~) M! F+ C$ d( e2 K7 {- A: L; k
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
: _7 y1 M6 U9 V& L: d' D
/ b$ W' E$ p0 B [由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:1 @5 S9 M3 V- e: X
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。/ Q. q0 D% V2 S* S
于是可以得到双截龙2的是:; H |% z0 Z5 K6 u* x1 F- v
(8-1)x 0x4000 + 0x10 = 1C010。
3 A0 S7 Y3 ]/ I! D7 ~+ F; q然后跳转到1C010:: ~' d% g9 I9 }( J4 a! \2 f4 V7 g
1 [4 }# W( r8 V

# r! S) C6 i8 O5 n1 w } : s [ E2 K+ K1 Z" W& B; q
然后插入0x20000字节的FF:" F1 D7 ` {" T+ x
M9 A/ L! \! m

$ [' E9 X9 [" [# ?/ S6 h
/ z% U, v& V% U' H4 \, V然后保存:+ w V) h ~" _; w

) V. Q) n- R8 }- \ 1 c( a! x: G1 Q% E" u2 V
用FCEUX打开正常运行:' a T/ z; W/ N* C0 X

" w f6 [' o3 y p- i6 S查看文件信息:# Z2 d! [) Z h' Z

& t+ s' a; Z+ s1 M9 h6 @6 y8 r 6 @/ B( N# X7 q* Z6 K& Q
接下来切页:9 |8 m) x9 [3 z. _2 F) ]& j
先打开十六进制编辑器:
7 L- v' m6 W5 M
/ c7 g6 e' k9 g' ~4 \拉到滑块最后,看看重启中断2 c* F# ]5 M0 R
中断地址 中断 优先权
" n0 x6 m2 a4 ^6 a$ N$FFFA NMI 中
& z6 y! M8 X! N7 m5 u$FFFC RESET 高
: p) d# Q+ O+ X* |5 G$FFFE IRQ/BRK 低 & u! g& J0 M6 |3 r/ S
5 R: m$ D z3 m( v0 O/ W
RESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
1 z! z: X' f* v( j% a9 A' ^/ G由此可知双截龙2的RESET中断$FF65。
) B. e9 ~7 S$ x4 D7 ^接下来添加$FF65的执行断点:
4 J5 ~6 H6 {. a/ [# }' e打开调试器:& ^/ N; S; q5 O% ?

$ A% [. g6 @" \7 `5 G% ~; ?! w添加$FF65的执行断点:" o8 d( e# {; e5 U: ?

8 i! x7 s, @1 o: X4 y% Y 4 N( l7 F! O4 U4 V$ B0 K
0 p7 k2 r% r* P单击确定:. l( W7 ~( S) c# _" D. x' A

* C7 q9 r/ }( {3 H) a
]2 A! `9 c& H- c1 O然后重启ROM:! X: C4 J& G) l+ r/ z
8 D5 e3 s- z3 w
调试器此时弹出来:
; T+ z0 U* g$ Y! u* J
, f6 l; _+ K' X0 ^3 L/ T' ?6 I然后打开Hxd,写一段mapper 4的切换bank程序:
: m/ D/ h1 S1 ^" E9 c7 Y: z; w7 r先看看mapper 4的说明文档:
+ p# `0 L! e |3 \1 wMapper 4
( A0 D* |2 j/ L6 q
' \3 `' r) T8 D% V& U# I$8000: 模式号' b5 P: f0 h# b; p
位D0-D2:
3 v5 H% {# v6 ?/ D' K6 w; g. z: h 0:选择2KB的VROM存储体映射到PPU的$0000
1 z1 @$ h, \& |3 x 1:选择2KB的VROM存储体映射到PPU的$0800
( X. k# V0 S2 Q7 E' w 2:选择1KB的VROM存储体映射到PPU的$1000
, n: [9 i3 V# h- K2 S. R' } 3:选择1KB的VROM存储体映射到PPU的$1400
- y4 O+ [; j+ P6 \9 e 4:选择1KB的VROM存储体映射到PPU的$1800; M6 o: V3 U. m1 }- g* j
5:选择1KB的VROM存储体映射到PPU的$1C001 Q1 X, J/ M8 b4 t
6:选择8KB的ROM存储体映射到$8000
( e6 ?% T8 M' t# X3 M& b 7:选择8KB的ROM存储体映射到$A000
/ T9 D; X' j |6 y 位D6:7 B* Z- m, |% {9 o
0:允许擦写$8000和$A000
2 m6 t) f$ E8 x! {3 r 1:允许擦写$A000和$C000' V L" q. @+ w6 O4 O& |
位D7:" W, p( m$ K1 j
0:模式号D0-D2使用普通地址1 k( r! f' x4 }+ q# l
1:模式号D0-D2地址异或$10002 {4 D* B9 C. e% x6 ~
& y2 {/ q1 f& Z8 w/ m) H5 }
$8001: 模式页面号
$ c. a! h* A( w* w* `6 ?3 G* C 写入一个数(00-07),切换存储体到对应地址
" B8 s# N/ t5 h/ c1 J- g z9 l; P+ x7 e. J O6 q0 U* Z- }
$A000: 镜像选择
9 G$ C: H5 k8 m& [4 R; U* F 0:垂直镜像, L; ` l ^3 ^% C- v: H3 R% c
1:水平镜像
" l8 [2 H& i! a9 C
% N" [# u, a1 H$A001: SaveRAM 切换2 T+ A4 m- Q6 e- M* {1 M G! Z. a) ^
0:禁用$6000-$7FFF
: x$ e! s& v7 Y! A/ i. ^/ ?3 {1 W4 G& A 1:启用$6000-$7FFF: h& f. M8 Q/ m g8 Z
6 E% J1 k Q( V, \+ f
$C000: IRQ计数器
. M- s) y+ {1 ^, g IRQ计数器的值存储在此处2 H0 _2 A. ^3 w' ^* C% b
* B; |, m! s" _
$C001: IRQ暂存器; \) d9 ?' r: |7 a- O2 ~+ I
IRQ暂存器的值存储在此处
3 @0 S5 E) v1 v* Z6 O5 A& }3 S1 M. Y' D
$E000: IRQ控制计数器01 [& ]/ \2 G& O" L+ f7 ]1 m, K
向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
" ?- o. @% c; c- w6 F" `
" V6 Y0 j% K' ]6 v) p. \+ I; G$E001: IRQ控制计数器1
% _; X4 X) n V: E0 N z5 l 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)
# f% `( E% s, s2 u7 T6 |8 Z2 [/ m, ]# o h8 T1 V1 ^: [
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:0 K9 V7 E& N) t) l3 [2 o
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68
* D) S9 h/ N o5 q3 Y7 OPHA 累加器A入栈
6 M6 ^7 {2 T5 p C7 D. z8 M' ZLDA #$06 设置切bank地址为$8000-$9FFF: `# ~9 d- W1 o
STA $8000
# Q/ x7 V8 y/ ILDA #$0E 将第0x0E号bank切到$8000-$9FFF
/ |2 {; l" ?. E0 ASTA $8001' e9 V7 ~( f3 R9 I; \# C
JSR $8000 跳转到子程序$8000% o' z E: Q% t ?' b( Y e
PLA 累加器A出栈
" C/ j( m4 C! m/ B3 Q
5 o0 ^& w2 j/ w. Y7 u$ d/ M为何切换的bank号是0x0E呢?
: _8 u6 [8 N: b ?. v3 ~) i因为在扩容ROM后,文件PROM结构为:! r1 w* W o4 Y/ Y0 X9 |
原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
4 ]+ g$ t2 @3 m# O# R
( z# Z1 Z# A; ^% a; B2 bMapper 4切bank一次是切8KB,那么文件结构就是:
3 `1 R- x3 m3 d, ~6 @- }+ ^原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
5 l& J. m7 M3 V- Y因此选择扩容的第一个空白bank就是0x0E号bank。
7 o7 X# O9 C9 b: v% n) J$ U) P # l) N7 H# } C7 C7 S8 `+ D( ^
& J7 s- F/ f! s$ R% Q. |
然后去调试器找RESET中断中可以放下切页程序的地方:
& G* m" t8 ~$ G! W# G' x. \$ \首先长度要小于等于自己写的切页程序。
f# J4 x" {, b可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)' A( ]' H% @2 f5 E2 ~% U
: |# a3 y$ Y/ N; P$ X6 R6 z9 ^4 P
然后跳转到$FF7C:
* E2 e2 x- X6 K& e# r: y5 C
# X0 R) p0 F! y5 f5 B3 l k3 [" u6 z2 ~- g9 e- z
+ g0 B. Z9 a$ N7 p! r" M( l5 p ]
4 f7 Q [: n' U- h1 A( Q
复制下可以被替换用于写切页的程序:+ I3 a: k& s) [& R8 L8 }! \
先选中,再复制:" S) r# ^: m6 M$ W1 |4 C7 F

3 |# s0 d4 E. I. D D0 v6 J' C , t9 u# w3 e! R7 X( m7 y
在Hxd中新建一个文件,把复制的数据粘贴上去:- I0 l- f2 B$ i L9 j
; t# R2 Q; k5 u q( E

! ^5 M' }9 d# n$ k" ^0 _% M/ } / T& a& J9 y8 {7 h
# p, }7 [3 d s/ y. ~然后回到十六进制模拟器:
" ~8 B5 I8 o: |- X+ f9 q9 e* P转到$FF7C对应的ROM地址:3 e. R6 K2 l! p) J5 s

8 C. w: b, V3 }/ `5 O
t' K6 t S2 L9 e8 C% w# v然后复制Hxd的切页程序,粘贴到这里:
; U3 Z, p7 O+ g- M4 N8 P; t2 M* V J3 L) @! P9 H4 F" ~

+ B! x0 p2 o- Y. t$ m- S& E , Y+ ~! R) _( m3 _/ U
没有覆盖的用EA覆盖:: ?7 n4 n; B% @5 P
$ K, U* m" P' O1 ^: Y7 \0 p9 j, n
打开调试器可以看到变化:
( i& e+ f+ ]& p. {2 y) ~- N( ?
$ f% L8 l8 O3 i& N( D然后添加$FF7C执行断点:
# g, y1 X: b6 P* \# C s% V4 _% |# x- s' s
: X4 K2 s2 k6 X$ Z- L( t

! q& p. P0 ]% L$ ?1 ?单击运行:
; d& [/ d& P# p. C( S# I6 I2 k然后程序在$FF7C这里停下来了。3 {7 g' y5 U, {2 j1 }3 q
3 }/ q) Z% W" w5 r/ S) e
然后单击单步进入慢慢跟踪,直到跳转到$8000:+ O9 R* r& A% l) G+ p/ {+ P d+ l. L
- k7 `8 A5 A7 N0 K: d7 q
然后打开6502_Simulator:
- h# k, X5 v. _6 u9 r 2 [0 L0 w1 u2 U' U5 x$ g
再打开我写的数据搬移程序:
$ n" {! ?- H: |; y+ h9 Z" O* d ' e& [6 a, {8 Z2 R

0 K6 d* ~" P: \7 K9 |然后修改对应的数据:
# s* Z e* T) j# s i( m" S程序开始地址:修改比如$8100就修改为 .ORG $81002 A) C! [; K! J* r
复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
: J/ S& H- J- ^7 b- K从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82
) V2 \! J1 q6 V5 E想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91" O5 A8 g& H6 `/ n+ }" z" L7 r9 p
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。+ g, w) a! r. C% g2 w
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。9 I `6 {& N) d: M! q
中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。* ?& `( d1 _- r" R9 r9 g( W: D! t# k
后面的不用管。
) r0 f: ?3 B# x( X
$ P. \0 h7 Y1 l# a) ^$ \设置完数据后单击编译:7 ?( F1 l2 _2 T; b' R% i# v
1 \) [6 m" |9 p8 v3 Z
然后保存编译文件:' x2 [/ c0 |, M1 |) p" ]

( A6 [ a Y" l3 q, t选择二进制方式保存:
) B7 u$ m4 f! ~2 _6 Q % R5 D( f& s+ `( e) M R

, H7 a$ f3 J% I1 s6 H/ o# I5 U7 z用Hxd打开保存的二进制文件:% }, ?7 v7 e$ i+ [$ t4 L
4 ~" M# {# E4 i1 R7 \ R; M6 g

8 k( ^# [3 \/ d& l" Z' S2 k跳转到设置的程序开始$8100:! D! J+ R5 a$ G& e! g$ U
' Z5 W3 l* F) Q( U" K* z s

9 H" f, X& s2 k/ X. I 6 ~" ~ n* h4 L0 L7 n
回到FCEUX,转到NES内存的$8100对应的ROM地址:0 H% G) h, M5 g) B* \; p; p: p
9 h9 V: A# d$ j
/ h* g) Q+ [. D, n$ ~' O

, g0 z4 x0 t: n1 H2 S
, u8 M) @ B& s/ _
, n! H+ x) _# B) X* v0 I1 A9 ^然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:$ T/ E$ B6 L$ p4 N/ `

9 e$ Q$ j3 I4 i " C# ^% }; w, B

9 ~- s$ N( r5 m: A- S! E# }1 d0 H8 a6 L然后转到NES内存的$8000对应的ROM地址:
+ c0 A, H" }& b$ l . F- P3 m* G; F; |) B
, P- g6 t% O" I0 v7 k
1 K' P% b* e- _5 A3 s8 l/ ~

9 Y6 q2 m- b. t0 R1 C' C写上如下程序:3 ^2 ~5 n6 m) H+ V7 G V* V
A9 80 8D 01 A0 20 00 81
8 u$ Q) e7 r+ }: C4 M( ^LDA #$80
; b' L, Q; m K2 i4 ^1 E% ZSTA $A001 可写方式开启SRAM
& q" Z$ G; w4 kJSR $8100 跳转到子程序$8100
; F% J/ U% Z/ M/ t: z, }
% J% `% b! o$ f然后把Hxd里被覆盖的程序复制过来粘贴在后面:
% y* }" |6 t/ Z
! _* T1 r3 [: X+ w/ f2 q" T 6 Z; T6 W7 R* [5 G. k' W0 M
' W5 b% ~+ Q( H
末尾补上一个0x60:: ^2 D& `9 E/ p5 X; l$ {' q
RTS 子程序返回1 [4 k, p! H0 r w

# w% J1 h, a# f( z0 p8 N然后单击运行,ROM音乐响起,正常运行:$ ?+ K3 R/ L" ~+ T' ?7 i

& M" D6 B, x5 q2 W$ @ V! c) w 2 H/ A5 p) r8 f- H9 A1 g% j
然后转到NES地址$7000:
1 y E1 o# Z& u) O( R* Y ! `: b% X1 `, b$ A4 h0 v1 @7 G! s
- n# {. B a! z) |

( i% B0 Q. j5 s
& A7 u: o. f! m: K3 u . G1 y2 `. b- j2 l% H8 d' V
可以看到,$7000-7FFF都被复制了一片数据。' s! J; c+ V! A* x( ]
测试没有问题,然后保存文件:4 m1 N0 H4 G7 i; |& T; C

( [3 Z# n8 `) t+ D' G' g: J; b9 e以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。 w/ ?' e4 i; ?6 s
后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。: S+ g/ d& F& o
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|