|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 4 N) ^; u& J3 h V/ |
, v% M( W# w& _[FC][SRAM扩容教程(Mapper 4为例)]; Y) i2 N2 s. `2 K. Y, d
2 h$ c% |& D n {: j% z% b, {时间:2017.4.28
$ t7 s7 V# j% k8 N) r' u作者:FlameCyclone! Y. ]8 {5 Z* F8 A- z
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator; C* w+ b& a. |1 |! i2 k# q1 G8 k
ROM:双截龙2(J).nes6 [: b/ ~) N! f# N: Z
适用:没有使用SRAM的ROM
: f o9 j2 I2 G7 p' D# h( f* k' P1 c
首先用Hxd打开ROM:- M; E+ H4 z. ^' }1 G6 H6 @

; `0 p5 C0 X* ]7 P( m# @然后扩容:
7 M" Y( _" B- V4 U! a2 Q( s0 J$ Y0 Q3 [- a
/ y2 ~3 [' R3 L1 u
) g8 q: ?1 m1 h( O) S0 H: b) u, \
$ f9 V: ^% ^! s; U# w2 u3 _
: O5 ~. @/ o3 @4 P! w* b5 i7 s4 @9 Q" ]# O6 h
4 Y* Z' M5 G/ K
4 C7 m4 N' N8 \( V1 U# ?: } ?$ u先看看任天堂产品系统文件对NES文件的说明:) N' G9 ?0 W2 v+ f$ p
NES文件格式& S1 \7 e; B e
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。
" ~' L a$ l% _: S& O: S( w6 r' Q偏移 字节数 内容
4 I9 P5 j3 ~4 i. r: Z0-3 4 字符串“NES^Z”用来识别.NES文件 8 F/ V2 @9 U! p4 i
4 1 16kB ROM的数目
" }! u; q1 }# _& x5 1 8kB VROM的数目
5 K! T: i5 k3 X; i$ O p6 1 D0:1=垂直镜像,0=水平镜像
6 t2 N' X% I: L8 F7 ]# j D1:1=有电池记忆,SRAM地址$6000-$7FFF
) l* O$ X8 u- Z8 M D2:1=在$7000-$71FF有一个512字节的trainer
" [! A1 ~4 `6 W' w D3:1=4屏幕VRAM布局
! ]( M- M# a' X3 ? T D4-D7:ROM Mapper的低4位 , y0 o: k9 N+ ~) X: [( Y
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) + }- I) s' F, z7 H2 ~+ B% c
D4-D7:ROM Mapper的高4位
& X+ W8 V2 u. B$ A- t6 i7 j8-F 8 保留,必须是0 + f1 x+ m; ]3 n
16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前 . ?2 w. ?& }( w Q# _) ?
-EOF 8KxN VROM段, 升序排列
+ ^0 g/ W! U6 z) |3 [0 r; n, V
, T4 c" _$ ~+ ^4 D& p* ?7 s然后知道这个ROM有0x08个PROM和0x10个VROM
t5 e* j8 q$ P, T. `. ~接下来扩展PROM位0x10个:/ O6 B' v! `; e% F4 g$ F: U
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):8 G! Q( g E" ]4 o6 J

2 H* f f6 N0 H* G f由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:) j' e5 `3 z" J
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。" V" n! Z2 \$ i7 U) `: P7 k4 A& Y
于是可以得到双截龙2的是:4 H/ b* C' f! ~3 V( R& {5 B
(8-1)x 0x4000 + 0x10 = 1C010。
- B: ~6 u0 S& s- V3 j然后跳转到1C010:. v0 E3 R4 T3 X# K/ m0 W
( _3 U0 i1 t9 T# @& ]. g
# |4 C' q# }6 t/ `
$ i# I& b d5 S
然后插入0x20000字节的FF:
9 E- o3 h$ R* L. I; l9 l9 c8 A 6 j* O9 t/ y7 z5 ]; R3 J) ~3 n
, P, C# c8 V# v# x$ y

