|
|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑
3 k1 }& ]! q2 H! K) s( N9 H
; t1 I6 S4 y3 l[FC][SRAM扩容教程(Mapper 4为例)]! i9 a: E+ D8 c p3 i# O# @( c
- s! S. s/ q$ K时间:2017.4.28
( k6 e$ s% B0 G+ p作者:FlameCyclone6 K8 t& G$ G2 ]5 N! n
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
0 F7 d8 B" A) ?/ G: SROM:双截龙2(J).nes: }/ u; ~5 r! E8 v* B& D3 C0 K
适用:没有使用SRAM的ROM0 f- j/ U* Q) d$ G V, E% D9 \5 {
! r+ z1 p2 \& F4 O. _首先用Hxd打开ROM:
5 a" \/ m- H9 j5 r( w. T 3 h: f: @. J1 b& Z
然后扩容:* a; H3 j8 [4 H: g
, ?; f2 f; o0 f% d* j
6 ~$ Y/ m( e. x: f0 L
. B: B4 h* H1 n% Y5 f: D: J
" O3 x9 E1 o _1 |" |5 t
8 }3 a0 F8 Z% j+ j7 ?" @
/ L0 f2 ]) i3 `" n% h
; X3 ?3 @% k, e3 a9 w& z x, [6 `/ V5 ^0 v! D
0 L \4 b0 [3 x# N; s) E
先看看任天堂产品系统文件对NES文件的说明:* Y5 G6 g& j' V: Z3 D+ h
NES文件格式8 V, O6 D$ d( r7 @
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 - s. Y# l* ^. ]' U
偏移 字节数 内容 8 E5 Q" ~5 Z( p: k1 U/ r
0-3 4 字符串“NES^Z”用来识别.NES文件 ( f& k: r& b( [/ l. o" E" k. e
4 1 16kB ROM的数目 ' i& t9 |' U Q+ t) ~* O1 Y; [
5 1 8kB VROM的数目 ' g( u# v( T5 p+ I
6 1 D0:1=垂直镜像,0=水平镜像
5 E$ D5 Y+ \) V8 M& R D1:1=有电池记忆,SRAM地址$6000-$7FFF , w# @4 F- |1 W# E2 o X
D2:1=在$7000-$71FF有一个512字节的trainer 3 |; ~, D1 F6 K" b! w/ u2 ]4 Y
D3:1=4屏幕VRAM布局 1 t$ s) K- I. C6 N# `- ]- S& O
D4-D7:ROM Mapper的低4位 . j( r5 C/ D t5 N. q/ Z. J: {7 @
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) ' g, h$ E: R, ?+ g, k- H
D4-D7:ROM Mapper的高4位
4 c/ a" T$ a# D, z8-F 8 保留,必须是0
5 E7 h& @/ z: W/ Z3 e. O2 R16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
/ m5 y- C& x! y1 G: R! X2 q-EOF 8KxN VROM段, 升序排列 2 u2 W0 ~ \6 P( S, Z7 w, l
8 {& P# ^8 X/ D* C; V& l4 U
然后知道这个ROM有0x08个PROM和0x10个VROM$ ]% S, J6 y8 C4 i- ]# y
接下来扩展PROM位0x10个:8 [& A+ G! f; V
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
( |% \% r& y$ d& ~0 [) |: ^ * ?# K2 m) D- m
由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:4 |0 }+ Q, P. P& @% Q
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
, g$ [" b3 y3 @1 s% e于是可以得到双截龙2的是:
1 Q) r( d, z K- _(8-1)x 0x4000 + 0x10 = 1C010。1 h4 T7 S, Q0 {
然后跳转到1C010:7 I: I% a d& d# x
( n0 d# e0 X0 W. F" w/ e6 v

! `0 G: R' j3 a# Y' c9 Z9 b
: p3 P6 p. Q$ q$ p$ V+ ], t然后插入0x20000字节的FF:
2 d* F3 P/ x7 X+ X9 Z - {( u/ b# s5 r5 U. O! t- V0 [
3 [! y: Y+ Q5 e- A- @- g# @
. t+ u" x$ ]% G7 `# D/ K# ? k
然后保存:
! J' C$ ~* u+ V 6 E- c0 A# D1 |

1 s% K a! h( H) }) n( S" P用FCEUX打开正常运行:
, ^+ T1 q7 \( | 4 Q/ A+ [6 w: }: D
查看文件信息:# s0 z* Y! i# _2 t
% l- Y, t7 C M& @0 P6 ]

6 c# `: D; v5 c6 o7 F( |接下来切页:
( E) |+ a9 K* D% o% u/ a2 M先打开十六进制编辑器:
# X1 X; r" \- l1 e
0 [8 Y" z3 l2 q6 N% ~. t拉到滑块最后,看看重启中断3 d: [. W/ S( G, } h( M
中断地址 中断 优先权
/ `. w7 k1 q% D A- {& C2 ^) D$FFFA NMI 中
6 x: D9 v+ q, b. x- J9 W" E# Z$FFFC RESET 高 - e9 Y, k( U ]# Q
$FFFE IRQ/BRK 低
, i5 u, H2 `# e2 z6 ?6 Y' i
' I/ l3 r5 F8 w# MRESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。" O) e' y! l) n, I
由此可知双截龙2的RESET中断$FF65。; j. t( V7 L, f9 L
接下来添加$FF65的执行断点:
# y( _* S( H& Q, O2 X0 v打开调试器:1 h6 [7 W3 i3 O1 n w9 [
) j# S( r# K0 P. i ]
添加$FF65的执行断点:
; Y) v2 {& A/ J8 n" v _/ s ! c N3 E! x! u: Q+ Y! v& ^7 `2 y

# j" |" u) {! E# s' k* L: w0 f. Q( E' e
单击确定:9 m0 Q$ R, M" ?" A; } j& \
4 F5 m. I: `( N7 ^. L4 n
' \- l% M5 U6 ]0 G5 f然后重启ROM:! q9 A9 I h2 _; A% K8 t0 V

* }, ^& ]/ ?6 _" c调试器此时弹出来:
Q! g0 b: N: c5 X/ d 0 K- z$ M X0 G4 g& K
然后打开Hxd,写一段mapper 4的切换bank程序:
_7 W9 g4 n* Z( R0 W' ?! ^ I先看看mapper 4的说明文档:; u4 O. O" F1 X m4 w
Mapper 4
% K) `& n8 z5 _& k
( \& f, A5 s/ I$ ~3 Q$8000: 模式号3 X: H2 p) H3 x& o: |7 Z5 D/ Y7 H2 B
位D0-D2:
' ~( _4 U: i; V- M9 [" T" E 0:选择2KB的VROM存储体映射到PPU的$0000
, T: n5 C- A* G 1:选择2KB的VROM存储体映射到PPU的$08004 u* P& F2 x% ^: j+ r" M/ {
2:选择1KB的VROM存储体映射到PPU的$10005 x" i3 j3 [+ V7 F- K
3:选择1KB的VROM存储体映射到PPU的$1400
9 r0 p3 F8 ]% L/ o6 E/ V 4:选择1KB的VROM存储体映射到PPU的$1800
& ~% ~6 T U5 p" C 5:选择1KB的VROM存储体映射到PPU的$1C00& i. Y3 E: Z2 r6 s/ P Y
6:选择8KB的ROM存储体映射到$8000) b! X% e! t/ r7 u" ?
7:选择8KB的ROM存储体映射到$A000
1 {% d5 j1 t. m1 U7 x; ^) f7 }! Z 位D6:! s3 q6 [( O9 |" W9 y+ \% e6 ?5 l
0:允许擦写$8000和$A000
# E3 \: d: r2 h4 w. A' Z. T& j 1:允许擦写$A000和$C000
6 S$ l8 ^1 m( H/ X9 I 位D7:4 k% g( s# u+ Q
0:模式号D0-D2使用普通地址3 ]! u9 y# Q. k7 Z+ v' }) a9 v
1:模式号D0-D2地址异或$1000
3 P/ \! [6 d; B& t1 P3 m
, ^- [& r. H* m5 d: f: p$8001: 模式页面号1 r5 J F( B. X1 t4 f
写入一个数(00-07),切换存储体到对应地址) D6 v! Z3 a0 f0 ]" T( j
+ }: H0 \5 e) U$ f$A000: 镜像选择# D' D3 q* U( S, Q+ N
0:垂直镜像
! b- B ]2 ]& F& b9 R1 ~; Z/ _ 1:水平镜像
7 G6 I+ t4 i% a0 S- r" _
' \ r# V( G( {. G* G$A001: SaveRAM 切换8 Z' g7 [& e( q; Y; q# M
0:禁用$6000-$7FFF
( D! U2 _/ ]* j 1:启用$6000-$7FFF
# y9 T* {+ {- P: n5 a, u7 t# v. r6 S$ G! f7 o0 _
$C000: IRQ计数器3 D8 }5 w* `1 N6 t) {
IRQ计数器的值存储在此处
0 |. Q O6 y$ h
1 C: `& D9 j) v( S6 E$C001: IRQ暂存器
& i0 j1 l/ S8 Z, P( b. K IRQ暂存器的值存储在此处
; i- U% p$ M6 p) b. y$ F
1 C3 |2 }8 ~/ V8 Y% J$E000: IRQ控制计数器0
% p! ^& v) P, H% Z0 B' A 向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ3 t% E Y U5 k) h+ S, }+ q L
; l5 o, }: d; W+ s t' l- K4 m
$E001: IRQ控制计数器1
2 T( {( ~9 r8 @! E$ | 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)9 N8 e, n3 L6 L
8 V0 `9 X9 F: g) x+ N那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:8 B+ T" ]0 E' Q/ I* V
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68
) [9 h, o5 B- L- H2 c/ a0 fPHA 累加器A入栈
2 ?1 M/ y* @. U( NLDA #$06 设置切bank地址为$8000-$9FFF
+ k7 g2 E2 w# [" k/ }STA $80006 H' I* Y( _+ C" o
LDA #$0E 将第0x0E号bank切到$8000-$9FFF/ U5 R& y' c! j3 ]( r, \
STA $80011 x6 Y! L \% [, b4 N
JSR $8000 跳转到子程序$8000
) d) e7 U5 F# c* F% z9 U2 lPLA 累加器A出栈
9 u) G6 J% n: d9 \) E- U7 D
9 c1 K+ ] F) y& p+ }为何切换的bank号是0x0E呢?( ^: m+ Q, T) k( h/ S
因为在扩容ROM后,文件PROM结构为:) g' `0 M* o( ?/ y4 ^8 w* |1 p! ]
原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM: |# P, `7 U' g- G5 d
- o- c. S( C; r$ v7 v6 O) z1 U2 n8 ^1 t- m
Mapper 4切bank一次是切8KB,那么文件结构就是:
6 |) e$ ?+ }7 B, u原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)6 B6 z* H. f/ O8 `5 r+ D
因此选择扩容的第一个空白bank就是0x0E号bank。7 L) w) v( y' [1 u# M

0 |7 f' V0 Z. Q+ I6 ]- A- E" Y2 A6 V
然后去调试器找RESET中断中可以放下切页程序的地方:% Z& D# C+ J" E: P% R$ ]( E
首先长度要小于等于自己写的切页程序。
- P" ]% {4 `" D8 O' v5 o可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
- c* T- B: `5 ^. H
' o* B; T9 c6 t3 e( g8 d4 H然后跳转到$FF7C:7 K1 g! }4 e& y' n$ k; @
3 m' r1 @/ x' }- A! D; [3 u

9 u2 y4 E# S& A0 @4 j) y
6 o. T/ o: z6 G+ w6 V1 n- W9 i- j; w( E6 j9 w$ q7 O
复制下可以被替换用于写切页的程序:; U1 L+ U4 B4 W$ q
先选中,再复制:
# l; F% Q0 s0 b* l7 v& N
5 o1 W9 M! p# W" S' R0 Y8 h 4 O- n/ d0 X: G! d9 W
在Hxd中新建一个文件,把复制的数据粘贴上去:2 ]- u" z( X' G; Q8 Y" P2 _; L
3 P# V/ n: W5 ]+ \

2 B6 I% P" q( |1 \! v
" L, Z, V1 s6 B, p4 t v3 {5 l8 B! w
然后回到十六进制模拟器:) { L3 V4 r& E4 Z% r: f" u& |! D
转到$FF7C对应的ROM地址:6 L( h5 \7 M n$ h. H8 L- Q+ s
G3 \$ I4 A [' c: x0 R" b3 t

8 l- |6 P" ^1 Y' H9 A* D& c* P然后复制Hxd的切页程序,粘贴到这里:
8 T) n& E8 `( c+ S6 O H/ O " Y9 y( N0 h9 |$ d) t o

% h0 Y% Y( e5 A 3 H. g/ U. s' G
没有覆盖的用EA覆盖:
7 }' Y- V- T' k2 s+ u* g4 \: O- c" w 1 d" d5 A! |. u
打开调试器可以看到变化:9 [ X# m6 I$ l( f, r

$ l; M- |! v6 j然后添加$FF7C执行断点:/ R8 Q' c" i( \9 J: J& w

4 U G4 A2 Z T) k1 N6 J
" k$ K* O( ], w' E" p& G1 l) p, L
/ G$ y6 _$ n1 M, F. j' `单击运行:4 f8 d3 F5 t9 z
然后程序在$FF7C这里停下来了。: [2 h; y! T( \6 Z
) Z# O1 v7 c) C3 Y! p$ P/ ?9 t; p
然后单击单步进入慢慢跟踪,直到跳转到$8000:
- q# g2 C0 q/ {' _9 C# J
& N$ d. O& h' k1 h然后打开6502_Simulator:
6 H) r6 x% g* ?( W }. |; D( J: N
6 R2 |3 U; n$ z. m2 o- u再打开我写的数据搬移程序:( ]# ^$ x; k, |- z

3 Q: m0 D8 S" Z) R9 A
: Q) k1 i m0 E& i0 g: I然后修改对应的数据:" r6 X8 y3 h4 F
程序开始地址:修改比如$8100就修改为 .ORG $8100
* F% {( I4 ?, b8 N% m7 x' h4 C复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
: a! s- R: ]" C2 X从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82
# d% D2 i' d2 I* ^ O" |想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91
: v' m) v% ^+ d* q: U/ H$ @也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。
$ Y1 i. O. T6 \. a( k- W2 t如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。2 ^- G$ o* r1 r4 t9 d( m: F% e
中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。: h6 T e9 ?; z/ C
后面的不用管。
" E* Z3 c$ G$ o! a
+ ]4 j1 J' d8 z5 j5 B. w! H设置完数据后单击编译:5 Z& g7 C' d; Z* n! }) n8 U# W

