|
|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑
6 o) R0 B+ o/ R/ I+ D$ v' q0 G0 N0 s
[FC][SRAM扩容教程(Mapper 4为例)]: | L/ i& r! B5 p j8 |
) x9 Z/ Q* K* w9 Y时间:2017.4.28
m8 ~) G8 b, y- |) r* F作者:FlameCyclone+ M% y- ?1 d3 X; \, [& [
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator- `- _, @6 V2 ]; {
ROM:双截龙2(J).nes
$ A! D% M2 v9 C' L; j4 o# x适用:没有使用SRAM的ROM
* P5 N0 t( J* O
f8 N! \5 q6 q首先用Hxd打开ROM:
8 p$ d! W# ~( O4 h- F 4 }5 ]1 _# v' E: ?
然后扩容:
) s9 I' Y6 b! Z
4 E( e2 |+ {: d/ d, r/ B/ v: r- S9 L, I6 B" X: ` k9 }
* D' S& l+ K6 Z- O7 R
% Q& I) `) l; R, o4 ? b/ g, @+ l# v
; v4 p1 i" y) z' L1 _0 c/ ^
: \$ I6 C% [; p, F* D% ]
6 F2 u% e3 C5 I! X* n8 Z6 h' n' P6 {$ c7 s# z0 a
先看看任天堂产品系统文件对NES文件的说明:7 f3 Z6 _, _5 c: P& k* w2 f
NES文件格式- ?$ I3 c. g; K* V9 s
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。
; c+ J6 z" Y: R( V' F偏移 字节数 内容
& S8 d/ b q4 Q" T% T0-3 4 字符串“NES^Z”用来识别.NES文件 # Z I3 @# V* l1 q/ E. f3 f
4 1 16kB ROM的数目 0 J3 O% {7 s, K' s) t
5 1 8kB VROM的数目
p2 I+ E" e' N' [! B6 1 D0:1=垂直镜像,0=水平镜像 : _. F" ?" a6 y1 y- Y
D1:1=有电池记忆,SRAM地址$6000-$7FFF ) ~1 v# r3 m, X3 ^$ X) h
D2:1=在$7000-$71FF有一个512字节的trainer ' l3 L& J( h2 O2 n6 ~3 o9 \& E
D3:1=4屏幕VRAM布局
4 u: u7 U7 V3 @ D4-D7:ROM Mapper的低4位
7 ^( V6 Z& f, }4 ^+ |. Z1 |. {; c! V7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^)
6 ^+ z/ V. t" S' m D4-D7:ROM Mapper的高4位 5 w, R% j0 J. Z2 S/ A8 g
8-F 8 保留,必须是0
. T2 [- W# T3 |16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
2 X" i6 m M/ j-EOF 8KxN VROM段, 升序排列 [, j/ j' L1 Z7 K! i2 J0 S2 `2 s3 c
, ]% J0 U9 v2 t/ j4 |8 C6 L, K' @然后知道这个ROM有0x08个PROM和0x10个VROM9 T% b' x# q- r$ m8 g* P& ]9 O+ }
接下来扩展PROM位0x10个:
. F% u7 o0 h7 y5 P先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):" W P I9 U: H

9 d- }" y, r" H# ~由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:" O7 M2 C. o- Y( B) H3 O
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
+ I8 K9 N |# @于是可以得到双截龙2的是:0 i- j+ d" T, W I
(8-1)x 0x4000 + 0x10 = 1C010。6 b5 T' g! |6 O8 e |, x3 g
然后跳转到1C010:9 X$ c! }. }# A' B v1 ]

7 D+ `+ M$ B& S3 ]( g1 V X
) W2 `' B" e N; w7 N) I3 _( M* [. b8 K3 V / u7 \* l6 a- @) |( J8 ~8 [2 X# Y
然后插入0x20000字节的FF:
' v, A' m" s2 {3 _
8 ~! D) F, `9 S5 a6 Y , o7 w6 y7 u% P6 h9 w) }

9 K/ E7 W6 f$ ]然后保存: F0 c$ X- E9 g$ p9 n! M
0 n: S' L% B. V. H! }* ]) p7 m3 [

6 X- }" V! {! j5 a' r% c6 o$ T8 G用FCEUX打开正常运行:- v& [. ?- i, H, v/ t

# J$ m- P! W2 S& C$ ~查看文件信息:
W# D4 c1 d- Z% A7 L2 D; |
- O' {4 J+ ]$ L1 E% H
6 }# c* }" Y& H3 @' V! w接下来切页:
* E/ e! l" e% b5 s) L先打开十六进制编辑器:+ I' Y$ w2 ^# o4 O+ ~

2 B P u; h3 y1 e! k. V拉到滑块最后,看看重启中断/ H- S! V; [) h+ }2 N- E5 h8 G
中断地址 中断 优先权
! v: d3 G1 y3 R7 o5 z( h$FFFA NMI 中 # G7 O, l5 Z/ s7 b6 u
$FFFC RESET 高 5 l5 \* P5 X5 D8 o* J+ h, P
$FFFE IRQ/BRK 低 ; L+ |! r% `8 z t; G" n

