|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 0 v* P4 I/ G$ n) g4 P
3 G: C4 B7 A9 j" U% ^" ~[FC][SRAM扩容教程(Mapper 4为例)]
) b7 H. X- H s5 B+ J( O
' R$ ]( f; O+ t: m [0 a时间:2017.4.28
$ S# G. k# P, O# t9 L3 j作者:FlameCyclone0 [( J- F/ [/ v1 a5 R' ]5 }3 K
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator# D1 T' t9 X" B3 v9 B( t4 k
ROM:双截龙2(J).nes/ N- \; n) t2 y7 K( m4 F# h' \
适用:没有使用SRAM的ROM; ~' S) K( p, O$ F, \5 ~ S3 s
5 D3 i( M: u. W- |首先用Hxd打开ROM:
, A+ q; H' ?* g! X% Q/ ~# U# V
3 p- T1 h' [1 |; T" W3 t然后扩容:6 f. B0 m [6 I2 n
" w5 ^2 D8 H1 _1 H$ `2 o: f L# m/ X6 f9 A
( O3 s$ X1 k9 D, N3 k
9 r: p* M% g- O9 T
! y3 _# _* {/ U, P5 e, z7 H
! n- P5 n% p- V( p) r t6 S4 @( a& U) }5 ~" X+ Z% R
. ^- F) W( m( Y G
: L- m2 o# M# q先看看任天堂产品系统文件对NES文件的说明:, g1 {. | j0 D7 I; K, }
NES文件格式2 O- t+ W+ i# |' z! @
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 ; s& _4 h) |$ u. S
偏移 字节数 内容 * G# Z, E0 ?( L! C8 c0 p- r
0-3 4 字符串“NES^Z”用来识别.NES文件
8 g, ?8 {+ E M5 ~4 1 16kB ROM的数目
3 [* W+ a! S; [- @/ c$ t5 1 8kB VROM的数目
0 t, q. N! o( o# U6 1 D0:1=垂直镜像,0=水平镜像 , ?. \/ R6 a, M$ f6 c
D1:1=有电池记忆,SRAM地址$6000-$7FFF 7 L }; G2 _+ p) G
D2:1=在$7000-$71FF有一个512字节的trainer - v: ^3 r K' \" K& F& @
D3:1=4屏幕VRAM布局 0 {. U, U R$ }! ~ v. u t4 ^
D4-D7:ROM Mapper的低4位
* M9 U# P- _) g# B. n/ S' ^: ]7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) % Z. C2 Q ~, R/ r8 x. o8 _& F
D4-D7:ROM Mapper的高4位 H. e& Y- B* Q9 g; l
8-F 8 保留,必须是0
% g* b: V3 D. a, p0 m16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
2 ]/ Q4 F$ R, D0 s( `0 T& S-EOF 8KxN VROM段, 升序排列 % e* r$ I, [1 W& ]5 H. O
5 E) H L6 r/ g# R& c
然后知道这个ROM有0x08个PROM和0x10个VROM7 o9 P$ e; v$ |! Z
接下来扩展PROM位0x10个:4 j( L6 A$ g1 U& k: g: F
先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
V4 p( l" Q. ]1 G1 t- u
" n" z! _# A; K! m8 B; R4 G- K. M由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:
7 A$ }- t$ z, h最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
2 v- f+ p/ u+ P! ]于是可以得到双截龙2的是:
3 L9 @" B* A5 M' Y+ w(8-1)x 0x4000 + 0x10 = 1C010。7 a& p# j: W7 y9 J9 y# i: ?: |% r
然后跳转到1C010:# N" p& S7 ^7 F) b7 X' x

9 p' E/ M2 g9 {0 c3 x
% R+ w- m; \4 {* q2 t4 @% c0 D 3 ~* c) Y# K$ ^! {
然后插入0x20000字节的FF:) Z+ {7 d: t7 ~9 m( b' Y1 f9 E

( [3 j% v& f+ i. K0 C& W - P$ `5 C8 u) h* `4 H* G

- N2 Q- D# y- S- f# m& }然后保存:
. _* J7 S" Q4 z+ p
3 v7 m) C2 e; C: r0 n 9 |9 N& @3 w P: S- H: u
用FCEUX打开正常运行:( x; w1 w" s% \% D, e
7 d; Q4 A$ r% ]: a$ J
查看文件信息:
( C2 t# f, {$ V; O6 @ Q' u1 {: {/ S( n

, P0 |0 z" O6 S1 F) a+ U4 L" ]接下来切页:
- r+ X1 k- K7 X2 Y$ F" ]. U7 @先打开十六进制编辑器:
6 ~. v# I+ ?7 M7 l3 D , G7 j3 ]/ O: W5 I1 B4 u ~
拉到滑块最后,看看重启中断
" t3 U3 _. f7 Z O" W中断地址 中断 优先权
# d) K6 a, }4 V$FFFA NMI 中 % b+ Z6 R. |0 \
$FFFC RESET 高 + F j/ q7 l) p. M c
$FFFE IRQ/BRK 低
! }- z7 o$ B6 P; Z
: h* C5 I7 G* b M5 nRESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
0 h M/ U. u R' u由此可知双截龙2的RESET中断$FF65。& e4 x& t) g) |3 Z. ^
接下来添加$FF65的执行断点:2 E2 b R; V! Q6 `
打开调试器:7 u0 n5 N8 m _% E; E

# B" O% A9 m' \7 H& {: O' y添加$FF65的执行断点:
- J2 y9 N0 W. @; l' E% E; w; f; ^
; Q: y8 l% Q8 V* [% C9 G4 I
$ N; k+ Z7 i8 I; a1 W# T( z2 D/ ?1 B5 M3 |& }, \5 e. m
单击确定:
. w4 u2 V' A% I8 X/ }( K/ d
% a" R0 X9 `( A6 t. O$ [/ o! s& F4 `5 I
然后重启ROM:0 ] T) q& ]' E, V6 g T8 F

2 A7 E0 I* r( X4 ^调试器此时弹出来:8 X# E4 w, r+ O
) \' q8 t" O3 N5 {' `( d
然后打开Hxd,写一段mapper 4的切换bank程序:% x q3 N1 ?5 m% |
先看看mapper 4的说明文档:+ V& F/ x. t" D/ n
Mapper 4" H9 [, y# o8 V$ k
: a" X2 |2 z" B L8 a3 ^7 R! z+ J
$8000: 模式号3 k1 r! \: R/ N2 K
位D0-D2:% D }4 O& }2 I" z2 \
0:选择2KB的VROM存储体映射到PPU的$0000! [' g+ h5 N, D: v4 U
1:选择2KB的VROM存储体映射到PPU的$0800$ a `$ t5 B* c' ]1 Z6 F; j
2:选择1KB的VROM存储体映射到PPU的$1000
/ G5 i. K$ @! @9 m 3:选择1KB的VROM存储体映射到PPU的$1400' j- p4 n0 q7 l5 N8 z7 ^9 G
4:选择1KB的VROM存储体映射到PPU的$1800
- T: O; m1 h, X* B# A4 _ 5:选择1KB的VROM存储体映射到PPU的$1C005 \/ g1 Z% }. t9 H2 h+ z: H7 D
6:选择8KB的ROM存储体映射到$8000
# Z+ G) [* G9 a7 E A9 z9 E" o( E. u 7:选择8KB的ROM存储体映射到$A000+ m4 u5 I8 `( K6 A/ x1 r
位D6:, g4 R( W8 M! c0 |# g/ ~! p
0:允许擦写$8000和$A000
# }% |/ ]8 r0 B& c 1:允许擦写$A000和$C000
# ~ @# c7 e6 g6 s 位D7:
' ^& }8 m! Q5 x( C6 m9 n5 ? 0:模式号D0-D2使用普通地址: \9 r6 H% C8 i3 h
1:模式号D0-D2地址异或$1000/ r- G0 q# ?( a3 r0 T
2 O5 i, E: P1 f: |
$8001: 模式页面号
: D7 y7 Y* I; Y* ] 写入一个数(00-07),切换存储体到对应地址; `2 m4 ~) b+ `! o5 W2 M/ ]
. ]7 ^# N, u% ^2 X
$A000: 镜像选择
) N9 \! K. G% M* b- b. n 0:垂直镜像
/ ~0 W2 n( [- A- Q6 S6 I 1:水平镜像
3 X' j3 q: j; x) n3 ]! S: j/ m
$A001: SaveRAM 切换, l4 n5 X+ ~' M
0:禁用$6000-$7FFF y$ `: S2 T- }2 R
1:启用$6000-$7FFF C0 r2 K9 `, ~: N. A2 F, ~
. m& w; g2 u, a) x; j* B$C000: IRQ计数器3 y$ l5 {$ ] W' S0 M" J! ?
IRQ计数器的值存储在此处
& z5 I3 X5 w+ ^( {# P7 z& p0 @% ]* C5 |7 r! ]8 N) N
$C001: IRQ暂存器3 T* Q: l; F, H
IRQ暂存器的值存储在此处% e2 R2 N) Y4 t, x# W' S! _1 t
; x, x1 u4 x6 F7 R. [% O$E000: IRQ控制计数器0: X) {, c. Q0 _/ V0 R3 R
向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
' F9 }, d/ a3 N: k* r
: Q9 {/ ~* _4 C! y, ]$E001: IRQ控制计数器1
+ I; r% U0 F$ N' o 向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)
' H6 m9 X8 R! }8 t, E) K6 D
5 h0 H$ R) u( J, z7 _3 K那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:! R3 i" f6 E% }! o+ m9 ?2 r
48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 684 L- N6 B; ?0 G9 A- D
PHA 累加器A入栈, U, Q/ F) G1 L* y) t
LDA #$06 设置切bank地址为$8000-$9FFF( j& }! h" e! q( p7 U0 \' ~
STA $8000
2 X! f/ f. B! _3 n, H Y+ S* cLDA #$0E 将第0x0E号bank切到$8000-$9FFF u# z1 Z8 }& y+ G
STA $8001
# S9 C( u/ ^. x7 eJSR $8000 跳转到子程序$8000# C1 r) ?& R2 c1 W# q' _
PLA 累加器A出栈
9 H/ ?; H. X% X0 S' j1 E, Y6 ]2 D9 v* r L
为何切换的bank号是0x0E呢?
- K* F6 C4 ?! F9 w1 d0 ~1 U7 y5 u因为在扩容ROM后,文件PROM结构为:* R2 o0 R- J/ }3 E w z7 K0 ]; m
原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
3 l U0 b+ W. R& F" J5 ^! I% T; A/ j9 t1 t: r
Mapper 4切bank一次是切8KB,那么文件结构就是:
5 d7 I7 D9 z S3 P& f& X4 {原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)- Q- d' v- ?8 E8 o( \8 t
因此选择扩容的第一个空白bank就是0x0E号bank。
) Y) W9 f" B" \7 w/ A. i $ ~0 z, {6 `2 c/ p
" l' V- q: N, F) R
然后去调试器找RESET中断中可以放下切页程序的地方:
3 K( p' H& @* M& J1 L+ w首先长度要小于等于自己写的切页程序。) p f2 [ r2 b' b
可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
7 c! \8 s7 F! \' Y* |/ O$ ^ $ z7 N' i0 S' z( Y+ S
然后跳转到$FF7C:( m1 D8 A& w P7 s! U
4 B+ |4 H, y3 y& m! @

" a1 t7 [* V7 k" w. [0 Z
3 `4 O( P* R2 T& ~9 e1 w
( |9 f) x! C3 _0 c复制下可以被替换用于写切页的程序:5 d' ]7 O. |; i$ S+ n: b4 d
先选中,再复制:: `% D) q: `$ i0 M( |! M0 s

' H \! H& |9 Y( p7 j1 U ! W- H0 m. o1 L+ e/ m. |
在Hxd中新建一个文件,把复制的数据粘贴上去:- I/ H* x, t8 i2 w2 F+ _

# T( |( b% K/ P8 l6 C 6 s" c, q* o) P9 ]# r& D& @* v
& J$ { f9 Z# h2 v+ e& E2 k
- O& b# ~5 T6 o' {7 A然后回到十六进制模拟器:
. [2 W" G+ M6 z6 |8 s转到$FF7C对应的ROM地址:
, ?: V+ h6 f: A j4 n. L6 ?' `9 c) r0 I

4 T0 v2 M+ Y" P- C9 x$ n# K, O- }然后复制Hxd的切页程序,粘贴到这里:
+ V- c# M r: f" }2 Z
8 o+ b, }! q/ ]; d; n6 J0 Z: D. G . q$ U+ b, @: V

K$ I" w }; g0 ?9 A4 c y没有覆盖的用EA覆盖:& _% V8 A8 c7 A% ^, g
# ?, F9 K3 V4 h* g4 V
打开调试器可以看到变化:; r+ D2 K* K5 p, K) p$ f

- C Z0 n i8 y5 y然后添加$FF7C执行断点:- @# O; Y8 u: z2 n4 ]7 Q9 \( q* U
4 r& h8 @( H) w8 Z: O

( ?; T, a. b) t8 j
0 ` J% {- P) N8 F b0 l单击运行:
" r, R/ K1 L* ` ?然后程序在$FF7C这里停下来了。4 L- b5 Z# ?' D
; ~0 E7 Z8 E/ @; _3 D2 Y! m
然后单击单步进入慢慢跟踪,直到跳转到$8000:
( ]3 e N8 m# d4 t0 Y) W' ]
3 l# a7 n; R9 z然后打开6502_Simulator:
; ?% C1 D+ D; e/ R% R9 Q" \, {1 m
' Z4 w+ g: P) q- J. o再打开我写的数据搬移程序:1 Q5 X0 Y7 G C6 Y8 L N5 ~8 d

1 O, ?7 K$ e+ V2 V3 a- O9 r) e2 d4 T
& M$ L* A! T. S3 Z然后修改对应的数据:0 ?3 O |4 [, X8 _
程序开始地址:修改比如$8100就修改为 .ORG $8100
- \( P. B0 \& G复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$708 K* L6 D( }) a5 U
从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$823 b# V) C c, A! |* X1 S" j6 t) X
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$91; X* [# Y1 s$ C/ }
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。5 B( Y! m) |/ x7 Y' k# f
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。
' E& i6 ~/ g6 w) E I2 J5 Q+ P" D中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。! H; z2 Z0 E9 v& Q
后面的不用管。
: N6 d2 [; R! d5 Z8 ~
! _$ E' b0 w( {( i( l( r0 E1 H设置完数据后单击编译:
5 }& c3 s( Y; H5 v' i8 B, ~
9 Z4 ~% o, Q/ _然后保存编译文件:9 G+ @2 |3 \4 W( K7 O

6 O' G6 d% ]. K; m) u: s选择二进制方式保存:6 z' ?( T6 o2 Z- L+ y" Y) I* B U. c

) a' s1 {. ~5 b ; r, h* K5 y, z/ G4 f3 F% y8 z
用Hxd打开保存的二进制文件:
) S6 r% {6 ]5 d ( Z1 j! m( d1 ^1 B+ k# i, k- Y; U

; W4 O# N( Q+ G6 C; m. b跳转到设置的程序开始$8100:, u" |- l) ~9 ^- s0 H$ ~, [1 c

( _' z1 S8 |! `, U
. A* E& A6 Z. y0 k/ @. h$ V 7 n# X9 a' z! z- E' M1 M+ ?6 n) {6 i
回到FCEUX,转到NES内存的$8100对应的ROM地址:
9 j' N4 `7 p$ G H# v K4 B : M" S/ a4 J% [4 q2 K8 m
; [" F* J& f( B6 c
' P; {3 `2 k) @9 m" @

0 F6 a. p! u5 A& _
E- D' t. L5 e& f, `0 Q- n, s然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:4 \/ Z+ d3 \/ L

) g) N! L0 B) |$ @; ~- ^: U* o
3 \2 D$ `0 ]& H* D" v; a 0 K% Q, @& f- b# L1 y
然后转到NES内存的$8000对应的ROM地址:: q) X4 m& j3 z9 @% f4 Y

3 G8 w1 ^0 d F) I3 P . U7 O. r. h- ]+ j2 a9 Y, b

% G2 K# d; D! g( e 9 p; K. N: t" W, P' W4 g
写上如下程序:
, @7 Z- u+ S* E2 n% M# @( iA9 80 8D 01 A0 20 00 81
. ?! C' d3 _" L& E# B6 HLDA #$809 J+ e+ W7 {+ G2 |! i' U+ b: i
STA $A001 可写方式开启SRAM
; J7 }9 [: X2 iJSR $8100 跳转到子程序$8100+ {; O# H5 Q5 H% V

% w! ]# }, \3 H# s; R然后把Hxd里被覆盖的程序复制过来粘贴在后面:' c3 N1 [: z9 B( |# ]7 \# w6 x
. n' S, ?4 y: m, W; t' i
% L( ^% L9 {/ R& k1 u- U

" @2 c! _& D4 {# n/ Z末尾补上一个0x60:7 I4 \& ]! Q1 [' N) f: Z5 G' t
RTS 子程序返回. q( j' w0 F8 D

! ?! c( w/ N0 }& _% k0 s然后单击运行,ROM音乐响起,正常运行:0 K+ ~( ]6 B0 S" R: s
. F, N4 \4 o2 F# o' a- Y* V
: l. ?' V5 K6 j v* K& |
然后转到NES地址$7000:
- H! j0 J& [. d * A- V1 t$ }$ f; m! }$ C. U

0 E! A! k4 a j' F
3 f" a/ R* P0 h $ k; R9 k6 g% b) M

0 Y( ^' M! W, K7 z& o9 n可以看到,$7000-7FFF都被复制了一片数据。
R! A' s1 n! }" N% i5 ]测试没有问题,然后保存文件:9 J1 Z9 T7 f; ^5 I- o
* y! a8 e# v6 G2 Y
以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。; N* K1 E% [* d3 F& v5 T6 Z
后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。) K+ \( T W: W% L% D% {4 \7 v
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|