: R! A0 m, E2 S; p然后保存编译文件:
- d0 f- y& d( X* G$ Y , _ P4 f$ ^4 g" h' e$ m
选择二进制方式保存:
, J9 S' i) W: ~. J' d4 V# _0 t
; G9 D8 x2 G: t1 F4 _
9 s3 m/ @4 i! o' |# s6 V用Hxd打开保存的二进制文件:" F4 X, w- s4 k: |/ c8 A" _
' @3 D( O) l0 Z7 W
+ ~% _# W7 Y: G1 t
跳转到设置的程序开始$8100:8 t* \7 y# m: Y# B, z

3 I: J2 ]6 x3 h/ \: ]$ v6 G ) H0 X* w m6 f: A) S: `
# v6 W1 f {$ R' K D3 j: b
回到FCEUX,转到NES内存的$8100对应的ROM地址:
V$ e/ @( a: ?* q) U
) e1 a) x. ~- A9 E
4 Y& O! l3 j6 I- v5 g' O% T
$ w& a( b6 T/ y0 D1 Q) S* O0 t& W 8 a W* G/ X$ r9 i: s- l, c
) N5 r, r" ?6 c* ]: K/ J
然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:# Z; `* Q) Q) H. C6 {
/ q0 O. ^4 t* u/ V( {
! b& ]3 I5 S- z. G) N
l3 i" h6 Z5 x2 \; a, {' `6 }& c
然后转到NES内存的$8000对应的ROM地址:: ?3 T* K/ B a5 r

$ g/ ~3 l* W. F8 ?4 N7 F3 |0 N2 N7 q0 y 4 @! L$ N) l# n$ `

