|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 ) u6 |2 v1 r8 J/ W D
' F9 b3 ^6 q( U1 K N5 B+ t: {
[FC][SRAM扩容教程(Mapper 4为例)]3 i3 f6 ?* b/ E) W* }
) }6 O# n1 z2 L: g
时间:2017.4.28- t0 y, U! h& ]: h; g
作者:FlameCyclone7 h4 Z' q: d0 o( Z) n2 u
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator& u' B9 w# C4 ?% j' `) E1 E4 D
ROM:双截龙2(J).nes
+ T' u: D8 b0 g& h4 O* V适用:没有使用SRAM的ROM1 `+ c" t! ]3 ^8 e4 S
: G4 N5 K2 t: R# }3 d, q+ f
首先用Hxd打开ROM:
: Y8 o! l. H+ @
0 x) I% X% q$ b然后扩容:
9 x2 C# S( `" ~( h+ t
1 M3 A O) [0 G! V2 |1 r/ h) k H) |$ X% X4 r+ G: M
& [0 V( ?3 Q0 H: K- E8 P/ [7 K& X# r! C5 Z0 @
2 S4 C6 I2 I z) j: ^
, e( x3 H$ _8 }3 _
$ ]. ~6 `) o& ^' `
5 L8 Z0 z6 y* b" g3 ^% b, p" f' f( [
先看看任天堂产品系统文件对NES文件的说明:
* C: |7 r3 c2 g; oNES文件格式
0 b" d1 j! ]6 z& v.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。
/ n- f1 [8 U0 n3 R偏移 字节数 内容 0 R# V) w3 C# s* @' a5 D
0-3 4 字符串“NES^Z”用来识别.NES文件 * i5 t' d1 p/ i2 S
4 1 16kB ROM的数目
; l, R+ \( `0 f2 M! D, @( l5 1 8kB VROM的数目
# z8 U5 c+ A4 t1 w! ^: U6 1 D0:1=垂直镜像,0=水平镜像
$ `" k5 A0 G2 V8 V. U9 D' H D1:1=有电池记忆,SRAM地址$6000-$7FFF * q3 ~ [7 _5 n9 x) J, q
D2:1=在$7000-$71FF有一个512字节的trainer
( e" {5 g0 @! Q( R/ ` D3:1=4屏幕VRAM布局
& w& J' _4 j P: |. T$ ]+ N+ h D4-D7:ROM Mapper的低4位 & \2 G! O* T8 k0 ~
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^)
% v8 v( v9 ?. o1 w" D D4-D7:ROM Mapper的高4位 / `' O! j' q9 _9 } t
8-F 8 保留,必须是0 n7 ^: B/ ^' l' i5 N
16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
' h; G. \3 n+ E) f-EOF 8KxN VROM段, 升序排列 4 b* T) g2 p6 ?+ X2 z: A( C
: q' q1 i7 x" A; F5 j' x然后知道这个ROM有0x08个PROM和0x10个VROM- A! J9 i# u5 ?0 t- b
接下来扩展PROM位0x10个:
" D2 d" z! J9 m" H先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
- `" |* S4 q9 I# l F D* J/ | : |! u- W8 q3 e# ]; _
由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:
7 n: |, c! e3 M, r7 k. o9 {最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。" ?& w# m( @ P
于是可以得到双截龙2的是:! Y. l3 |' ]1 u& Q }; I! }
(8-1)x 0x4000 + 0x10 = 1C010。 g( v6 d* ?; J( _5 q# }
然后跳转到1C010:
2 S& d3 x n3 }, q u# ?7 w: _
: u2 C& U5 V/ {1 v- \$ ?$ B2 c$ ~. T
$ ]* ]1 Z5 p# ~7 H. g ' V, k1 ?4 v0 d& e0 O& i1 E
然后插入0x20000字节的FF:* H7 C: d9 A8 d) c( A

1 l# n5 |: Q9 m, t
3 e: ]$ L# `8 B, m: ^7 V
2 L# k- }3 I4 W" f然后保存:
- {. j. U& O/ r6 O. U) T
4 q; _# `& E, {7 G0 `4 P) f% [( h 2 t9 F! A: P2 d% [( S r
用FCEUX打开正常运行:# Z' s7 l/ u# |+ G" t

& L# m: d, w2 Z" G6 u+ g查看文件信息:3 U7 K1 l2 }: ^' S4 Q! o

( d$ s1 B$ e/ r0 Y0 ^
& J1 v. R3 H1 J8 O$ P! u% J8 G接下来切页:
, @5 o5 [, {6 A, m, s先打开十六进制编辑器:
2 V% h3 e5 \0 _( R0 N' @ ; V( S* t+ T$ o% a9 j8 X# N j
拉到滑块最后,看看重启中断
# c$ ]4 z: E7 D# j! |中断地址 中断 优先权
6 z$ n! q1 D* R2 l5 ?$FFFA NMI 中 . A, E" X) t0 P; m& [2 x
$FFFC RESET 高
4 d/ J2 N" m' Y! O$FFFE IRQ/BRK 低 - {3 U6 e* H7 E$ W2 w* }) X8 ^
; z- h a) L# }6 B( k# z- X
RESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。/ q5 j& a Y. n, e# d
由此可知双截龙2的RESET中断$FF65。* s" F( O+ l7 c6 ~" S5 t8 [5 ~/ l
接下来添加$FF65的执行断点:
" x- X' R# s5 o* I; e# j, V: r: @打开调试器:1 u! P9 Y2 w3 l) P$ Z8 d' t

' C: p, e" H) g9 P添加$FF65的执行断点:
& \( o# o% l# t9 `% T5 @$ n. g' g
3 s* |3 S& {: i+ h- K
' S% Y0 g. P1 Q: Y( M" I/ c7 I V; C' M
单击确定:
3 G% q- U# k3 L
+ v/ o- A/ s3 l, m5 _0 s( w
+ K% R0 b4 M0 @! `$ i然后重启ROM:( ^. i& \1 a5 p' i1 Z5 U! B. P

. t9 ~/ j# X8 L) i% ~& ?调试器此时弹出来:
8 g5 S1 @/ B8 _% E" W/ C) f& K
% V3 F' J+ b9 f4 G3 f然后打开Hxd,写一段mapper 4的切换bank程序:
! S J. t; e9 A& f o8 d先看看mapper 4的说明文档:3 f# A/ x- g% ?2 Z4 K8 R" ^' E
Mapper 49 J* u- ]+ Y( y
" a/ M9 ]# _. [; s" E$8000: 模式号
7 ?$ { A- X; |( c 位D0-D2:
: s6 D- w7 X4 M0 [6 ^ 0:选择2KB的VROM存储体映射到PPU的$0000
6 s7 C' u. G5 m 1:选择2KB的VROM存储体映射到PPU的$0800
- p5 o8 R7 I$ L5 C! l' K; I$ H 2:选择1KB的VROM存储体映射到PPU的$1000; i% e F% d( O8 K
3:选择1KB的VROM存储体映射到PPU的$1400) O, P- I$ H, g' U S
4:选择1KB的VROM存储体映射到PPU的$1800# W4 o6 k8 O, C. x0 n
5:选择1KB的VROM存储体映射到PPU的$1C00
E# [! R3 w8 ~% l3 x- Y5 B1 V* v 6:选择8KB的ROM存储体映射到$8000
- o( s5 A: j+ s, Y s/ A9 H: u% @ 7:选择8KB的ROM存储体映射到$A000
8 c7 y+ w0 k: P5 G. h/ [$ O" b6 I, v* \ 位D6:
) a% `" k9 c! K% S, A 0:允许擦写$8000和$A000* E' [) P( L8 e
1:允许擦写$A000和$C000
" k# r* Y, k4 W9 v3 p% @/ m: V" W 位D7:
2 S% @9 c0 y& [5 Y% ]( k9 b; S 0:模式号D0-D2使用普通地址
' p7 x; A( z- f) `9 _# l 1:模式号D0-D2地址异或$1000
4 O" `6 b$ p3 o2 y! N+ `( c. k% O0 d
$8001: 模式页面号
. E8 ^. {5 }* L3 A+ V5 d1 x 写入一个数(00-07),切换存储体到对应地址
" z" U: m2 _. N% t0 D5 l2 N5 ~1 K
& D; J3 Y6 q) F; P9 h$A000: 镜像选择; A, C' Z& \7 M; `
0:垂直镜像. \: O7 H" Y" m6 A( x+ h6 u
1:水平镜像
9 u c3 @& J7 X5 G* |, b" C; P9 d$ n- _7 K
$A001: SaveRAM 切换$ D* P) f( D, j/ N. I
0:禁用$6000-$7FFF
d: h6 C2 |9 I# {3 C) b 1:启用$6000-$7FFF
1 q, l* q* _" ?- w3 _/ }. ^6 U$ K7 Z
$C000: IRQ计数器: R6 x% i+ ?' } l5 o
IRQ计数器的值存储在此处
2 D/ } J; y( e7 y
1 S$ v8 F* V1 U2 Z' D' a- B: p$C001: IRQ暂存器
' a$ D( f. H) ]# }% t3 W IRQ暂存器的值存储在此处( l# M) _% ]' I+ Q
7 E6 v- b/ |, m* ?. W6 F' E$ d0 ^7 {$E000: IRQ控制计数器0
! N _( b0 w! E3 W) v 向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
7 @; t9 p' Y+ o* f" @8 U0 T( c6 T& q/ J+ ?6 i4 o
$E001: IRQ控制计数器1: A+ [( z+ I# E2 z* b, h
向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)
" L' @; W4 z8 M6 G3 }/ U; |% \; d4 |% u( ~6 N
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:
: X9 f/ Y! f5 e* D* R* ^48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68
8 _ v2 T+ I( ?7 T2 G& APHA 累加器A入栈
4 y$ Q! Z( o* a9 a- |4 q1 ` Q, DLDA #$06 设置切bank地址为$8000-$9FFF
5 v- c6 e9 d4 b: f% e9 zSTA $8000
) J' r- N. M# e" iLDA #$0E 将第0x0E号bank切到$8000-$9FFF$ I$ q; f3 s/ m6 x0 A
STA $80017 F' g! E# m4 J2 `9 v
JSR $8000 跳转到子程序$8000- f2 J+ S0 ?1 _8 s5 n" x* l: A
PLA 累加器A出栈
4 r! g" n& r) T& L' M; j2 Y1 P( |! I, h; T L
为何切换的bank号是0x0E呢?
8 Z2 z" y1 }. o# l1 H+ }9 l因为在扩容ROM后,文件PROM结构为:, i! I; m$ v- T$ y4 b
原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
& J) o3 v2 N6 \% y( @" T% }7 C9 G
V4 Q! U7 P9 i, M1 fMapper 4切bank一次是切8KB,那么文件结构就是:1 ?2 r, `+ X! d+ K
原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)& I& f* \# w' O. b% M3 M" z4 _
因此选择扩容的第一个空白bank就是0x0E号bank。
) k/ O) N& @# V, C 9 p+ a0 F2 `8 ^2 V j; x
( R7 M) v! ~: F; i8 C4 h) v* _然后去调试器找RESET中断中可以放下切页程序的地方: C; i1 i( Q! b8 X) j
首先长度要小于等于自己写的切页程序。
7 R0 W2 x' X E" W- D可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)& v+ }7 K% v" L; {
: [# U/ ^8 j q4 U
然后跳转到$FF7C:
! V0 _+ K6 w* U
# k, {6 m4 G0 o+ B; \7 A; L - W8 F! [2 x0 m. A) d2 _
) L" U4 m) M3 R& X
1 {- ^: S: U0 K$ f! T
复制下可以被替换用于写切页的程序:
9 _8 u3 T' M: r" p4 F i先选中,再复制:
. H4 C9 }% @2 A; V0 r$ Z 3 B- ^: k3 ?2 O: K1 G- k: e
; _' A2 ?( R3 R; ^2 |
在Hxd中新建一个文件,把复制的数据粘贴上去:- M& P, e# Q% G& m, N* s( o/ A5 W+ ~
4 }# T1 w4 y7 H! `
6 M8 C, H5 c% ^; K J. u
( q' O' N2 w3 l' T) s7 r. @
; N. E/ d. V8 F# y! _/ [# W
然后回到十六进制模拟器:) m$ ^( y& A, X7 L
转到$FF7C对应的ROM地址:: `- z- c3 h! @8 R$ u

. z7 \7 L% U, ? / n5 g! P+ t! Y4 B( m; n% q6 f
然后复制Hxd的切页程序,粘贴到这里:
# q3 u( V4 V: x; I$ x 8 t4 Y0 {6 h# g2 e8 O8 w
- D3 O# C8 {9 M6 V% x3 [
( c& O9 ^( W- q) e
没有覆盖的用EA覆盖:8 M) G$ i# Q0 |- s& z

F. b+ }4 m5 c# G打开调试器可以看到变化:% Z, E0 I, o2 z6 D* y" {9 \/ Q: o
; b) p/ N% p* |
然后添加$FF7C执行断点:
2 U, ^2 s4 l* e$ O& S
# n5 j6 d! B, v5 R , z, g, C+ _3 S% G7 X. i$ {
, @' w) @$ z" y0 r
单击运行:' i, D/ Y. d0 R
然后程序在$FF7C这里停下来了。
0 N8 _7 o1 O4 g
. k' s' o: I4 T6 K然后单击单步进入慢慢跟踪,直到跳转到$8000:$ V, Z$ y: N3 }& R$ u4 T$ l& X9 U
/ `' w- p, W0 j
然后打开6502_Simulator:) z' }; B; k5 H& g4 ?

6 j( ~7 V$ E4 I再打开我写的数据搬移程序:
0 Y& g& O& b0 j2 d0 o( P
2 F& `* C0 O& X5 V3 T/ b% \ : }4 S- m$ n- s* C8 o: Z2 M+ f
然后修改对应的数据:% {+ Q, r/ v5 }( ?; i* X8 b# y5 `. W
程序开始地址:修改比如$8100就修改为 .ORG $8100
% Z0 }$ k, q/ \复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
^1 n9 Q1 `% t- l5 ?4 p9 l# J从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82
2 Z6 {9 X$ v; N3 _. K4 W, j想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91
% |* ?5 g ]( J) ?也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。 @% ?, |8 s8 h
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。
0 ~6 k! E& H$ T/ `3 f( n- g6 w中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。
1 U- j5 w y8 g后面的不用管。
" ?3 [: O z5 h8 N8 [! n: S: a! N2 D4 B+ z+ a n
设置完数据后单击编译:# V" a5 O; ^4 U# R& e8 a7 \

) ` J \" B; `- w3 ~5 G1 `然后保存编译文件:3 l1 n. q) \+ I" [
; _2 _7 d8 r9 p% s; p0 }, V
选择二进制方式保存:
) _5 m& G, s8 Y1 T2 }: V
/ `) Y2 [$ B2 |: p1 l 4 Y3 g5 q# n% V. Y& @2 ?! F
用Hxd打开保存的二进制文件:
/ ~6 b! b. ~6 x# ` , w. o9 `0 Z- v0 r$ l

% H1 _: m$ L/ n+ A跳转到设置的程序开始$8100:
6 ^! z! k" s9 S6 b. W 3 a: v" H2 T7 M- }% X x/ f/ v

. {, y! V; j0 ~# l& {9 C
$ X! w0 H9 e. t# i$ Z回到FCEUX,转到NES内存的$8100对应的ROM地址:& I) a- U% t4 ~7 N! R6 S

1 C3 z# R& T, b6 K
+ |. J9 ?0 v5 I* O. i: B8 B# f
; e* k4 e4 ?7 C" Y* ~7 O- k" ^ ! _$ |- \: M% o5 H& @2 {

9 H: I6 t" W7 H然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
6 V0 @4 \9 R8 B' C' k: P 7 v0 q7 K* g% F8 M1 W4 _

5 ]3 e k# t$ H* l8 h2 F
6 M9 [) d7 K( v" P C0 j然后转到NES内存的$8000对应的ROM地址:/ p; v d% x6 Y) y* K

4 Y$ `# N! k3 Q7 E5 @
6 \+ ^9 E- W2 D' `& T3 d7 j ? 9 O4 e- U# r9 X j, T. d3 y" E
* K: K7 k# E4 B& X4 s
写上如下程序:
2 @$ f! Y# v. O0 m8 IA9 80 8D 01 A0 20 00 81
9 @* g- r; e) `3 gLDA #$809 w: s- _) F& t
STA $A001 可写方式开启SRAM/ H) v1 a3 i0 u( s
JSR $8100 跳转到子程序$8100
6 a% n9 t. p* {. m1 A: y
$ M: I# u& T5 U然后把Hxd里被覆盖的程序复制过来粘贴在后面:( V/ X% ~; O V5 T5 K

7 Q8 j) e) t$ Y, T+ R
8 }7 A1 F' ]: b 6 N, a. f# _8 L$ j1 x q
末尾补上一个0x60:: q1 r. L3 ^' \) ~/ V
RTS 子程序返回
* m z6 g, W1 {) ~ U& G) p- w 5 A" r" j9 @; T n7 h$ o
然后单击运行,ROM音乐响起,正常运行:" ^# ?$ r9 D: q6 g3 ~# c
/ \! \4 ~' h7 c9 @% i2 `

$ U: S9 o2 T! ?. {/ J4 w然后转到NES地址$7000:
5 _4 D0 U# P) K
' }$ f4 e/ N8 d5 i) `4 q 0 P! m" k2 F4 D( W; B
- A# ~( k \- @( e! \- V
! A9 c( Z" ?0 H. J7 J; _6 V7 R

$ P0 c2 B) S6 H/ U! S7 m6 _可以看到,$7000-7FFF都被复制了一片数据。/ z) ^( C: r6 e3 `- V& W
测试没有问题,然后保存文件:% l( E# V% ^( S

; |" x0 }& X" z/ m8 R2 R以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。& c. |) F: S( W7 @; `" Y; U
后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。
]) Z0 ^$ O$ z0 \3 K# m7 O |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|