' L4 i6 @/ v) l0 m: F* o6 L" U5 [然后保存:
! u& p- ~: {' [1 { $ ?& Z# \. _, _

6 [, G {- O; w2 Z- R Q a r用FCEUX打开正常运行:
8 ?2 e3 f% x/ f3 \" X% J4 O1 g9 L4 ]! e & G* N7 E" e3 @" T
查看文件信息:
* n& I+ }$ O0 e) g" D& B7 p
& K, r& a& _3 Z/ b6 P N0 R: V( B
/ h( J0 T9 u6 c* e. {接下来切页:
% D2 H; `% {3 y" R! G! q: Z. V/ w; W4 M先打开十六进制编辑器:6 j! O. w+ d ?8 }8 z: M4 e
: p/ T9 s. H" x8 a- X
拉到滑块最后,看看重启中断
! D% _1 g3 P; r2 D' ~中断地址 中断 优先权
! E7 i; I+ C0 [: L' u; n) T* s$FFFA NMI 中 $ ^3 k: j1 K9 |& i
$FFFC RESET 高 / g: A. |7 h/ Q% b% W
$FFFE IRQ/BRK 低
. @ k' \ Z6 C
- Q; b j+ L+ [1 k" m- yRESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
+ c6 z& q& \; t! \! F; W由此可知双截龙2的RESET中断$FF65。
4 g! z- p8 t4 Q. q9 F接下来添加$FF65的执行断点:! C2 K! u4 t" p: U1 B
打开调试器:. ^# c% |" e# E- W d/ U+ H
9 V, x L+ W4 M N5 T- h+ d. q
添加$FF65的执行断点:
0 `' D# U$ ?, J# x " S/ h% O% ^! j& X* H6 I* ^

: S% \+ r8 _: x) |" q0 z: |/ ^& M' q$ A
单击确定:
6 z8 V" c. U8 L& ]8 t0 t0 t5 [; G. v
$ @' B1 n1 u3 o1 v1 t3 X c6 d
/ J+ E! I. x! ~0 J7 j; i然后重启ROM:
) Q; f% i7 w) G8 d ( y$ c! t6 f) Z* k3 l
调试器此时弹出来:! [; l( x5 P! I7 b. y, R6 ^

6 D* O* I) v! k( p' Y! U. ~( I* f; {( {然后打开Hxd,写一段mapper 4的切换bank程序:
S' W. l5 Z2 J2 K8 V* i9 @先看看mapper 4的说明文档:7 k7 B' x. L9 J' B
Mapper 4
8 U* |; p1 x6 P2 |$ \. _
+ ?; ~4 g# d7 m; |3 Z$8000: 模式号
7 n0 J5 s& ~0 m+ Z 位D0-D2:+ ^4 C0 I+ x0 @: S8 v
0:选择2KB的VROM存储体映射到PPU的$00008 L4 Q5 I" [' E3 d4 c- V
1:选择2KB的VROM存储体映射到PPU的$0800
( C' Z9 D1 w$ [4 f 2:选择1KB的VROM存储体映射到PPU的$1000) s2 z1 e+ M% }* {, `7 a' S# T0 d
3:选择1KB的VROM存储体映射到PPU的$1400
( @& F2 P# K; {; f8 L3 \+ E 4:选择1KB的VROM存储体映射到PPU的$1800/ ]+ E6 {! [% B" R- J' O
5:选择1KB的VROM存储体映射到PPU的$1C00
9 [8 I/ J4 ~$ p 6:选择8KB的ROM存储体映射到$8000: {6 i6 W" _+ m0 ]8 N) a: e7 v
7:选择8KB的ROM存储体映射到$A000
( ~- G. z4 K8 N2 r7 m5 l# Y1 o 位D6:& ^+ R/ r4 t0 T
0:允许擦写$8000和$A000- Z z$ E. @+ q$ [% U
1:允许擦写$A000和$C000
# o# O2 B2 [! T$ Z3 u9 ] 位D7:
" ^" Z( j2 D( X) k- K 0:模式号D0-D2使用普通地址
) N* X. W2 a3 h- @% U 1:模式号D0-D2地址异或$1000
# g( O) h& I0 V/ n* a# ]. {
; m& [6 ]. S; m1 U' i5 P0 S$8001: 模式页面号
5 R' c3 M5 m' L% N 写入一个数(00-07),切换存储体到对应地址; w7 \9 q/ r6 ~4 J4 u& ?6 G: ^+ C! E
2 ^$ ?7 z$ H# Q: j, P$A000: 镜像选择7 ]3 `' g0 S0 ]7 s+ Y4 k, t
0:垂直镜像
6 l* B0 I) i4 ^$ Q7 \6 A7 r 1:水平镜像
6 u* c* U1 H$ l, }) h8 f- S; U* W& o; W1 M+ z
$A001: SaveRAM 切换. `, b; A8 j1 p2 A, L' J
0:禁用$6000-$7FFF
8 ?+ w. d, }9 _3 `0 e 1:启用$6000-$7FFF
5 b* Y3 U* i3 N/ V
! k8 n$ _0 T' M8 C0 k8 c6 ~$C000: IRQ计数器9 p+ n3 H; ]4 g ?7 o3 ]
IRQ计数器的值存储在此处& S' V' l- c. G
6 Y$ Z1 G1 `" D! r; ^
$C001: IRQ暂存器0 R! S$ ~, C% r# l9 {9 v3 r1 N
IRQ暂存器的值存储在此处! C! t; f: ]$ e! R/ D p2 p
/ `( i- g% d* X0 k$E000: IRQ控制计数器0
; h& b$ Z8 ^3 G+ D. I4 K0 @ 向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
, o' v" r" j2 A% S+ P8 J7 r
/ u% S5 O3 p7 N3 T* K4 m2 ]6 X$E001: IRQ控制计数器12 X% ?5 Q5 e, [* E3 V
向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)
0 @! |6 B6 |9 w* E4 W5 @% k: j: i( p
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:
+ P6 \/ g! P! N3 l5 I4 @48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68* b9 ~9 {' M7 `$ R+ h
PHA 累加器A入栈
; ^# _4 }# a5 t! ?3 f3 S" mLDA #$06 设置切bank地址为$8000-$9FFF
+ ^$ k; ?; G- e OSTA $8000
1 d( S$ m2 \8 R# aLDA #$0E 将第0x0E号bank切到$8000-$9FFF
5 y/ ]* d4 q1 z2 c( g- mSTA $8001
0 k5 S3 m7 _+ H1 bJSR $8000 跳转到子程序$8000
- z ?! m$ ~# d; b! lPLA 累加器A出栈2 G. l' n: j, A
- a+ P9 j2 @9 ^2 @) C% C为何切换的bank号是0x0E呢?
! D+ `8 T# ]- y因为在扩容ROM后,文件PROM结构为:
+ W" M3 w: |- H1 Z3 Z5 I原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
5 v& F* m8 _$ ?- R, f% R2 |( J
/ g2 s2 M# S' y+ n: B# FMapper 4切bank一次是切8KB,那么文件结构就是:3 ~: S4 E" m3 P; ?4 a4 y6 f5 F
原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)1 g, v4 y; \6 ?" W0 t9 s7 b( Z- E
因此选择扩容的第一个空白bank就是0x0E号bank。
* k& |, }, I* X% w ; u, i5 Z& o8 d. V
; h% {+ `! M4 L8 z& i- z然后去调试器找RESET中断中可以放下切页程序的地方:- n& R; i, P. g' k: J/ E
首先长度要小于等于自己写的切页程序。
* r) A/ D' u" n) F8 L可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
5 z O, b& i* [4 A) o+ ~/ L
: K/ {+ _9 K6 g' p+ `0 Y# h) {然后跳转到$FF7C:
- R& e' ]1 Y. w3 v, Q/ k- m5 U 0 {4 C: o. q. A# I( J( q

) F: ? t3 l* S! y0 U: Y
2 S+ r; Z8 m& k3 m8 l) c! o3 ]3 _# u( K& w$ H H, J, D2 T
复制下可以被替换用于写切页的程序:
: U2 K+ T6 ?% {& f5 Z7 y+ S6 }先选中,再复制:) W# T. T6 E; r. O2 `0 T* k
+ M" i* q0 c, j {

3 [* c1 B$ h' o3 x. W6 ? s在Hxd中新建一个文件,把复制的数据粘贴上去:
( l- _0 r0 F$ F v7 Y5 h- G / Q8 [4 f% I( s; T/ w# U9 W

) f+ |9 \# \( @$ F0 K8 W: @ : U; X: j F5 D0 o7 A$ L: L* b
" ` Y8 z+ M# Y7 t. V& \! Z7 x, H然后回到十六进制模拟器:, w4 t3 w, N! F p* j$ \6 f
转到$FF7C对应的ROM地址:5 B4 b9 Q7 a6 n+ l" M3 b* X

; n2 D# \' Z& b, t. w 6 B j2 @7 \7 C8 s3 h8 ?) h
然后复制Hxd的切页程序,粘贴到这里:' a% f4 M! D# |# i5 l* d

3 _/ h% G1 o, s9 S. F8 ] & ^8 e% I* q, _ Y
" l3 n" R& A2 g3 S" u5 h& h, B
没有覆盖的用EA覆盖:
3 t' U2 t" K) A- G% ~9 R
4 p+ a7 ~+ c* s打开调试器可以看到变化:
& v) T, O {1 n4 V8 u p$ z
H4 ]1 u; N9 j9 r6 \" Q8 W! P然后添加$FF7C执行断点:, F2 @" U$ i, P% i0 w8 A4 f

4 L/ G' J# z- X7 J8 [ J : ]% E# W! F: A. y6 K/ J# U- m

6 W" [ x& A8 ]! l% }1 ^单击运行:2 j1 P. g$ t( X& [8 G. ]( J
然后程序在$FF7C这里停下来了。
; _1 k3 c4 ^' R4 ^ D8 K; z ) ~$ S; z( h% }: }
然后单击单步进入慢慢跟踪,直到跳转到$8000:' T: d2 \& s; k' m6 Q
% A4 D$ b1 m; O" S' Y6 K; [
然后打开6502_Simulator:
- \4 j) V/ ]. c! W. _ ( z) R9 H- }) F5 y; P% X
再打开我写的数据搬移程序:) R" e" A! y. c5 N( U, X8 n

7 j; l8 P5 I X7 a % I, A- b( G# {( U
然后修改对应的数据:3 I# g- X1 A& `; R
程序开始地址:修改比如$8100就修改为 .ORG $8100
$ f( t3 P& S! I8 I- Q0 q; W2 t复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$707 E6 w+ J0 t3 `( }
从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82
6 K' |5 G7 r: i; G想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$913 o7 v) B6 Q# K0 R
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。
4 B9 }2 X" G5 g# n5 Z如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。
4 o$ o( x2 J1 C& D8 Q' J中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。
! O, w+ C' W; v7 a后面的不用管。0 T1 g t/ t6 v: |
/ |; _: _0 E! a' @4 {! n设置完数据后单击编译:! H$ e* u5 j8 \3 x2 {9 }3 \

' s3 K. `# y! _3 D ~ r# G4 s然后保存编译文件:
" Q# t* u9 P/ r, V# ^5 w
) K) o+ H9 M M- l选择二进制方式保存:" N. M' R% ^! z$ o# y
! _& }7 t, t: {% L

! a; l. A4 m, C2 ]4 O' e: T9 I用Hxd打开保存的二进制文件:$ M' ^3 Z$ `* h' ^. m0 b$ E
% {2 p6 ~) a# m8 }9 L

! A/ p% i( n, ?5 J* t跳转到设置的程序开始$8100:
( A! {( Y ?3 {7 U ; g* l, \1 E; |. F

# {9 I# s9 S* R6 m
9 ` y4 y8 F- H# `! l! ]7 _% K回到FCEUX,转到NES内存的$8100对应的ROM地址:6 f& i8 Q* x$ D: @* k9 N! }
3 P/ u7 [5 a( H- v

% p2 k# F4 p# O2 W% r7 [ ; E( i0 n& c& }7 W' P
+ @! E, c J; S2 l: m" P
3 o# E2 V& n2 C4 \( J' D9 Y8 H
然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
- j; l* i* w* G' \6 v/ Y ' a4 z' h$ P9 r5 H3 o- g

/ d2 r& _3 f! s6 U& v ; S+ X0 s) n* H9 F4 b4 E
然后转到NES内存的$8000对应的ROM地址:) @" r9 t6 M8 r8 F
1 h, c9 Q0 k' ^$ u. w) g& u3 a; H
& q. c3 c! c8 |% M

