|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 8 C) `, R X' Y
' V& F: i+ A: F, W5 ^% u3 R9 ]
[FC][SRAM扩容教程(Mapper 4为例)]3 Y( n9 r' j1 [6 E$ I
# K- f$ A* i6 r) @# Z: _( ?时间:2017.4.28: Y# G1 ]" `0 s, [& b, P
作者:FlameCyclone
! @2 h4 I6 W' ]' g, ?工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator% J: p4 t, T! k$ P; Z/ z8 V! a+ \
ROM:双截龙2(J).nes
* o$ I7 @& T- F9 Y适用:没有使用SRAM的ROM0 d+ p. I! x f$ U" w* T! Z V$ Q
p! y. a) A3 M$ s5 T
首先用Hxd打开ROM:
# Q* {( Q$ z4 N; F3 h7 S+ Q) @" t5 A# m" F* C* x% I- Q; i+ \5 [) a+ Z
然后扩容:
6 L1 Y& I" U% b2 W* M+ K. b
/ y: K0 a0 |4 e& l& l5 Q9 L& E8 I% \4 c& `; s5 M, }$ h; c
. E; @2 C; H0 T- K7 \' k I" Y5 G7 s( g8 [& y5 w6 g$ P
3 A, j9 |1 W8 U) m. X
: s, q4 |' {) p7 \5 a% Y% u0 k+ `8 y# ~* P
3 d6 b" b5 y! y: Z
# d- I8 l3 O: U% W: u" w6 O先看看任天堂产品系统文件对NES文件的说明:! K- N* W' J1 |) { L( \5 Y7 ^1 T- ^
NES文件格式
( ^$ |, Z, [2 w$ G" y% b4 Z.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 3 V+ D3 T. h3 D7 ]2 K+ W( n
偏移 字节数 内容 & K9 Y+ c& n8 P7 \' W" T
0-3 4 字符串“NES^Z”用来识别.NES文件
$ D' k/ W' z$ m4 1 16kB ROM的数目
2 k' m* c# a2 c2 ^- F9 R# j9 g5 1 8kB VROM的数目
Q! O# c: }9 w# M7 T/ H6 1 D0:1=垂直镜像,0=水平镜像 " N0 u& B* B7 u1 U; S
D1:1=有电池记忆,SRAM地址$6000-$7FFF
8 Q3 {) }, f! _, p D2:1=在$7000-$71FF有一个512字节的trainer
5 s, x! L; ?* J D3:1=4屏幕VRAM布局
% U. R8 l& z: f: r# A D4-D7:ROM Mapper的低4位 ! e5 J# T: P- b# {
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) 7 z1 ~$ q4 m" z* s9 B
D4-D7:ROM Mapper的高4位
; S, j- }1 ]/ ~8 i8-F 8 保留,必须是0
4 G$ |9 p" J+ C' G) G" q* Z16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
B% {! K! m& W. T+ N* ?3 i-EOF 8KxN VROM段, 升序排列
7 e( s" m1 @* E" c: d1 `- B4 W/ k1 x* A @ n F
然后知道这个ROM有0x08个PROM和0x10个VROM
" z- v& _4 V1 x; u接下来扩展PROM位0x10个:
/ y: K' J' t0 e/ O先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
' t) O' I- f" t& K+ c# T/ L
+ R+ K0 ?3 |! j" X由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:
: B( O% ?- {5 |) J% N, `最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。3 q5 K. @- \3 f8 E9 Z
于是可以得到双截龙2的是:4 T. r+ m+ c! i ]0 ^3 s
(8-1)x 0x4000 + 0x10 = 1C010。. ]7 ~9 M8 o% z* ?$ y) N7 v
然后跳转到1C010:+ }; f1 n g& d% O) I6 f3 V
0 A$ j1 y7 I5 k& X* G+ \0 _- p+ s; T- t S7 D
3 y8 { f3 r5 b. b S3 e# p7 h
然后插入0x20000字节的FF:3 M0 i8 ]2 h6 I x! P5 t3 u
5 m) j% A4 |# B" K$ @' w4 R0 O3 ~: h W' q, U
4 C0 Z0 P* C+ |/ |7 A
然后保存:
- Y# ?* e u. l/ h1 i" o' @9 O o0 l' t/ p: N# q: _6 Q& \
- C/ h+ M T8 i {$ f" N! M) O用FCEUX打开正常运行:
* ~& Q9 e: D( Y; C, ^" ]# T8 a; q1 ?; @
查看文件信息:4 \3 e% _! t, n$ R
' `: V' @0 f6 f! J9 Q# R) T7 k# `
* ~: U) b5 r8 y; m0 F接下来切页:
0 K% X# e1 c. I6 i先打开十六进制编辑器:
L/ k" \( c0 W0 ?9 v) w$ s
+ Z/ x; g* p& ^( n2 x$ V: W J拉到滑块最后,看看重启中断
" I9 |) Z [# ?, D6 }中断地址 中断 优先权 ' h" H }( T* }" [0 n+ K. C# t, ?
$FFFA NMI 中 % U0 M0 t$ r2 C" T2 F) S# O+ ]
$FFFC RESET 高 2 y9 b* S% F. Z
$FFFE IRQ/BRK 低
2 c0 k7 \7 @8 }& S n
7 H) t) K6 L0 M, B8 HRESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
8 g6 X9 |0 r2 H) n+ r由此可知双截龙2的RESET中断$FF65。
. P4 G% A$ y) R. }6 G* ^6 d' y接下来添加$FF65的执行断点:) D* X% Y3 I( I' m; X" v: x5 {- S- y
打开调试器:
" g- {" D2 k' @6 H M0 x/ X
5 ]4 T) l) g/ G$ P; W5 j; K添加$FF65的执行断点:
+ v2 k9 N) t1 l! ^/ X% d$ H! K- J& N
- N0 a i; S6 Y5 t, b
9 U$ v! O2 f4 D# v9 N5 E% _( W: P; z; g( A" Z
单击确定:
; P$ [- `7 \# C0 [& y* g
* {+ q4 r0 O. K: c/ m$ A5 ~9 M. P6 f2 k3 {& J2 W& \
然后重启ROM:& U8 F6 I: W/ `9 m, ]% ]2 n3 B
- Q3 I' H: F7 r% A% R
调试器此时弹出来:
; @# p8 O& J- ^. ]+ } _: U& D2 V1 ~3 c" z& ^) a' M
然后打开Hxd,写一段mapper 4的切换bank程序:& p6 Q4 m2 ^* }8 k$ v# m
先看看mapper 4的说明文档:' o1 L$ Q3 R# M5 o+ F) `# z: j
Mapper 4/ r- h: \9 j. s' p" [$ c) q
$ g: Y& Q, Y& y [: @: ~# z
$8000: 模式号. T. k9 l S& O* _1 L9 ~1 B
位D0-D2:- ]- j j. s2 b2 F0 y" P# G
0:选择2KB的VROM存储体映射到PPU的$0000
9 v& ?1 ~6 _& ^. p 1:选择2KB的VROM存储体映射到PPU的$0800
: S) l7 k! i; E 2:选择1KB的VROM存储体映射到PPU的$1000- Z* E1 j' `, m3 w" ^! l
3:选择1KB的VROM存储体映射到PPU的$1400) }$ F# D$ ?2 M. v1 [. M
4:选择1KB的VROM存储体映射到PPU的$1800; ^! R7 ?2 h4 d5 I8 ^' j
5:选择1KB的VROM存储体映射到PPU的$1C00 u/ P% c8 Q! J4 }
6:选择8KB的ROM存储体映射到$8000# Z6 ~! G5 y# c* O7 w( |6 m
7:选择8KB的ROM存储体映射到$A000
8 I4 R G( ^; P; ` n2 J+ L0 _5 I 位D6:. V# S* ~/ Z1 T( e
0:允许擦写$8000和$A0008 K9 r% x7 Q2 a3 ~/ X
1:允许擦写$A000和$C000; i( {4 y- @6 R7 s7 `
位D7:
' q V9 V# S( D% r8 W0 x$ W 0:模式号D0-D2使用普通地址) T2 k* q; b) A( Q* r4 ^
1:模式号D0-D2地址异或$1000
& S) b: A' u# Y# A, z# E. Y, o7 J* ?5 C2 s I% F! n
$8001: 模式页面号7 W7 u7 F5 q b- d/ m" V4 P, T s$ ?
写入一个数(00-07),切换存储体到对应地址
3 N# _" B) e- G& d* T
/ R2 u- a$ u) d) o+ @$A000: 镜像选择5 |/ ?$ N! r9 w+ { X5 M
0:垂直镜像$ m4 c9 m; l5 |' v. ~/ ?; `8 ]. n
1:水平镜像6 {& h: L4 D, A( {4 H
5 _! j+ K0 w0 g6 H6 N& T! F5 s, Z1 ^6 L$A001: SaveRAM 切换
# _9 C. z9 w+ S, e; C 0:禁用$6000-$7FFF; O, m$ p4 P* s; w
1:启用$6000-$7FFF3 [: P1 k, T( N* X; Z- @
. C( \* @& ?/ b( `/ V
$C000: IRQ计数器: q$ f x* x4 N/ {- K
IRQ计数器的值存储在此处' n( H, F" ~- C* K% o$ e1 X) j
3 r" P, ~, e+ Q$ K. T
$C001: IRQ暂存器
* j1 {2 W% [0 e a$ C1 U U2 D IRQ暂存器的值存储在此处. n' ?1 f0 \4 C9 }
) E6 y% `+ b p) o# M) X$E000: IRQ控制计数器0* M0 O% d! b; M! y
向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ$ u# q7 o* ^! X! A5 ]7 ^* N
7 F* F" ~8 S( d6 Y q. z8 _
$E001: IRQ控制计数器1/ f0 o' A% X9 b, x$ d$ H% o8 Q. B2 a
向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)! t1 s G R* `" j; D0 N% W
* V! P0 F; h: g" @
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:& H9 U) N3 D1 T2 }6 V
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 687 P9 \: W5 ^% M( C. Q" f
PHA 累加器A入栈: K, i0 `& p1 |4 d$ B* K0 A) k- ]" P# S0 u6 f
LDA #$06 设置切bank地址为$8000-$9FFF4 T5 M; b, N' @ p
STA $8000: I9 ~1 R/ v4 H( D4 q5 Y) h
LDA #$0E 将第0x0E号bank切到$8000-$9FFF# B. K0 Z7 m) h, b: ^* G/ h
STA $8001
! Z" Y! Y8 h# L% S$ IJSR $8000 跳转到子程序$8000" M# R9 B- \3 F
PLA 累加器A出栈1 g: J8 r t3 `. ?# ^; B" a
@. `1 `8 L5 E% q; b
为何切换的bank号是0x0E呢?
7 i T2 o8 D1 f" s6 _# m因为在扩容ROM后,文件PROM结构为:
" |5 f9 i& \% [; I( \ Q" ^1 e) `原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
8 m' w" R3 _ Z* y/ I2 O. |9 H5 _, H; b# I8 c7 ~$ o
Mapper 4切bank一次是切8KB,那么文件结构就是:
) V/ [2 L n, Y6 k原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
5 @9 o9 _4 c( \0 |. H/ C因此选择扩容的第一个空白bank就是0x0E号bank。
/ ^9 | P# \- t3 a7 u; H
4 n3 g! X" z5 c( F/ _; p; u* ^: ]
然后去调试器找RESET中断中可以放下切页程序的地方:
4 S' n4 z! [' ~6 A2 @首先长度要小于等于自己写的切页程序。
# N2 S8 Z2 O5 i( D- z( ?3 P可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
( f7 w. ^2 j" _8 z1 f7 L$ L; A( I: w2 c4 W
然后跳转到$FF7C:
+ q5 I% o Q! G; {1 f" P# g" B" T/ B! l; m9 f
2 \4 |' s) r( @0 R
% F5 j; K6 L0 ~6 n3 y' C0 c
$ p5 _: v* k$ r& K, I复制下可以被替换用于写切页的程序:
* Z( ?+ f1 ~2 q% I; ?6 g/ P4 P2 r" u先选中,再复制:3 j. F6 Y( l6 U( i1 u% O
0 Z& }" x+ z4 ]9 x# W3 B" x3 F) k7 J% Y I
在Hxd中新建一个文件,把复制的数据粘贴上去:4 ?/ \* r1 v9 W/ s$ }- z$ r
* W, ~. C& c- v
: h' x; N, i7 F$ x" L4 K" b) I0 W4 ?8 T
8 C1 B' b/ o* M. ?1 ?" U& X) W. `9 i, ~1 j# w
然后回到十六进制模拟器:, U6 {4 u+ E5 W7 k% S0 @
转到$FF7C对应的ROM地址:! m( a6 \' G: P, O1 `9 ^
& P2 C" F! P- ]1 y8 [/ p
: X2 V! [9 \1 J+ d* ^8 a% P+ p; }; V然后复制Hxd的切页程序,粘贴到这里:, S: h- o+ q k- b
- i: p3 ^+ K. b" h' m' f* o
* H) O# g- {/ e' m
( f: N; _3 P" [1 r7 h没有覆盖的用EA覆盖:* u3 w/ T8 Z, x" W
* \1 b/ X9 t6 [3 H- H7 A
打开调试器可以看到变化:
: z7 j- R9 h) b
, A, v7 G% N0 B$ ]7 C然后添加$FF7C执行断点:
0 p; G$ L6 g: |- g b) g2 F9 m
J; x1 Y" K: C( N/ L V
* p, a2 @( F9 K( _ A7 ]( w# p X. h7 H& ^, t1 I1 j5 y D
单击运行:* H2 m- W0 X2 a: }2 d' t( t& x$ h( \
然后程序在$FF7C这里停下来了。0 H+ \: h7 e; W3 @7 j c0 I
; J0 J) `) l8 ^- [3 ?& c+ _
然后单击单步进入慢慢跟踪,直到跳转到$8000:# ?& u" K( ] I! I- S% j
& T4 c3 o+ n! ^9 k/ ?
然后打开6502_Simulator:
2 l3 f7 M- C6 }" N3 \& \5 b+ q2 }
- Z4 s( R, R$ m5 P3 E, E3 g0 Q再打开我写的数据搬移程序:% W5 w. T6 Z( v% j6 J( I$ _
% e! x! g5 t% t+ O3 ]* `0 D, y
% F1 h# z# Q7 U! X0 x* N然后修改对应的数据:, w, M1 f) |7 ~ }+ y, H5 B
程序开始地址:修改比如$8100就修改为 .ORG $8100
7 Y9 ^/ m9 R( @9 l9 O: \复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
7 \' I. s1 o2 I. k+ _; N从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82
1 a6 s; F8 M0 u- a5 M& |1 G想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91
" ?* D- m7 o. m H" G也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。$ ?* m. A8 w7 w
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。
& d2 k4 y" Z( m- | u$ X中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。
; l+ k& ~* M+ A# @, F: a后面的不用管。; U- V& I9 ~& ]: q
7 K2 R; F$ |" ^3 B# j( N
设置完数据后单击编译:; n9 l. c' D [4 P! M k
" @' y; `+ h! {- r, ]! `
然后保存编译文件:' k$ z. b/ c8 Y) B3 J
" R% _& Y* O* Z. n! s
选择二进制方式保存:
+ y1 u3 J& Q6 X% {" K
/ \( h6 w6 P' Y* j4 ~) _$ C- W+ ~" J, w
用Hxd打开保存的二进制文件:
, X" p& M+ o# M8 w' u; @( P
( w8 i3 v) Z {6 W' S# [. ]+ [& Q1 E) m) ~* H: Q) Q9 j+ W
跳转到设置的程序开始$8100:# q/ W8 @$ ^! L6 U/ d" M
9 b2 S" C& s& h9 s$ S0 \
6 e( k1 I$ p: K* `
5 ]) V% U" o8 E" M" ~回到FCEUX,转到NES内存的$8100对应的ROM地址:/ t( ]6 v( R7 |4 _- T
# o, d9 I6 m( q- g+ e% b/ T& Y% a: Z. P9 v
& h; A; S( N e9 b
i; K4 j+ S; x: @ d5 n$ W
' Y& e: X7 O. @( T- B) j
然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:- j P8 b( [% T/ S5 `6 L
) i1 A% M% ?& s
9 y7 ?0 P' s4 G" U" e# W- W2 J4 x. {/ A" T: p
然后转到NES内存的$8000对应的ROM地址:" d) p( D9 J/ ?" W
1 r1 @% F1 u6 K R; f' J) K1 p
9 K: D) Q% b, w! g. W4 ^
* n/ \: W0 n; @
2 S3 W3 i+ K( M3 y# _写上如下程序:
4 X* Y/ D5 u; z JA9 80 8D 01 A0 20 00 81
' D( s+ ^4 o' {LDA #$80
# Y$ @; k. G9 H; b# mSTA $A001 可写方式开启SRAM
4 y& o; I+ y- D* sJSR $8100 跳转到子程序$8100
3 ~, q8 f5 g" h2 z8 E0 b
, \ k& B+ j0 D% Z2 H, X然后把Hxd里被覆盖的程序复制过来粘贴在后面:2 x) K% Z- F6 d9 p" H. r4 G9 ]
( [; L% V1 e" |' ] | H5 r4 @
' o* s. G- H# j S6 y5 v* a+ D$ d1 O$ a: i! @: l% I
末尾补上一个0x60:% J, ~8 P1 k' R$ y7 d
RTS 子程序返回
- U5 H8 o# s, x6 Q( E; {8 G6 S2 s- ?+ F7 f3 y- ~, Y: n
然后单击运行,ROM音乐响起,正常运行:
% S& a' _$ X1 ~: l) J
# R3 X4 `9 c+ l- n7 I4 l8 P! P6 O8 w. s2 m, z7 m; J( B
然后转到NES地址$7000:8 M3 l" A- Y0 w# b% i: @
" F$ v, F$ m u: w, D
3 ], k$ e/ O$ K) _9 v: w
3 p4 ^8 k7 V+ X$ u
% s1 B) m. w# s7 P* z0 J8 g# [& v' f: g* J; i" P
可以看到,$7000-7FFF都被复制了一片数据。% S1 |" O+ [$ @9 I' d
测试没有问题,然后保存文件:
9 Y9 }, v, W6 v; ]- ~1 ]' d( k. y* @4 K
以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。# t4 [, H6 o$ c; w
后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。( D% ]4 [' U. W. t3 P
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|