|
|
本帖最后由 yandagui 于 2017-4-29 10:04 编辑
! ?# G" ~5 R4 z! b3 o% b. T( P0 ?5 Q8 Y. Q h6 X0 v* I
[FC][SRAM扩容教程(Mapper 4为例)]; }/ ?, ^8 }5 b( N5 e
- M6 j" F) Z) z" b
时间:2017.4.288 _: U3 f) c0 q
作者:FlameCyclone
3 j# g/ t5 g! t8 i工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator8 N1 z& Z7 \" _! l* n6 \
ROM:双截龙2(J).nes3 {0 @" o1 w, ]- ?
适用:没有使用SRAM的ROM7 l5 I8 Y R0 {- Z( r
8 |* b, {0 Q: E. o首先用Hxd打开ROM:
' k8 ]" B' _9 q3 x# U! O- c
) O" u/ d: c/ T然后扩容:/ D7 D4 z0 X( c
& s h' {* I) W( P& P6 G" K! `
1 Y+ Z# v+ |. Q! c' }/ z/ j1 F6 a# F: g2 l# O G
% B! l4 a( G9 p; {7 p N0 X! s) \4 @6 |
% I' c- d' A" p: z
2 ^2 Y* b2 I) f
, G- @! d5 ~3 I* Z% Y! N( _+ f8 P! {
先看看任天堂产品系统文件对NES文件的说明:
) L- h O% b% [6 `' p+ u6 u! S dNES文件格式0 M/ @' j& e7 ]! L0 f5 D; c2 n" a' o
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 5 O" P, |2 Y0 {' q
偏移 字节数 内容
! j' s0 P8 t& |/ o0-3 4 字符串“NES^Z”用来识别.NES文件
/ m8 J( t& P* q( N4 1 16kB ROM的数目
1 h7 R' j! s% q' p3 i: S5 1 8kB VROM的数目 7 K4 s1 I+ Q7 ^& e
6 1 D0:1=垂直镜像,0=水平镜像 3 {. |6 K# e8 O _! W* f1 `7 l
D1:1=有电池记忆,SRAM地址$6000-$7FFF
1 b# L9 o# u) R% t+ i n9 _ D2:1=在$7000-$71FF有一个512字节的trainer # g. {) _' ]4 M. ^# }
D3:1=4屏幕VRAM布局 3 S6 F' Q0 Y0 H: R
D4-D7:ROM Mapper的低4位 5 M% K8 d, {$ @3 ?* O
7 1 D0-D3:保留,必须是0(准备作为副Mapper号^_^) - f( a. \$ c. `0 ^" g1 O# J
D4-D7:ROM Mapper的高4位 ; v$ ^ v. m" Y
8-F 8 保留,必须是0
5 j0 B1 b/ D$ D# D) h& T# D16- 16KxM ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
4 B8 z2 ~0 ^# j4 H-EOF 8KxN VROM段, 升序排列 - X! ^( y7 A4 s+ y) N3 |
& j1 Z/ C, S E( K4 K8 D$ ?$ w然后知道这个ROM有0x08个PROM和0x10个VROM
9 \5 A0 {1 n( P* y2 |7 j# C接下来扩展PROM位0x10个:
* v( l7 b: ~8 U; q0 _' s先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):
) e- T3 G9 ]& p: U: Z! W; U+ f) a( J
/ y, C `3 O# C( K7 J( J由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:( f: ]5 l5 X8 Y
最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。
1 x" h" g9 N+ x/ | `. R' c; ?: J于是可以得到双截龙2的是:
7 L6 v! X2 ~! W/ U(8-1)x 0x4000 + 0x10 = 1C010。
5 M5 y( U ?3 e然后跳转到1C010:) J9 I$ ?! |, k4 B/ o# C

- _- {* U9 a; T: t5 C, F: R
" i: }7 c7 v- Y ' _; J. f6 T' P" t8 L' @
然后插入0x20000字节的FF:
& N$ y! x. a& x/ K ! n/ m% o4 O5 w9 z
5 n" K! h' n% Q/ w
* N2 ?( m; u- U% ?/ k
然后保存:' @! b/ H9 u& \6 I: ~. Q7 L

# f2 _7 \5 B( P9 Q; ~% z
$ |0 c) E2 [: a7 U0 t' ?( p4 P用FCEUX打开正常运行:
) |( g' V. ~0 f' C# ]" Y: x ; G! u# E9 ~; P9 L8 x
查看文件信息:. d4 K: [- [1 { b

! j% P p8 s7 @ . j0 h# o+ ^$ {5 W
接下来切页:
& T5 B. B( p" }先打开十六进制编辑器:
6 S2 }/ v* N% `7 r! R* L, T
2 z d9 t7 A/ S2 n* e拉到滑块最后,看看重启中断* r8 X* o8 U/ a7 R+ O
中断地址 中断 优先权 $ N! @# s1 ?" d( o
$FFFA NMI 中
4 x! v$ F; u1 P& Y$FFFC RESET 高 - f4 @- T( j% _6 i1 U7 B2 w' R
$FFFE IRQ/BRK 低 - o4 E3 k- ]! U" F5 e6 x

& Y7 m: N% k6 M, nRESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
. }" v" Q8 ~! r: L+ s* W- l1 o; Y0 w6 K由此可知双截龙2的RESET中断$FF65。( }5 m) J D7 V2 P- D3 M$ P+ w
接下来添加$FF65的执行断点:
" z( I+ D: F; ]6 Z5 C& A/ O5 t; `打开调试器: _+ e3 i5 g8 \- v

& A" t Z P: n; b3 s T) q) U添加$FF65的执行断点:
! C, U# _! R5 K1 I. W
$ U5 M( s# ]6 m. {( J! M6 e) E
2 c/ y1 w' ~3 |% C, k! F. V Y2 A" c8 j( j
单击确定:
/ h0 u+ _# Y$ F& m- d
; d/ P& N- q! d9 V) @. F
+ |" y9 }7 X2 p5 ]: g然后重启ROM:( E& Z8 G& r8 J5 f, | o

1 k6 i4 d7 S" n, w; ~: q调试器此时弹出来:9 y1 ?, o5 p9 ^3 Y$ p: P1 [
' e+ k6 b* `% j- P! X. {
然后打开Hxd,写一段mapper 4的切换bank程序:
! C' F3 O/ G M! X: E% G先看看mapper 4的说明文档:% f7 \4 B! |! _: |4 E
Mapper 40 ?4 K5 e% H% U: j4 @( Q. l
8 O9 ^3 E5 D7 w' s, b" D- }$8000: 模式号. v. l+ M* j& g) V; E
位D0-D2:
4 z; T% U% {+ l% Z 0:选择2KB的VROM存储体映射到PPU的$0000* R e3 _ ?% Z6 A7 ^6 K. U& [" M
1:选择2KB的VROM存储体映射到PPU的$08009 |0 F) M( B. b) ~9 P1 ~
2:选择1KB的VROM存储体映射到PPU的$1000& @. Z, F) a, `; ^! n4 Q
3:选择1KB的VROM存储体映射到PPU的$1400
9 F! s9 u' g4 w& d! ~6 c8 y5 P 4:选择1KB的VROM存储体映射到PPU的$1800
0 o+ w7 f; n3 {4 O8 k 5:选择1KB的VROM存储体映射到PPU的$1C00
0 U* x4 m( J5 ?. e4 \% Y9 T 6:选择8KB的ROM存储体映射到$80000 f4 l3 w. s7 q, D) [# E
7:选择8KB的ROM存储体映射到$A000, N2 T { Y; Y$ d/ H! C
位D6:
3 `5 G! n' m+ |5 j3 C 0:允许擦写$8000和$A000
: w. S1 s# m8 s# u 1:允许擦写$A000和$C0003 f8 S- A8 V) l& s7 ?; _2 O
位D7:
* @1 O$ I0 F# i# x4 T) ` 0:模式号D0-D2使用普通地址5 O: @7 V5 s7 |% O) O
1:模式号D0-D2地址异或$1000
' W: m7 s+ X! l$ n% Q2 r a4 S( D+ [! i2 K' f/ ] z& ~
$8001: 模式页面号
3 N; E% u2 E1 \5 t4 L, [! H/ u5 y" n 写入一个数(00-07),切换存储体到对应地址
( w0 ~8 M- K) z$ g& R f) g- z4 _& ]# g; F3 l: M
$A000: 镜像选择
. y6 A) G2 z3 a4 R7 y 0:垂直镜像 {, Z" L, E; [& {1 j' U0 e6 A: i; q
1:水平镜像
4 _; w' V; S0 i; P: a2 a# D& {
6 Y. C. C& |3 Z$A001: SaveRAM 切换6 l+ m. I4 Z! K, `
0:禁用$6000-$7FFF! N J6 o+ L1 `& ^
1:启用$6000-$7FFF
5 Z! v7 k8 R, g* P) B! G' R/ {0 |. N$ S! A. l$ Q8 s0 m
$C000: IRQ计数器8 y# ?7 | d' N; x F
IRQ计数器的值存储在此处( @1 J+ E. G8 G4 `! k: i) D
* k. ^. F$ s3 d$ y4 l
$C001: IRQ暂存器% g, ~( j s8 ~' G/ R: @! c
IRQ暂存器的值存储在此处
2 j0 Q! B( B; K
! @6 x D2 `' A# B$E000: IRQ控制计数器0
" n0 R4 r5 h" I 向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
" k8 v* }' K. h; T
; m' A* Q+ l N- C. K$E001: IRQ控制计数器10 K' U* Q- j- A5 h% {
向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)* _" k' d5 T: g7 N- `# `
: l7 `! L3 M! T) K$ X' H, Q
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:
3 c! @9 o) N i& h! G48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68( X' @: L1 `, y) ]7 k& T. n
PHA 累加器A入栈
T* j# d a- a. |LDA #$06 设置切bank地址为$8000-$9FFF* P! R$ P' m" Z) l
STA $8000
0 f9 L" o( k) W# ^- NLDA #$0E 将第0x0E号bank切到$8000-$9FFF
+ d$ b7 L0 K1 x3 ~STA $8001
! m, p- g4 s/ n0 jJSR $8000 跳转到子程序$8000
$ S# S8 N9 h: E* T; j2 \PLA 累加器A出栈
$ v3 v4 D3 E* X% y
: \. s7 @) u2 m! |( b为何切换的bank号是0x0E呢?( h1 R; q0 E+ h2 G* G) I9 d& q
因为在扩容ROM后,文件PROM结构为:
Z4 M/ y1 H: }8 z6 h原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM/ P6 k% A6 @( g6 b4 M- b0 |
, d5 t( U E/ _5 O! W. z
Mapper 4切bank一次是切8KB,那么文件结构就是:
: t* m# n" K9 t* p, w" Q/ n$ ^( ~原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
! h" h" W/ H5 _3 `因此选择扩容的第一个空白bank就是0x0E号bank。( N* n0 @8 B/ ]) @
5 }$ x: s# o$ g* F' c# S# O' |
/ t) i% n; ?. G" Z$ N3 R$ O然后去调试器找RESET中断中可以放下切页程序的地方:
& d( ?, C% T5 |" W; a9 c) T首先长度要小于等于自己写的切页程序。
6 ?" f+ o& ], m5 E" r: }: C可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)
5 H6 i9 G5 m' c# L
* y K( V) S3 _6 j) f9 N然后跳转到$FF7C:
) z! v+ B [" W8 C
& B6 J+ c& a+ n4 x9 f 0 K6 D0 g# C( M; y u

- S" Q; B9 p8 j, b
8 R" x0 ^5 A$ a) g9 z! y9 Y复制下可以被替换用于写切页的程序:
/ d; u# O9 R- `" S先选中,再复制:3 W) ^" d; Q( B3 o4 w* z6 O+ {+ Y

; O8 v$ @; |* n; x1 L m! U5 J 4 ~& `& y5 A2 b( y/ u
在Hxd中新建一个文件,把复制的数据粘贴上去:
3 H& {3 }0 l z: g
* u$ @1 t `4 Z1 g3 W4 ? 6 p' M; s8 i3 U, o, U" t
. v/ R1 x$ g* U9 r; U |# [
. Y( c6 @) s+ ]& }1 Z
然后回到十六进制模拟器:
' M4 T- N) J# n$ [转到$FF7C对应的ROM地址:: i& J0 r& _2 w' c3 D h' w

% v( @' y/ e6 _: I; b' G- _
, r. \/ [! S2 M" F* Y. ^9 o然后复制Hxd的切页程序,粘贴到这里:
4 D/ g$ E' l2 w2 T/ W; z # }6 T- n3 O8 B h; y/ m
8 E( s3 n1 M- Y6 ^/ m' F

$ u# O' C7 h5 J没有覆盖的用EA覆盖:
/ [8 \/ \5 c- G% n5 @$ c
* Q" z, w% ~" m# N; k+ y6 ]4 V" B打开调试器可以看到变化:
1 l# Y E' H- ^( d% F+ k ) T: a$ Q( x5 e' o& c }+ \
然后添加$FF7C执行断点:1 m+ W' S- p0 n5 z1 E/ O

$ V5 b( ~8 b# g7 v5 o 1 a2 t' F5 ?$ f2 J
6 S& H" x( J0 {4 P5 r
单击运行:, R* R( T1 E4 F4 ^
然后程序在$FF7C这里停下来了。
! O. K9 E/ D5 d9 i1 G 5 ^$ \4 Y! ~1 i
然后单击单步进入慢慢跟踪,直到跳转到$8000:7 P& M; L; }5 X

) w# B' k5 E' [( ?( q然后打开6502_Simulator:
* i& l9 Q, b9 Y; J% c' x) Y& Y' ] ! t9 E2 J) B9 O" C: |
再打开我写的数据搬移程序:
2 m9 V7 ]+ F. I
( @6 k0 B! i: w; j2 b
9 H( q4 B5 w6 ? F, Z9 Q然后修改对应的数据:2 R2 b% C: l8 z
程序开始地址:修改比如$8100就修改为 .ORG $8100
' L3 i1 Q2 ` ?' p* C& h% g( h复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70
, Q( W" o- y, C- A% X& {! k% Y1 c从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$823 J) i0 X0 `! o3 E+ W* ?
想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$918 O$ S& ^8 M8 x# m+ I
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。2 \5 a7 Q1 p% i
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。
e7 }& ]2 ]0 [# a中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。
' S1 I$ ~" t% O后面的不用管。$ k: Q/ _$ Z4 {2 E. ^1 N
4 C) b$ E9 P: H/ a# P) [
设置完数据后单击编译:7 y, D8 C2 ^4 O

5 `- t) h R5 Q2 u' y6 r然后保存编译文件:
* f7 ~/ {3 N8 ~9 r J# Z. a1 z * i/ `, ?/ l' z6 s$ H8 C1 m
选择二进制方式保存:: f( X# F* Q. a3 T% L' Z2 i

h- q& h- s( ?! A/ w1 D
- P$ m8 e+ o) r# Q用Hxd打开保存的二进制文件:
0 l/ G3 w8 e; e' Y5 c7 Q7 @# h 5 m) G! _' [$ f* A

! @6 S0 n4 V/ U' T: c: D3 A跳转到设置的程序开始$8100:. _' p6 u, T; ]2 y, {

& O9 a8 _; i! P& x3 c) S/ C; H / u4 B; i/ f) }9 T4 B
( N1 [0 B0 G. t% C
回到FCEUX,转到NES内存的$8100对应的ROM地址:
0 Z1 J& M% G( H( X8 A
" [+ j& C0 p% _; c, H
5 C; e8 L# `+ v- _7 ~- U + s' l L4 Y4 b1 ?$ o' |& |# k
% F3 g+ K' Q" w( c' O- i* j2 Q

" C( X0 S1 [: I. t然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:& N, @* h* P' V! J; [# X

4 x# R& L5 q' y }. L# T" s( c 1 U' Q4 T: Q1 U: ]
( H: Z/ T) L0 {+ d- z
然后转到NES内存的$8000对应的ROM地址:
! b( D& g7 o; d0 p p3 r) A. ?2 u 0 w; i' F# @1 w* s6 k. A

" s7 r5 J0 U! [ , L4 s$ D9 E0 @

- y1 z. k2 ]( T写上如下程序:1 i, E; {) b2 ~4 }- a; k
A9 80 8D 01 A0 20 00 81
- }+ ^2 |. G+ z" |LDA #$80
+ s5 x; m3 b5 H9 m0 H# T3 bSTA $A001 可写方式开启SRAM" Y4 i G$ V$ f. m. ?
JSR $8100 跳转到子程序$8100
; B2 r y& o# x/ W/ t& _( A u9 O5 R, v 0 W8 |$ F6 R6 g( h5 f0 s
然后把Hxd里被覆盖的程序复制过来粘贴在后面: l9 j9 M( J& I( l

, ?+ W' W' t1 _1 g0 I& u3 S& B / s' Y( O7 ?# X* f/ v
- h7 h# ~: p% D+ m* G
末尾补上一个0x60:
: Q7 h3 P7 }4 S$ r, q7 [0 vRTS 子程序返回
7 g7 D, b) F* ]& B9 h . i+ j- @# V7 D" U, [# V
然后单击运行,ROM音乐响起,正常运行:
3 H* A& U* y0 f6 l( t" s9 M c 0 ^" B5 _4 r: _, @& P

! [' k/ l h, Y3 K( ]/ m& ?然后转到NES地址$7000:* B. z1 }$ l9 I8 a) P- D. w) y" E

# P& U# G5 u* @3 ?) F' l4 w
9 m8 V2 r4 H% x / ?* g$ [7 B2 r5 h% P, d) K! U

! Y. o5 |" b+ |, T 6 Y2 r' i) p+ Y( Y' n' }+ N0 Q) q
可以看到,$7000-7FFF都被复制了一片数据。
* W! H' Z2 q( p0 C测试没有问题,然后保存文件:
( G. k7 s0 Z! i7 u' d& U) W) v * O+ s" p# _; T. j2 d+ t
以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。
; b a' l4 m( K, `( {9 e5 I9 h9 p后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。# m, z; I- R3 D& k; A% A8 W+ Q$ g
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|