|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑
! o2 _- m$ P+ z% o% j k ^ T! ?8 s2 }5 J! u9 i5 S6 Q
[FC][SRAM扩容教程(Mapper 4为例)]! _' V) J$ J& V8 H
8 s, Q* `6 J9 T {- @, {
时间:2017.4.28
! @9 [. c$ j T4 n8 W0 S& J作者:FlameCyclone9 F+ w- G# s7 q$ o! x9 A
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator
8 @7 {2 n; R D, V0 LROM:双截龙2(J).nes( L: u" @; ~9 z) H( m7 N- L2 p7 Z+ q
适用:没有使用SRAM的ROM6 i& p! d/ p: l6 F3 y
# I3 Q2 Z3 @/ h; V: l( j* X首先用Hxd打开ROM:
; ^4 K+ [* m& u: X& J! f
# {+ Y* \7 F2 k) v1 z. W8 n' e然后扩容:3 b$ T' B0 e ?- d8 Z. G
6 `6 d" G; w+ o6 }
% O' N+ E6 P8 N; r5 c6 L
# n4 X: c8 l' ^- ?
/ [- n# [ |% a, t X% a
' C2 z$ p k$ u7 _
6 g& N: m8 P! K9 X/ s+ p7 \# g- @/ Q2 x6 A O
7 N2 u! K7 i7 o+ @
6 X5 ?$ N/ D3 T3 L
先看看任天堂产品系统文件对NES文件的说明:/ w/ r# s, a- k" O
NES文件格式
6 W- K9 X7 I; h7 s+ h0 W/ R4 Z.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 . H# R+ T$ B0 @0 G2 Q. s
偏移 字节数 内容
P) {6 L& @: P4 F x0 l: I0-3 4 字符串“NES^Z”用来识别.NES文件
, |$ A; y) V# X$ Z z" M4 1 16kB ROM的数目 8 [6 S: f9 D, @
5 1 8kB VROM的数目 + s) w# a$ q% |* h1 V
6 1 D0:1=垂直镜像,0=水平镜像
4 t w6 [5 M0 u. ]2 s D1:1=有电池记忆,SRAM地址$6000-$7FFF
; i I6 b* o& r3 z7 i D2:1=在$7000-$71FF有一个512字节的trainer 3 [+ N* a4 b' ]; L7 H. p9 U
D3:1=4屏幕VRAM布局 ' G2 K3 ~8 O' F7 t: d, k
D4-D7:ROM Mapper的低4位
6 i; t6 V6 _5 i7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) " a* W8 ]% ~& u( |- n! G
D4-D7:ROM Mapper的高4位
0 C V7 W+ Q3 N& d `2 e) |0 K6 G8-F 8 保留,必须是0 + P) e( t' C% b- Q9 {+ u
16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前 6 }+ q+ z% k$ l: \
-EOF 8KxN VROM段, 升序排列
( {9 C. R- b- H* @& V1 ?
. O9 t; J) H1 r然后知道这个ROM有0x08个PROM和0x10个VROM1 f7 w) P$ R, q( E
接下来扩展PROM位0x10个:& p5 [, w6 I: S( H, [3 k5 y" y6 ?
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
; ` o" `- ?- F+ `/ c! p! q 9 x3 \ ^/ [! g+ h8 Q7 T
由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:; t3 B7 I4 b* I1 S% n) m) c5 l! l
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
6 E* R; s7 ]5 e! k" P( m ?于是可以得到双截龙2的是:( E3 H/ Q4 L# V6 Q4 g
(8-1)x 0x4000 + 0x10 = 1C010。6 u7 _+ a/ C8 Y1 r
然后跳转到1C010:) c3 m3 Y1 ]. p. d
k0 g% V; N. O$ _) t

