|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑
5 s9 I' N2 F5 L- z8 W, u8 S
7 Y4 e+ o6 x/ {8 r[FC][SRAM扩容教程(Mapper 4为例)]2 a* _/ L1 R& |$ {+ _6 q3 e
9 x" O9 x+ s( }4 c6 X# O6 A
时间:2017.4.28
: ~' W. v5 k2 Q3 I$ _! [作者:FlameCyclone( A+ [! c/ F4 @4 |+ \: z
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
1 P" \9 [) G* o' S7 l+ uROM:双截龙2(J).nes2 Y, Q1 _/ y/ N, Q9 W9 a3 J' z0 l! j* n
适用:没有使用SRAM的ROM# q- ]$ Z7 ~+ ], P
1 E. p+ o4 x- l! T* P7 Z
首先用Hxd打开ROM:
/ V# ?3 c/ o) r! v9 q
! Z- r$ Q" m! l. _: D然后扩容:
0 V/ Z. n, W) E. N/ K; B5 l
9 A# e) Y# V# H4 X# ?
" }* S% @" R& n5 n0 u# z+ m
5 X* t3 U" Q. D7 i+ p% [1 S+ \6 r& M2 }4 A0 P3 O
9 f' V6 I& j; u z: y% m3 p
7 U- h3 X. R: M6 W5 e1 Z7 q
P! b ~* [ p5 c0 k4 q* ^8 w8 p8 [# u5 R0 S2 O% P* {
( l0 q8 p- d; C, ]4 W) N
先看看任天堂产品系统文件对NES文件的说明:+ {1 D9 x/ E; K& J$ @
NES文件格式6 i( }1 Z; O5 O
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。
! ~( ^" |/ X/ ?$ `/ T6 z& z偏移 字节数 内容
1 y8 O/ b' c* k3 x% u0-3 4 字符串“NES^Z”用来识别.NES文件
4 {0 u& I+ L. O6 P* D" I" s! L4 1 16kB ROM的数目 / ~' [+ n, _* z+ K n
5 1 8kB VROM的数目 2 c8 J* G2 }% u0 M
6 1 D0:1=垂直镜像,0=水平镜像
8 S/ \" E+ S1 v' o4 N# W D1:1=有电池记忆,SRAM地址$6000-$7FFF - D) j. A5 V: J b/ y
D2:1=在$7000-$71FF有一个512字节的trainer 5 C6 t$ p/ V6 k' [2 U
D3:1=4屏幕VRAM布局 , R3 m/ q+ Q0 ]# M+ x# l" p
D4-D7:ROM Mapper的低4位 7 P/ V' Q9 I, J/ P
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) 5 s9 X# q }' |8 s$ Z* j- o0 ^
D4-D7:ROM Mapper的高4位
. a% U5 v. C% d8-F 8 保留,必须是0
0 s; N* h# z; E2 ]16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前 3 Z; K- ?: U0 h6 ?! I% X: r
-EOF 8KxN VROM段, 升序排列 7 m; W- Y! ^; W7 P* s- e' Y
; l/ P' r* D+ X+ T) \9 E) P
然后知道这个ROM有0x08个PROM和0x10个VROM
* F7 u. W5 z* L2 Z接下来扩展PROM位0x10个:' n. c9 O" a$ t$ |. M
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
8 B a, \4 l/ j) n# ]5 n4 j- E+ Y2 V8 n1 e; J
由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:
3 S$ n8 Y3 `5 m5 ]5 b* U6 Q最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
) J2 T# m. U. L! I" i6 d8 T于是可以得到双截龙2的是:5 D' u9 {. I6 Z
(8-1)x 0x4000 + 0x10 = 1C010。
5 S8 x% E% B1 f5 r9 B" m4 o然后跳转到1C010:0 b* d2 |& T, G% u
% E: I0 G, g5 a3 q0 @0 ?
# T8 ]/ f1 y% ~9 b5 e O8 E
9 u4 z$ y. |9 |8 Y( B然后插入0x20000字节的FF:
- X% M# b* W3 a( n y' ~& Z
; T6 Y" [2 T) p# }6 C+ b
! u& d* e8 |( I8 f
& V' n. Z' ~/ D. j9 }: H8 t2 y然后保存:0 k5 t; u6 t( M2 u
# y2 [6 ]% w% w0 L5 \5 g" ?
, n# r+ t0 M, x/ B1 z0 W, i用FCEUX打开正常运行:0 S2 ?" b: i9 Z8 S
4 B! M2 L6 R; V$ J3 g查看文件信息:7 e$ W+ x$ [! h
+ B1 {: | j- [. W, z) g' Q- Z6 S5 `
接下来切页:
: u- g: z: V1 R* f先打开十六进制编辑器:
! R* x$ ]4 b& |0 h- k; |( s0 d6 M# G6 v0 t& ?, r
拉到滑块最后,看看重启中断
, x9 R: @5 L4 X' ]# P. F中断地址 中断 优先权
) a! F Q# e, o6 Z' P) H9 w) X$FFFA NMI 中 8 ?/ k! m! F" F& [2 m9 C" h# {
$FFFC RESET 高
+ p1 J: L. W7 t u& X" ?9 s! \8 g$FFFE IRQ/BRK 低
( C& @/ R) V. R; e& D6 B% G) D% K2 z- o7 E6 g% }0 S$ T
RESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
+ G0 J4 \8 `) D" z由此可知双截龙2的RESET中断$FF65。, P2 C! R# z, k
接下来添加$FF65的执行断点:1 L0 `1 ~6 x' Q7 \: |9 Q- J
打开调试器:
# J6 z3 U4 o1 }1 Z' n- g
+ a% |% w5 U+ c添加$FF65的执行断点:
) }- a# Y9 o0 }9 W' w" C% \: e$ H0 |1 j5 e/ K, q$ _
3 ?' `; m9 ?- D; R3 b3 x" h( P+ [2 \( b# q$ a, @% `( t: n
单击确定:: z7 i6 e q; p
" f1 G+ f K7 n% y8 a3 B1 ~ M; U& ?2 \) M h
然后重启ROM:, [( V5 U8 g; b5 z( X
+ d2 o/ e6 `0 }2 B1 v调试器此时弹出来:4 V0 q4 X9 o( `: K; B! B5 X0 l
! f# F; M5 j% R- X* }6 L* R
然后打开Hxd,写一段mapper 4的切换bank程序:
7 Y& w0 t/ Y7 k* C先看看mapper 4的说明文档:
) e* F8 q5 ]5 y) s( c% f2 u, cMapper 4
+ `$ U1 F C: ^) o
/ ]2 h0 w4 G; I$8000: 模式号
1 z$ ]8 Y4 J. _" y) ] 位D0-D2:
; }0 B) G7 c( \. ^* } 0:选择2KB的VROM存储体映射到PPU的$0000 }' h" X2 C3 U* ~/ \( R
1:选择2KB的VROM存储体映射到PPU的$08005 g* w$ g5 D5 X. e5 t- [
2:选择1KB的VROM存储体映射到PPU的$1000
" T* I' w* [# {5 a3 R 3:选择1KB的VROM存储体映射到PPU的$14008 m/ O' W; e/ K
4:选择1KB的VROM存储体映射到PPU的$1800
7 ]# E$ a2 o" g7 Q 5:选择1KB的VROM存储体映射到PPU的$1C003 K: ?2 T& ~. d6 k, g
6:选择8KB的ROM存储体映射到$80000 @% T' w% p [1 ]
7:选择8KB的ROM存储体映射到$A000! ~. S5 r# G( @ g( k6 w. o
位D6:& ^2 B- c8 {) R7 d. {9 w( B& E
0:允许擦写$8000和$A000
* x) M# x5 M8 Y) V8 ] 1:允许擦写$A000和$C000
- H3 z0 W9 Q: i8 J5 k6 I 位D7:2 e) l4 p! J# o$ `. D" ]& @
0:模式号D0-D2使用普通地址
$ Z/ n5 K( g1 {+ s% t# f9 T 1:模式号D0-D2地址异或$1000
& s* Q7 M: j& t7 f( x% ?. _1 {5 J; w: `0 W4 K# J6 P0 `0 d
$8001: 模式页面号( r: b) F4 Q9 @- c& m6 b
写入一个数(00-07),切换存储体到对应地址/ J; X( U( P" i* ?* Y+ m/ X/ @; i; t
! o3 K8 p; H: Q$A000: 镜像选择, F/ _" o9 M. j1 [( O
0:垂直镜像
% t2 H7 K2 l, u 1:水平镜像+ b2 x9 \$ s4 b# Q
2 B& u$ \3 k, ~$ J/ J; b
$A001: SaveRAM 切换/ G$ b* ~* R/ G# s/ {
0:禁用$6000-$7FFF
8 C/ e8 P$ m5 _3 \% e& Z, m 1:启用$6000-$7FFF) \: [; g0 [3 ^. e) u2 g
4 s8 u ]5 k% A% E8 V( {
$C000: IRQ计数器
+ D7 y( ?% P9 ]# E6 O1 r! O# G IRQ计数器的值存储在此处7 L# M5 S/ @7 B% s" u q
& J8 [ c+ z6 u- y/ u" S( F* h
$C001: IRQ暂存器1 N' x" S2 T* D/ c$ D: T+ |
IRQ暂存器的值存储在此处
) W, p3 ]1 t' E% _1 E/ d
* D+ k( U) s: i' R! r$E000: IRQ控制计数器0- D5 @' X* L5 g4 A
向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ; V: o2 G/ P" d' o4 W
' ~4 m/ w- k/ L2 _
$E001: IRQ控制计数器1
. A: N7 ~: U0 O. P& m9 H 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)
1 @- G9 j# _6 E7 S
. q& f( O$ ^0 _8 W; j那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:
! ]' D# {! F7 Q, {5 `48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68
0 F" v- J2 r3 Q4 O. m/ f/ aPHA 累加器A入栈# e0 e3 {7 l! v# k
LDA #$06 设置切bank地址为$8000-$9FFF
) r5 j- }* }7 F/ l8 DSTA $8000
9 ~, a' X4 m/ S: lLDA #$0E 将第0x0E号bank切到$8000-$9FFF2 _8 |8 F% V( X; K3 C- n
STA $8001
) d: G. O! w1 [JSR $8000 跳转到子程序$80005 W4 F. b* ?' o
PLA 累加器A出栈
3 f$ U' u5 r2 ]; g" c3 K, A1 E* |
为何切换的bank号是0x0E呢?
3 ]( W6 l2 C7 ]5 T因为在扩容ROM后,文件PROM结构为:; ]) d4 _8 R4 k/ s' u! ~* b- F
原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
# j* l( x8 L. K( i0 v
( L7 z% X& }3 k& }) W; B0 M+ xMapper 4切bank一次是切8KB,那么文件结构就是:4 m: k, A* a6 V2 ^* i. S
原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
1 i$ M: [" r: S& o$ c' C' r" N因此选择扩容的第一个空白bank就是0x0E号bank。
* V7 G3 o3 a% | c' q* {/ B, p' Z% Y0 `" P
1 w# b- | U% h7 a* m! L! e然后去调试器找RESET中断中可以放下切页程序的地方:3 d0 ?" N% ^- W' n, ]' x @
首先长度要小于等于自己写的切页程序。
7 p% I! @; e4 |3 `- R7 J# ^; r可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
0 k+ ] t& ]9 n0 U! L: n# g ^' f
, g1 y: D/ H$ F- _2 v' H' {然后跳转到$FF7C:7 P( m" R9 V; z; M K7 Q. P+ s
0 t/ O' D. c) f( r7 G T/ h! I5 E3 I" C
$ Y" ]. B* c( x; b1 v, C$ P7 S* V- d; Y" G: Q! a$ y+ f' D3 {* ~6 R. I/ |
8 [: ~/ x3 N8 B! \! t# Z2 C复制下可以被替换用于写切页的程序:7 e2 _) ~" d' S8 g* P$ B2 W
先选中,再复制:
/ W# V$ H3 t0 V8 }, D3 e+ E0 [4 D( T4 j. o% ?, z0 q/ C- b
- ^- v( ]% X% w, F4 e3 L6 l0 [+ A
在Hxd中新建一个文件,把复制的数据粘贴上去:
" r& k% z6 a2 L2 u. ?( ]5 J! l, i& }2 \7 f1 d+ |5 y' B0 [: m0 e0 m
( e- I* ]: |6 G- T9 q$ }7 B4 {. T; V5 H1 e2 t; X
+ f' n! {; P+ r% Y# ?, l
然后回到十六进制模拟器:6 T1 |- Q. b/ U6 S
转到$FF7C对应的ROM地址:4 a' i2 h7 L7 M: ]+ b
; E$ ?, k+ F! I& ~+ e& ]6 U U5 m/ d0 p% s1 r6 U$ b8 d& K
然后复制Hxd的切页程序,粘贴到这里:7 n* ]' ~6 [7 [ d% e
* z1 j/ U& {2 P* i# T( @
4 ?; u. d c" j% @& Z, p
' {! M. Y( v* @* d. t没有覆盖的用EA覆盖:' \" c! t4 E% ~& J' k. @+ K- W
v( |9 D. i2 E; X3 [! Z: h& z8 j打开调试器可以看到变化: h. x4 M5 o; b* p* }, P4 W# h
' h3 J8 Q- k. ~- G# a2 ^然后添加$FF7C执行断点:
8 o% p) A! U- y0 j; [2 |3 k) D
0 J* {! v8 }5 ]7 Z
2 _/ c$ ^; y9 S. s8 R单击运行:
0 B7 T4 i8 t$ Y9 p3 P U: a然后程序在$FF7C这里停下来了。# c+ z0 F. T6 W4 k; o
?+ A0 e: {% a5 ]- B. h9 C
然后单击单步进入慢慢跟踪,直到跳转到$8000:
# Z/ p! i S" H6 Q( j; t: F1 k
' s1 E6 H2 B4 h' K! {& B- ^: M然后打开6502_Simulator:4 o- l A* L5 z3 m
, c5 J, l( T& ^8 t: x
再打开我写的数据搬移程序:
+ U r$ `# L* R0 P h1 _* p
( b4 y9 p7 H! u
' d" S0 ^! r9 |. f1 x) @6 X然后修改对应的数据:
9 X5 F4 c3 A4 S. }6 S程序开始地址:修改比如$8100就修改为 .ORG $81008 k) |+ D+ V( @% b
复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
5 O! W \- E" k$ U+ _, k$ u从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82) f; D) n. Y( N9 f0 E3 L
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91
6 {! p y, q A, p F* V也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。( @% X! `9 T( j! I
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。
. b4 u# t2 E! m# m: [' C( [ P中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。/ U! \; l* `- @$ s% E# F/ r% V9 n
后面的不用管。! @9 a# u4 @, @4 \; S3 }
; ]% g" i% G+ U+ L; {4 C8 _: _设置完数据后单击编译:
! ^8 F2 u5 C( M$ k: v ]6 ^3 K! L1 i) U6 E/ h* I/ r1 V) x6 f
然后保存编译文件:
1 W: f# |5 s' t% l2 G t" m; V+ U& Q& p: @# ]
选择二进制方式保存:# |8 l! x' r: {1 ]! Z- `1 |& H
* H) D; k! T8 _) r( Y
$ A( ]8 Q W6 D$ B/ U! X( @. I+ W用Hxd打开保存的二进制文件:
7 g! g+ d+ i6 P' H! R' E) ^5 |7 \# k. C4 o @; I/ g! o
, A9 x0 D" s! x' e$ r7 }跳转到设置的程序开始$8100:
+ r" C y; l" y4 v
7 o/ n6 p, g9 |3 R4 V/ G: h" S- M x7 y, x C7 z
9 u3 ?" v: D4 d, U6 e
回到FCEUX,转到NES内存的$8100对应的ROM地址:
J$ S0 O2 D8 E; C! g* y9 J" ^
; r" `! Z2 _) p& b* ]- f
' }6 b0 y. d/ p4 ~, O3 Y* ~* L5 o% u$ K; H
; ~! l) _ }5 K! ] @3 U/ g' c: r3 T" d8 y1 s+ u# M
然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
5 g) V/ k% [0 n: A$ p* v
; q5 C9 W5 R( |+ n) h% ^ F1 D$ m+ ?
' P' _8 @4 s) \& R0 g然后转到NES内存的$8000对应的ROM地址:
8 F9 N2 D$ h/ k. S0 g/ f+ c) v) j" g: ~) r( C1 b
n! ~) X' R# Q i% f
; \. P8 h' o4 t9 N9 V/ T: \; |8 H' }
写上如下程序:9 v" L4 s: I2 |0 g0 z
A9 80 8D 01 A0 20 00 81 3 W/ @ d) C4 Q$ N4 `
LDA #$80( w x h! k3 C- j& u* n
STA $A001 可写方式开启SRAM9 H9 ~8 M1 e7 a% E z
JSR $8100 跳转到子程序$81003 k' \: o( X+ o
; @9 u3 E) t) s, E" y然后把Hxd里被覆盖的程序复制过来粘贴在后面:
! C" B i# }! I0 S0 r
( i5 S1 M( V, [% c( U8 o( e
& C' q x3 M' g; I" |2 X- |' A/ M/ K4 o
末尾补上一个0x60:
; A& `* X7 p5 p. W! n( C7 \RTS 子程序返回, b4 Z( l6 M0 E
1 N% E( Z- Z6 N/ {" O$ A
然后单击运行,ROM音乐响起,正常运行:
8 J- L( v3 k) I; V0 u5 _+ J# R
/ u* ]* W# u; T, {5 `0 b8 T然后转到NES地址$7000:
0 r$ j7 j5 Q3 V5 k0 S, `' t
% j6 x" ]. G, R' a b5 q7 I. `: {) M) n( x8 ]1 |
/ L8 F) z( h* J0 P
2 j6 J# k/ L: O, h# u
* V& ~0 L' r* [" T) L- a: `可以看到,$7000-7FFF都被复制了一片数据。
- I5 O/ N2 f" y6 t1 R. O$ I测试没有问题,然后保存文件:
& @& k0 F. M0 l' ]3 P" n; u$ P7 a* }% D/ P. c8 V5 X
以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。
0 A2 X7 [" U2 E! F, ?4 j: V; W' }! k后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。$ [. E, \5 P( C. A
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|