9 H' c8 A# D1 v/ y$ z ' m: R. T! Z( g8 q
写上如下程序:
3 S! @ B+ w G3 j7 VA9 80 8D 01 A0 20 00 81 . k A* ]6 ]) f$ f) d M
LDA #$809 e+ f+ X) c: D) J% a2 Q9 E
STA $A001 可写方式开启SRAM8 l! c0 f/ O- M& s
JSR $8100 跳转到子程序$8100" n- u: ~2 ~( }/ a* y6 ~

( T! x" q$ O1 E+ E `/ R然后把Hxd里被覆盖的程序复制过来粘贴在后面:/ ]; t" R% Y# L: w
& V6 a% f; z4 m0 v: A

6 a9 D# y+ x& T* d! [4 H R
. F" P+ X& J7 v. K% E末尾补上一个0x60:
8 t. X. }7 I3 _, a" ^RTS 子程序返回+ v0 A) s! |3 s, ^! W
$ C9 e: _1 N- R4 p+ n
然后单击运行,ROM音乐响起,正常运行:
7 P9 c1 ?5 m/ P6 {/ q1 ?! ?
" o4 s4 p7 m8 J2 T) r
7 {% ~! W( q: x H- k" u然后转到NES地址$7000:4 V7 g8 I7 e# ^& ^! x
2 `$ [$ }: x# l" L
! [7 g6 V" n. K* i z p/ T

. F9 \( i2 b: Z3 G. D" D: J. V / c/ @( f$ K T; L- p8 p9 A

# x. P+ n( X2 U# O$ u可以看到,$7000-7FFF都被复制了一片数据。
/ Y, N: o, i' Z2 J5 W9 k) D测试没有问题,然后保存文件:* l& B: @5 y Y, y

8 }3 Y5 U9 D- B+ i$ v以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。
, k5 l. q6 o! f1 Y6 x, B后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。8 R+ M, V. i" c
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|