" [# {9 C, A$ w, \$ X$ H
* v; m1 K/ ^: |5 k4 k然后插入0x20000字节的FF:
( L' L6 i3 e+ y+ d
7 b i1 v3 y0 K 0 t* [! l4 b2 ?9 h7 b
- I# t# O+ Z: e' L
然后保存:
Y1 O* Z' y' y* ?- K
& ~( {- |/ t: u, T: R # r4 p [7 D X6 S7 @( i6 l
用FCEUX打开正常运行:
) G0 n8 q! o: X; H- d
+ Z( D4 Y; T. h! ^' z, n9 x- ^查看文件信息:% T' F! z0 k& \- K7 n6 f9 I

; z1 C+ U5 @, u( E+ c3 g
; v9 O/ |" l& y+ n) o6 r. e) I4 J接下来切页:
6 J8 J5 h; K& l0 f4 B" y {先打开十六进制编辑器:
- R) i0 f) f: r2 |" `- p 7 ~. t# l. D( E j" P9 l
拉到滑块最后,看看重启中断
M$ g. F8 x+ b; L3 D中断地址 中断 优先权
6 A) Y1 R2 V7 O3 G/ A$ ]$FFFA NMI 中
9 ~8 W5 e2 E5 e [$ | t$FFFC RESET 高
) o- H+ F5 G/ v4 d4 N$FFFE IRQ/BRK 低
! S8 G# C% y, b6 B
/ e4 V& h8 @3 `! QRESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。8 q( @* R# k* s3 c5 s+ u6 v
由此可知双截龙2的RESET中断$FF65。
# c! f! n# y d* M) x8 D) Q' l: i" I接下来添加$FF65的执行断点:
( }, V! v3 \5 r) S% ?) h打开调试器:' H2 ]- M) Q. J
' K# }% ^, {# B; v6 i& V
添加$FF65的执行断点:
( v% M' S2 A& q2 v' j/ k # j' C: X/ v/ n. R1 R; a- W( p# P

# E4 K& i+ p2 v- Q8 t) F8 }& c, w3 {
单击确定:+ ^# i/ J1 @' ]% s& P0 _

7 N( S G% L# C' D+ v8 O: q
" z8 e5 d+ O: e然后重启ROM:
9 k) L/ o! b6 [3 h+ U* u1 m
6 z/ H' K9 |3 e7 S" \5 Q+ R调试器此时弹出来:4 I' i" m5 n0 B7 _* z) x: S
; }" a- K5 {2 Q9 y
然后打开Hxd,写一段mapper 4的切换bank程序:- ~+ ?% v3 f' F. x7 b
先看看mapper 4的说明文档: W2 S% P0 \5 K5 Z8 A1 {% p/ N( Z1 q
Mapper 41 D7 `- e ?, k
4 Y# G( y6 s- A$ D$8000: 模式号6 D/ z% u; G* f% _3 `: d) }+ `
位D0-D2:8 _7 f, N7 R3 }' i" Y- Z7 Z
0:选择2KB的VROM存储体映射到PPU的$0000, F5 O- B) S. N6 ]1 A4 Q+ [
1:选择2KB的VROM存储体映射到PPU的$0800
7 G/ G2 Q1 P" T: J- W 2:选择1KB的VROM存储体映射到PPU的$1000
2 W+ e6 k2 D8 [" a" T2 e 3:选择1KB的VROM存储体映射到PPU的$14008 l5 m+ N7 |# L& D4 N9 P! H
4:选择1KB的VROM存储体映射到PPU的$1800
$ t8 r" g; v: q$ n: x1 P8 N& h 5:选择1KB的VROM存储体映射到PPU的$1C00
& q0 h3 p9 O, s V. T6 V 6:选择8KB的ROM存储体映射到$8000
9 ]/ P% e4 {5 @) q0 v8 c 7:选择8KB的ROM存储体映射到$A000
! X1 D5 X( `; i* \ 位D6:
, v ^8 o p$ R( I0 ` 0:允许擦写$8000和$A0007 W1 d' j8 T2 _4 }
1:允许擦写$A000和$C000
8 j+ V6 W4 J9 x 位D7:
( r9 W! l5 c- p& @6 o0 j 0:模式号D0-D2使用普通地址
0 |9 L ~8 V9 O 1:模式号D0-D2地址异或$1000
0 E" i+ p9 B/ u
; X4 G" a6 `: I- Q$ F' Z$8001: 模式页面号% j" z. R; W9 {- p/ y' D
写入一个数(00-07),切换存储体到对应地址+ j. k/ E* L! h0 ^9 I# ], v$ X0 n
. y1 T6 `( J& R4 O9 l: I7 t$A000: 镜像选择$ a# M8 y3 o6 H8 I: l! @' Z
0:垂直镜像
3 ^3 T# k9 K) Q. n J4 l: L; R 1:水平镜像
, N2 j: z; k w9 l, x. H, {1 h+ K4 @
$A001: SaveRAM 切换
. j# O0 R u' z( J: ] Y 0:禁用$6000-$7FFF. A& v3 a4 p, K$ \& n7 {' R# s
1:启用$6000-$7FFF9 u/ \ B R _8 {* p
) z% L9 L. ]" Y1 V8 Q1 ?8 d$C000: IRQ计数器1 M/ ^& q' E3 G; @' \7 w0 l
IRQ计数器的值存储在此处
9 K1 y6 F. A; x2 @( d4 l" x8 l u/ V/ w9 Q/ d. V
$C001: IRQ暂存器
9 D x/ M% J g/ y IRQ暂存器的值存储在此处2 `; e/ z0 l3 |; a. O. I
3 F4 T+ X+ Q4 a5 p. I0 d* o$E000: IRQ控制计数器01 P! x, k, `+ m" h( ~
向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ) Z. z6 X5 ^3 D S
: X: K9 g* Q7 M9 r" T$ }- }$E001: IRQ控制计数器1
, `! H: A" z. t5 B% d T0 R 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)
" J- N) a: B/ S1 O) [% x7 |. B/ j9 Y; ^9 T) ]5 F' h
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:0 d( m- m v$ J1 G/ N/ J
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68$ N9 O4 R [9 A& S% s) E
PHA 累加器A入栈5 v" P- ]5 ?) R. d, ?9 H/ ?8 L
LDA #$06 设置切bank地址为$8000-$9FFF
; v8 Z! P: T9 u# vSTA $8000
6 G9 k H: |. o" ELDA #$0E 将第0x0E号bank切到$8000-$9FFF0 W/ ?) r( `* e1 T" G' W
STA $8001) j. Q8 `# ^& }, o, Z/ o
JSR $8000 跳转到子程序$8000
$ O, E5 C Q jPLA 累加器A出栈/ B5 G% h# c! G& ~4 M3 M
( m' w/ j" c" }* {, Z7 u
为何切换的bank号是0x0E呢?
3 N$ e0 a7 S6 B4 h因为在扩容ROM后,文件PROM结构为:) j. \2 r: O+ X: B0 E# r
原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
) T" E& o3 W' ^$ k
* B4 \3 R' t( F9 O1 V+ sMapper 4切bank一次是切8KB,那么文件结构就是:
0 C" H! D: N! i! ~' k# j原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
* t1 T; x3 V* V+ W" t因此选择扩容的第一个空白bank就是0x0E号bank。" k9 ?" C: H. o9 u$ |, {) k

2 m/ r: H, ?3 X1 N" |' e6 J& X/ c, B2 w% ?
然后去调试器找RESET中断中可以放下切页程序的地方:
1 s3 x! t3 G: R0 O首先长度要小于等于自己写的切页程序。7 Y8 d7 x# e6 R6 T3 T! c+ h
可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
0 m! V) u1 d1 j- D# v
5 Q: Z, \$ |1 m5 h6 u2 i) i然后跳转到$FF7C:) W+ E1 K. v6 f' _+ L

9 |" [1 f; T' j: Z7 N9 O) ] & c- T* ?: A& {" }- p' B+ \* s

* T% Y: k' f6 |! G
! f8 ~- S9 m: m4 I& b$ r2 w复制下可以被替换用于写切页的程序:
3 V& o* v0 p. W5 k+ F先选中,再复制:
$ f, N, d$ ?, O 7 ~2 L2 `6 Y7 k
6 { J# f1 v3 B9 g7 s
在Hxd中新建一个文件,把复制的数据粘贴上去:
$ K" D( I% n2 Y: v) _$ a3 t# l+ Q" f ! U6 u. E' H/ Y
( ?% c ^: y% l# O- m/ E

6 ^* w% U8 Y6 l/ K" J, v- O/ h- s4 Y0 r( X
然后回到十六进制模拟器:. v) q f, I8 ^
转到$FF7C对应的ROM地址:
- w& N. G9 s3 N2 s% n2 E3 l
/ X' e. \, b3 q7 x: B9 \, Q( y ; O3 E ?$ Y3 _/ |( I
然后复制Hxd的切页程序,粘贴到这里:
, Q; B5 {- b) u0 _& v 9 x; T6 v- ~* F" E6 R! I$ _3 k

- M* K: ?3 x% R
! {; I6 }$ B# G没有覆盖的用EA覆盖:
6 p/ r% p$ ]' N; k+ I% f9 [
4 D0 O% I. k/ M" T打开调试器可以看到变化:
# ?% x" c+ w; S7 \5 D 0 i' k3 W" j' a' a, ?; u: k1 t
然后添加$FF7C执行断点:
$ A' L' G% F% C, x
2 m9 z( Y, ^( s \! ^% e ; h( d2 R$ E Q1 F
4 n2 u( m' l& w3 g* b2 @
单击运行:1 C4 o1 z7 A# t/ y; |
然后程序在$FF7C这里停下来了。
' n. Y) I8 h5 M- z
7 m6 N* _7 q, J5 ?. ^然后单击单步进入慢慢跟踪,直到跳转到$8000:
" m! a, J, a$ q5 D8 q3 g! F; T4 r3 F D) Y4 o0 b. q3 O5 m. D
然后打开6502_Simulator:0 s& F0 G8 j& o0 e
( v- L; Z6 V4 J+ p1 [& B1 U) q( c
再打开我写的数据搬移程序:
- D& s, I& R+ S! j& \( {9 J1 s % @4 P3 _, Q9 n5 V7 C/ A) V
+ s. L* p) y/ i# q0 W$ X
然后修改对应的数据:
( x4 w9 w6 T7 D) h9 y- f+ ?9 O程序开始地址:修改比如$8100就修改为 .ORG $8100
2 W) U" w- u ~复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$701 i' L4 O9 J& m0 T( @9 Q N
从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82
1 F: G P0 }8 D& K# V& L想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$918 Y6 D% k4 b8 J
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。" g) W$ t9 Z+ y! V( ?
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。/ Z& ^7 Q. R0 F8 H. X2 Y9 ^- n
中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。0 x6 B1 ~5 z6 `( z* }& c5 P9 Q
后面的不用管。
, ~" b" B& c+ t! Q5 e+ U2 Q% ^ w) \: M) N) d
设置完数据后单击编译:
: [, {6 c% b( W* ~: P
0 u4 m7 ^, E( ]- Q+ T然后保存编译文件:
( _" ?9 W: C6 G) f# v3 e9 i8 k4 F - A" @7 m/ N. e6 @/ i* a: p
选择二进制方式保存:( ~9 O+ C( v5 P% }4 e% X* i

* y, t# O' I4 O2 A& l' _; B
+ X! d2 F6 e6 |" B8 o0 G用Hxd打开保存的二进制文件:
% u* R. f2 {8 m; f$ v+ G7 K E4 [
# z8 d* c+ `6 v7 x1 r0 u A
* ]- ~/ Y% l2 x- J; g跳转到设置的程序开始$8100:1 A6 l% D1 R- K1 u% k2 _: n

" x& C, T6 l8 r' K9 ?3 `
: v1 X( e4 N/ X0 ?
/ T9 y e- P s+ W: @$ ^! Y- T回到FCEUX,转到NES内存的$8100对应的ROM地址:
! q( ?! B% t/ z& H7 ]" b
% N$ w0 c3 @! G( M 6 R- P0 [, A& N

, {$ q& o8 }- L
/ I9 X' d7 V" n" ` T " p3 K& s' U8 V. ]
然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:* o; a7 N2 e! N# u- V

* x( S2 ~* \) b2 W6 v. N$ | ' T1 h6 i: g0 R2 ?* {0 z

. z7 Y. g2 n) M; E: o& {然后转到NES内存的$8000对应的ROM地址:9 K: M( m; Y& b; c- @! b5 h9 w( K! |" T

% {/ A M3 a: Z6 ]* M ( j- m B; E4 w7 V3 B9 a
D+ {- `0 Z8 R+ [

& b, X3 n0 O$ c( J8 ~5 X, r写上如下程序:- [% R- S A3 `" f( f6 B9 d
A9 80 8D 01 A0 20 00 81 2 @2 u7 R: U; T; F; w* U, h
LDA #$80
; M6 U7 H) y% A& M* R/ \STA $A001 可写方式开启SRAM9 I( C) C$ e& r3 s R( z, |
JSR $8100 跳转到子程序$8100
% U \" y8 _# x
: [4 ^( T3 f5 d. c, y2 t然后把Hxd里被覆盖的程序复制过来粘贴在后面:
4 r/ z( W" v$ U" P) m3 N
- N9 t1 k7 x; A: N 0 G- f. z/ ?9 x7 r5 e& R1 F; J
; a& J$ A" ]! o! R4 g* {
末尾补上一个0x60:
1 F: d4 g- g2 \- t7 Z* x' Q- NRTS 子程序返回# G( [& d" [3 n$ U; D5 m

/ S. _: z' D2 w9 Z& l然后单击运行,ROM音乐响起,正常运行:% E ^+ J, i" C/ |

" V9 T% {6 k; L% r( @* s* |7 E ' @9 d4 R" w% E) ?* o8 L. u6 Z
然后转到NES地址$7000:( N' Q @$ n+ m+ Q

! Y; P0 a& D$ f2 h* G7 t 4 o( b: l4 ~) P M2 y0 L: q

1 m. `1 I" g5 d# `0 w8 h
- D6 r/ K6 o2 A
6 U2 W6 w, i. ^9 M" v6 P可以看到,$7000-7FFF都被复制了一片数据。3 e+ T. U5 }$ _; q; q+ R! M4 R
测试没有问题,然后保存文件:9 @# n" O/ o( ^

6 ~& G4 R- R) |8 S% d以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。
; T4 ?! M9 H7 X9 {, v后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。8 d5 G) v& m4 t @- s) {
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|