! @- @3 x n# X. A5 \: u
: @9 Z$ K! C: ?5 M写上如下程序:" f6 n$ i5 T/ K( S$ n: t
A9 80 8D 01 A0 20 00 81
9 x% I6 d- }# NLDA #$80
( z [' M( o8 f/ B" {4 k' _: oSTA $A001 可写方式开启SRAM
9 E3 _0 b# D; V" JJSR $8100 跳转到子程序$8100& p8 |" h5 q$ }" [- A7 A

/ |; e6 i; \3 G3 G0 Z然后把Hxd里被覆盖的程序复制过来粘贴在后面:, }, V, L M1 s# d# H2 Z

+ z1 A' p/ R% i1 f: p$ [9 e6 D
! d3 X" E. _" q: L* ]9 T) t 9 a. h# D1 H" M% u5 [( u
末尾补上一个0x60:
" o8 B% l# B A$ PRTS 子程序返回* W! q5 m2 r+ F+ p( }! c0 m) x
2 `& a0 T* Y: G
然后单击运行,ROM音乐响起,正常运行:* a. Q8 g n' Z- D; X0 D
6 ^# J) M( _. K3 B& v- h7 W
8 w4 _+ G1 i! r
然后转到NES地址$7000:" Y5 N( z7 ]/ \2 a. {; k
5 \0 j* J4 a& `# R0 l @! n5 }
, [* r' y, S, ~9 E: F) n) U

/ L/ t1 [1 i9 h
/ w8 a1 ]# \& S. \ p' F. G8 h ! \1 X& q4 O6 _
可以看到,$7000-7FFF都被复制了一片数据。8 ]# F7 Z( D5 R$ s3 z' P7 B# x8 _/ s
测试没有问题,然后保存文件:) i* T) h" W7 C" K/ i. r

: F4 X" p$ M8 x; y) C0 b! E$ N1 s' _以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。
, A; H1 Z P' f: U2 K后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。( W9 e+ L. r# e6 s4 \
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|