% p3 `1 X3 \9 ~. w5 ^1 mRESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
5 j( D. f7 X, v* F' l1 g, p9 ]由此可知双截龙2的RESET中断$FF65。
4 c* _- P. K Z7 \, v+ }接下来添加$FF65的执行断点:
. g) I& v" B: T4 h. p8 J) T# e2 r打开调试器:. s' n3 |2 ]! R/ ?1 y% Y
; X, \, z; U* T0 A* P4 X
添加$FF65的执行断点:5 F7 R, L6 [0 K. x7 K
/ Q! R' h0 W2 }, b/ M
* P6 c) Z- y' h! ]
& C" b. g) P+ v单击确定:8 V' m( p+ i' p; U

2 C, W' \- t# A
) f3 Q' f: V, M: J$ h+ X# u然后重启ROM:! K* w5 N. I8 f+ a
% {: O( Z7 x+ Z ?# X5 h2 I4 F
调试器此时弹出来:
! {' ]- [' C- K+ u" o
0 y) p- Q6 J) W) _: F `0 C7 D- n然后打开Hxd,写一段mapper 4的切换bank程序:4 G, o4 U; S3 u% Y3 }9 e
先看看mapper 4的说明文档:
/ Q/ S* F8 }. p; U, F& y+ E$ m3 AMapper 4$ g# l. V4 Q' K1 C! i( n$ d7 M
+ ~% U$ J5 M0 S+ v; }$8000: 模式号! v2 J4 b' j8 _. f- f
位D0-D2:6 @3 W6 y( `) b: G
0:选择2KB的VROM存储体映射到PPU的$0000/ \- M T, s4 [+ g3 X E6 c
1:选择2KB的VROM存储体映射到PPU的$0800
! h4 b' I1 K3 _" h 2:选择1KB的VROM存储体映射到PPU的$10000 j, g. t& ~4 w6 v
3:选择1KB的VROM存储体映射到PPU的$1400; I; b/ ~% u& P3 N8 U0 r
4:选择1KB的VROM存储体映射到PPU的$1800' O# N, n7 z+ D+ l
5:选择1KB的VROM存储体映射到PPU的$1C00
g* L) Q6 W/ N. Z) a+ {$ y 6:选择8KB的ROM存储体映射到$8000
6 P0 s8 }+ R3 h, H# B. Y1 {8 H, o T 7:选择8KB的ROM存储体映射到$A000
2 U: P+ K/ \/ ? 位D6:' I' T: @* o+ y4 O' v9 S; N% p
0:允许擦写$8000和$A000- I$ D0 E9 k" h
1:允许擦写$A000和$C000
# ?$ o- b- Z7 u 位D7:
3 G3 s* ]# ] [" \ 0:模式号D0-D2使用普通地址+ i* o# `# A6 e: V
1:模式号D0-D2地址异或$1000& q2 p8 ?1 S; p- R7 o7 W+ P# O* o. R
! }' k9 Q9 V1 I3 {, J/ R8 ~
$8001: 模式页面号
* f" Q; C2 t& X6 H5 y 写入一个数(00-07),切换存储体到对应地址
( v0 g' m; f- d$ e+ x
I( V7 }" A+ P1 ^+ _0 G$A000: 镜像选择
. [! |& P; y* t& C/ Q5 t1 u0 W# p 0:垂直镜像; @; d0 p1 b& U( |: ]
1:水平镜像
3 C: A7 w, ^8 }' x3 c. d2 q# x' w1 o( o* ~7 m
$A001: SaveRAM 切换
3 c* A2 j% L3 m+ C! [& h' o, l. {+ S 0:禁用$6000-$7FFF
5 i& c4 A3 Z5 ]4 P' E 1:启用$6000-$7FFF c3 a* j% Q2 q, [ @0 \
+ [; D! ?9 S& R- r0 H% _# Q% x; G$C000: IRQ计数器* c2 m" o4 }3 c+ J6 ]6 Z# W& _
IRQ计数器的值存储在此处6 m% {0 h& h& s" _
; n) A; x6 W A: b
$C001: IRQ暂存器
" j& B. d, Q# j1 y" Y1 ` IRQ暂存器的值存储在此处
2 l1 g( W1 ~0 T8 ~4 E! b1 Y
# c1 {8 J8 h/ n- E$E000: IRQ控制计数器02 v/ E6 f9 Q5 s5 H, @
向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ1 J; m- i5 I3 b* S4 w: f
3 _* z8 l' h+ O9 r$E001: IRQ控制计数器1* e- d% f, |) m& k+ Z( y
向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)
' {/ i, A' y4 c) f4 N$ g: \1 z. U7 y0 b! H
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:
- h% G# W8 X1 S48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 684 e9 Q# e6 [2 \
PHA 累加器A入栈$ Y8 d% X, a4 D
LDA #$06 设置切bank地址为$8000-$9FFF+ P0 n( z0 K. W& e+ K
STA $80008 O- y- \# J% O. M
LDA #$0E 将第0x0E号bank切到$8000-$9FFF
b1 ^! [; G# Q6 ^$ }) P! ]STA $8001
) {8 A/ e8 `0 q7 q1 y0 EJSR $8000 跳转到子程序$8000
/ J: O/ N. e& P2 J- ]1 ]% x' BPLA 累加器A出栈
: u4 e% r7 P$ n
! S2 F- c2 k7 m& @; c" S为何切换的bank号是0x0E呢?3 i( T6 f x& Z# D
因为在扩容ROM后,文件PROM结构为:
$ k o+ }& S9 z原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
, {: ^+ X- Z/ v2 N. c) ]
% f' J! L6 c5 T- r: WMapper 4切bank一次是切8KB,那么文件结构就是:
9 N+ Q) X7 [# S原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
3 N$ y) m( |6 v$ t7 r, B8 J因此选择扩容的第一个空白bank就是0x0E号bank。; b; `$ b, l! h+ S# ]/ `, K5 X

9 x* i9 ^. B! o, t+ N/ I( Z2 Q: h8 g3 R* y0 b
然后去调试器找RESET中断中可以放下切页程序的地方:
& n# r4 ]) b- x+ k/ S首先长度要小于等于自己写的切页程序。
0 u1 U: [" k3 l& h) v可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)/ f: q+ j, \5 ]! N

! a9 o0 s9 V9 S4 ?# N: v w然后跳转到$FF7C:5 V8 J+ e" |1 |: f+ ^7 I- }* G

" }8 z6 h z3 ? E" |0 {* k' \
* l6 t* \ B+ d' [+ L8 a! t 0 G: R7 T: b4 g3 c5 P7 O
6 X8 V H7 B( {2 e& [
复制下可以被替换用于写切页的程序:' h: C2 s3 [/ s: R1 e
先选中,再复制:$ \ F/ T8 E. F- D5 A7 @( }
8 U4 K+ o6 Q6 |7 H

/ ^$ N$ o& t" {7 p在Hxd中新建一个文件,把复制的数据粘贴上去:+ ~" e0 k% @, e' \4 S& X6 U
( F% ?: D0 P9 q$ _$ k
% N) }2 o2 _# v- C2 K( y

' z/ t. E$ K3 X/ h% N: {
' i: g" a& Y# [/ N: M然后回到十六进制模拟器:
5 F& ~3 `3 s) j$ Y' }转到$FF7C对应的ROM地址:
: Y$ Y5 Y8 x/ J Z8 T6 S- ~0 t ]# w( h9 B3 C2 c* q+ o) C8 M
' K+ C6 ~7 }/ e1 F
然后复制Hxd的切页程序,粘贴到这里:
, b6 c/ K `) M7 R. L3 m
* Q& @9 h. R5 e+ C5 F% T- b& c2 v / a& k1 c) g5 e7 N
+ `- w* f5 f* H" Q
没有覆盖的用EA覆盖:' S6 v4 B; b$ g( ] T
, I# N; d6 L5 d3 v; ~
打开调试器可以看到变化: ? `. t# H' ?1 \# m m* B

. e i. R1 x$ e9 _然后添加$FF7C执行断点:% v9 E" D3 e7 V1 c4 Z3 c, }" E; [) a
* ^7 `8 n7 \* s# L2 o8 d' j, S4 i$ P
3 d8 U# M2 D5 {% U* P! t
; `2 ^& f c3 j% k
单击运行:
4 P1 b' ^9 {+ x然后程序在$FF7C这里停下来了。
/ Z' `' h4 J7 K5 m' _9 I + l! z! D* [0 ?
然后单击单步进入慢慢跟踪,直到跳转到$8000:
$ X$ I* w" w+ I : Z2 F5 d" y ?
然后打开6502_Simulator:* v" H% F! z' L

3 H2 D" D1 a8 n& x% Y! J. g再打开我写的数据搬移程序: @$ `" v8 i8 z

3 M/ P8 z! F7 C7 f! J: B 4 \* k9 d' H9 f8 J* Y: ?
然后修改对应的数据:
- N: x3 a' c* A* {* k# b程序开始地址:修改比如$8100就修改为 .ORG $8100) P( v- P. u9 Z
复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
; i- H* q1 t: O4 T从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$829 U8 Q3 H9 _; I6 L9 F
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91) K4 f+ Z, l. L5 l
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。; N# B& a7 h* V% S, y/ q
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。
/ f3 `7 Z$ \ ^中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。' B/ G5 L: A9 e; ~
后面的不用管。
" e5 u6 r- _# ]5 b1 L
|% V T% D8 s, R) [设置完数据后单击编译:
5 P' F" Y+ z8 _( S9 i. E2 f
, ~2 ~ u A; q然后保存编译文件:
( c9 m- c) ^4 e, e: O# W
, Y" v4 n* g4 y选择二进制方式保存:
# n5 l6 G' U: w % O N3 C1 L x

5 n u! w7 Y" ] e( @用Hxd打开保存的二进制文件:2 G# C' `/ I& a
# c- c& H0 n! u# C ?
' j+ M2 J7 _/ u" Z: r/ ?
跳转到设置的程序开始$8100:
) V- o1 u0 {! h% j' e
$ B' Z; s' i9 j 4 y {" J9 Q- [1 g4 r! Q0 g5 c
) ]% y/ f1 u; K6 v
回到FCEUX,转到NES内存的$8100对应的ROM地址:
+ J# a1 r r+ z5 U& Q' |% V 3 U! r. q; M7 M

' J* b' w! i7 R/ S1 L8 M, G) r
; m/ n5 C+ e, u+ K j* m8 B" J) @

7 L' \- }! F' G6 f然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:5 i: q. U# s& Z& z2 t

" a; r/ T1 m' f5 I0 |
+ R8 b$ z% t; D2 e6 B; y2 x, a& q ! j) L3 b& ?" Q( i( J
然后转到NES内存的$8000对应的ROM地址:
7 p2 C* n% G0 Q; s! @8 A 4 n: L! |' k* y2 \7 w

7 a: B: A# G5 A5 O0 u) e* J1 j ' L9 j4 O# N$ z' K* n
e$ e2 [. t A Q) G( D" z: l% [
写上如下程序:
. U! ]; U1 ?* Q: w# i7 I4 F% UA9 80 8D 01 A0 20 00 81 4 l5 x6 p0 r/ G1 W. ^# }
LDA #$809 v4 V9 E, b- a8 F8 {1 X
STA $A001 可写方式开启SRAM
5 f1 g; X) Y a' m) G9 V7 HJSR $8100 跳转到子程序$8100
$ R7 B9 G, R) p7 f2 Z4 {
) v( B2 q& N2 d) P, l3 L, z) a: O然后把Hxd里被覆盖的程序复制过来粘贴在后面:2 ~& y; C5 ]; { c

, a0 |6 q+ W5 u
$ W0 h" E9 Z+ w; E8 R
2 v$ K% K c& I& D$ a% U末尾补上一个0x60:
' p0 M6 o, }- ~9 tRTS 子程序返回
4 G4 r- a$ y) ]$ i
8 r/ G' z8 s% B% r然后单击运行,ROM音乐响起,正常运行:
4 a9 Z" C5 l/ v
% ~$ I' _) P1 M9 w5 Y) M$ D Y' U" i: ~# J" W$ W( u. f5 d
然后转到NES地址$7000:) z; {: ]; t, k$ U' ]5 o/ p5 |9 g

7 u! @' `1 A, ?; p
) l8 H3 Z; I/ c2 _$ o$ C " r' C- N; x; d& X& G3 {' q0 ~& B
9 `7 H T$ Z6 t

) i' S# u7 I6 W% M [可以看到,$7000-7FFF都被复制了一片数据。- L* D5 y8 S% C) J0 C
测试没有问题,然后保存文件:6 B# M6 K; x- ^4 v1 o7 v5 {

. [& N* L B8 V以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。
0 s7 d' D6 d6 ~; G& P# L后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。# D _6 t, D |( _- L$ d
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|