EMU618社区

 找回密码
 立即注册
搜索
查看: 1114|回复: 8

[原创HACK教程] [FC][SRAM扩容教程(Mapper 4为例)]

[复制链接]

签到天数: 39 天

[LV.5]常住居民I

发表于 2017-4-29 00:16:31 | 显示全部楼层 |阅读模式
本帖最后由 yandagui 于 2017-4-29 10:04 编辑 ' a9 y3 n( L/ o. l( X  M
8 d1 m( h: D+ G6 d& P6 l* S
[FC][SRAM扩容教程(Mapper 4为例)]
7 Q- a# v: I( }, C* w& [  l! M  E3 a" T$ ^9 d
时间:2017.4.28
& a6 n' Q' k" f8 C( ^" d( R作者:FlameCyclone- ~' Q* c: R9 z3 ]# |* m1 z
工具:FCEUX 2.2.3,Hxd 1.7.7.0,6502_Simulator5 n* r# d; P" K% Z! ]* F( Z
ROM:双截龙2(J).nes
6 @- ~1 s% X: d& O2 b& o# @适用:没有使用SRAM的ROM5 T& j, H+ b, S7 Q! k

$ f$ ]1 I) R+ _" B首先用Hxd打开ROM:
1 C" s! ^$ D" G( S6 a9 r5 z
- @0 }3 T7 m: P2 r然后扩容:, e; K; w1 @1 I2 P
, _4 ^9 ]2 E3 h& I( z* Z+ {$ E
* d1 v, V8 e$ z! A3 G1 s$ ]

0 E$ ?0 S" t$ m( z& k- ?6 S+ J  {( e
( e  B) ?' ]$ i4 {* P8 L& F
9 h9 D2 N0 ^5 m& f! s& d

! @5 I- t5 G$ X/ f' _& R, y: V: ^8 o- s9 A5 H4 M( U
& m8 v- ~1 D6 F
先看看任天堂产品系统文件对NES文件的说明:2 i7 q& j5 m3 \6 o  d6 \+ \# ^- D- v
NES文件格式& R; }- }! [+ R: P; v' v6 J( ]0 I
.NES文件为模拟用来储存NES卡带的映像。下面是一个.NES文件的结构。 4 j! N( C8 X0 }% T3 D& O7 i
偏移         字节数         内容
0 R4 ]+ n- ?$ ]) |. p! Q1 E1 b0-3         4         字符串“NES^Z”用来识别.NES文件 7 E/ i" u5 u) r3 U
4         1         16kB ROM的数目
7 I$ N2 K" r" |( ]$ }" S5         1         8kB VROM的数目
/ k$ I4 W: S& q: t3 s7 Z" ^9 _6         1         D0:1=垂直镜像,0=水平镜像 5 ?& ]3 n8 c: V. M1 U4 K$ h, P; f3 v
                    D1:1=有电池记忆,SRAM地址$6000-$7FFF / I$ C! d4 L- S4 Q8 q" n
                    D2:1=在$7000-$71FF有一个512字节的trainer 6 a& ~; k: t6 `0 t2 _; N% S' ~
                    D3:1=4屏幕VRAM布局
2 d" j4 `; ]: S& ?" ~1 F                    D4-D7:ROM Mapper的低4位 & M8 x) j, y0 m9 n# y) x2 C
7         1         D0-D3:保留,必须是0(准备作为副Mapper号^_^)
# `; H6 d7 t7 X; i                    D4-D7:ROM Mapper的高4位 # @0 z& b8 P8 {( q
8-F         8         保留,必须是0 " [9 L5 g/ T6 v2 S1 D; J
16-         16KxM         ROM段升序排列,如果存在trainer,它的512字节摆在ROM段之前
0 ?- r7 s! a9 ]8 u-EOF         8KxN         VROM段, 升序排列
7 _4 d: S! m" ]8 f/ D7 ~
, @  j* r% J: e# P, B然后知道这个ROM有0x08个PROM和0x10个VROM
4 `" I# a- ~5 r! I8 E接下来扩展PROM位0x10个:
; o! d: h" ]. N先把第0x04字节改为0x10,0x06字节的D1位置1(设置有SRAM):2 a: X6 M/ M7 F7 P6 \0 s
. ~. h! T$ k- g: n
由于mapper4的ROM在模拟器载入时会把最后16KB载入到整个64KB内存的C000-FFFF,因此需要在最后的16KB PROM(0x4000)前添加8x16KB PROM(0x20000大小),然后跳转到最后一个PROM的头部:偏移计算:
& d6 y0 x- T6 c& Z6 y: G最后一个PROM的头部 = (总PROM - 1) x 0x4000 + 文件头0x10字节。" w4 }; B) \) W+ D1 j
于是可以得到双截龙2的是:! p& b- {+ H# X' O
(8-1)x 0x4000 + 0x10 = 1C010。* Z+ `2 \  G, |  O
然后跳转到1C010:, C3 i4 D" D" h- Y; u/ k; y$ |4 H

- W" v* D9 j/ d& s* w+ U, a
2 Y! A7 X% Y  j; G& s' [1 x
8 d0 |5 |' P( x4 e然后插入0x20000字节的FF:% D1 c6 h2 f# F8 t. w

0 d( x1 z" x# y: U; H, M! C' D8 W* g2 d
& k1 C7 M+ Z1 j6 h, Y% Z6 l0 Y
然后保存:5 G4 a) V4 Y: V0 m
$ R" J8 }$ F$ \2 ~
2 S) M' Q7 P/ f! \4 [) f- I% i7 L
用FCEUX打开正常运行:
6 o  v1 b" \& f; E" t4 O6 H* q! @. |/ _* s" ~
查看文件信息:
0 L, t4 i' J( H- o9 }0 u4 n1 z' D
* z9 f  q( D  S& y# m, d. z. E7 p. {% C: r7 T3 p$ ]
接下来切页:+ V$ l! k  G  Q/ P
先打开十六进制编辑器:4 d2 R# A# ]3 @7 A3 j; b( n

2 e1 x4 c, q9 W' ^9 R& A拉到滑块最后,看看重启中断
" |$ a. T- B1 K0 O中断地址         中断         优先权 7 ^. r' V6 F# }3 N& _6 M
$FFFA         NMI         中
* p3 z5 N$ h/ C' M$FFFC         RESET         高
' E8 p. B/ L: h. k# @, R2 I$FFFE         IRQ/BRK         低
! k% U6 j* x0 w3 o3 L2 G& Z, k& q! i# e
RESET中断是ROM载入模拟器后最先开始运行的地方,只运行一次。
7 t! E2 e5 t6 B' {; }由此可知双截龙2的RESET中断$FF65。
- l2 t( P# o8 V" R+ w4 O* M接下来添加$FF65的执行断点:9 G# r0 [/ A, ?' M3 D
打开调试器:) X- p3 C# @9 F- S9 s

: ~# P1 `/ f0 @& y  Y添加$FF65的执行断点:
. C4 c3 M& t3 f/ a! b, }* e, t4 V6 p% l1 I; ^- e9 W: y3 T

0 ]# }9 o2 E2 l3 G. S9 e
5 m# Y/ U8 t" x' |9 E单击确定:8 o5 b$ _& d, t( G5 A

6 g4 z3 ~! Z6 P$ Z7 I
$ a, p0 I6 {; r+ d* q3 ^" J然后重启ROM:
1 v6 _: C9 y* {0 Y7 L$ u# G$ o' v5 r0 T- Y1 X0 U& P7 E2 i
调试器此时弹出来:
+ v& @4 P$ f: A3 h4 m3 g5 m0 h7 r( B1 C7 F
然后打开Hxd,写一段mapper 4的切换bank程序:
% }4 s6 T  H0 n6 O! N先看看mapper 4的说明文档:
/ R/ |% k1 g, Q' y6 w7 Y$ [Mapper 4
0 L' `- S4 t! @& s
+ m" k* w: L0 y" u6 V1 }$8000:  模式号) s, Z) w* M, |! U1 y
        位D0-D2:
2 [$ y$ ?4 u( T" d! S% ~        0:选择2KB的VROM存储体映射到PPU的$0000: O$ ~4 ?& M; R
        1:选择2KB的VROM存储体映射到PPU的$08001 }  P. w, Z* a8 N$ D
        2:选择1KB的VROM存储体映射到PPU的$1000! K; j, _) ^. n6 {
        3:选择1KB的VROM存储体映射到PPU的$1400
7 ^7 n) u0 \4 v& s        4:选择1KB的VROM存储体映射到PPU的$18007 O% B; K# Y; c- l
        5:选择1KB的VROM存储体映射到PPU的$1C00; ]! ?: @& F# M/ x2 H
        6:选择8KB的ROM存储体映射到$80005 \% N% s" x9 N4 d8 a, ]
        7:选择8KB的ROM存储体映射到$A000
5 L4 L  x9 F0 |* w% J        位D6:8 i2 |9 @/ e+ N
        0:允许擦写$8000和$A000
5 k4 ?6 n: V5 I( J  X6 \/ W4 {        1:允许擦写$A000和$C0008 J) Q& H% i1 I2 K% z. _
        位D7:- b9 X5 d% }8 M8 P1 N
        0:模式号D0-D2使用普通地址
8 h) ?/ j: @/ Y8 s        1:模式号D0-D2地址异或$1000
5 [3 e) t0 Z: B, F. X! u+ r3 }! G: r' A' Y4 x/ u
$8001:  模式页面号
: m7 g6 E. a9 N: d+ Y        写入一个数(00-07),切换存储体到对应地址
5 C) y0 ^) b8 Q4 _% Y6 l1 x
2 E1 N. E( d  P2 E$A000:  镜像选择: Y' ?4 ?8 p0 o7 a+ [
        0:垂直镜像6 q2 t2 s* X  E0 i
        1:水平镜像
- w6 [- f/ Q# L- ?
, r% Q/ A! j$ Y) x9 m$A001:  SaveRAM 切换
2 p7 B; X1 w! r# s& @        0:禁用$6000-$7FFF
1 N! w3 ^* A, \' N4 o# T- Q        1:启用$6000-$7FFF( @6 F2 ]9 r* q( e. J- r! P4 _; U( e

! Q4 V: b! m- z. M' T( v3 U3 E% l$C000:  IRQ计数器
2 E6 i3 b3 X; C$ T$ `        IRQ计数器的值存储在此处& w! o7 s) w' ~( `0 ?# F7 ]0 a

) U" L& o7 ^- c8 }- ^2 o$C001:  IRQ暂存器4 h2 y/ O* f7 I* x8 \5 E
        IRQ暂存器的值存储在此处5 E: D1 y/ Y+ i9 e) Y1 }% }2 r
0 U' F( f* G" t) ?. s2 s2 t0 n
$E000:  IRQ控制计数器0
$ B5 h# Q  E. w: @        向这里写入任何数来关闭IRQ,并从暂存器中拷贝数据开始计数,进入IRQ
. W( e3 }( d0 ]. J2 j* f" u; P9 E1 Q8 b+ t: H# g
$E001:  IRQ控制计数器1
! d0 J- s) t0 x/ ^4 h& [  ?5 s0 A3 ?        向这里写入任何数,允许IRQ(退出IRQ,允许下一个IRQ进来)/ {1 w, K  L( J+ L3 P( |- E
. g* k- }) w( H- c. V$ H2 b
那么就写段切页到$8000-$9FFF,然后跳转到$8000的切bank程序:
: H* m# q" B. |48 A9 06 8D 00 80 A9 0E 8D 01 80 20 00 80 68! l4 T- }- }8 M5 g
PHA      累加器A入栈
5 [3 \+ k) b! Z* n+ f5 Q% Z* T% WLDA #$06    设置切bank地址为$8000-$9FFF
/ v& }/ [7 r1 z- T, ZSTA $8000
7 W: F1 h1 y. {: ELDA #$0E    将第0x0E号bank切到$8000-$9FFF: I8 a0 D3 K9 G% {
STA $8001
# \% g& E) C: R$ OJSR $8000    跳转到子程序$8000
8 ~' P( B! W$ |! H; P4 iPLA     累加器A出栈
9 Z3 t+ D6 }, h; h: p; x+ \/ Q7 X6 ~* a' f$ X) @9 S0 {- [
为何切换的bank号是0x0E呢?
  r" e4 a4 P) a/ j+ X* Z因为在扩容ROM后,文件PROM结构为:
) V/ V- l1 L1 e9 F2 |, H原来的0x07个16KB PROM + 自己的0x08个 16KB PROM + 原来的最后0x01个16KB PROM
# X0 G' u; K' s3 a0 \2 Q% i; R( T. y8 i% o
Mapper 4切bank一次是切8KB,那么文件结构就是:
/ n5 b* a- |/ q" H* x# |" M原来的0x0E个8KB PROM (00-0D) + 自己的0x10个8KB PROM (0E-1D) + 原来的最后0x01个8KB PROM (1E-1F)
# `! |3 A6 f/ n2 Y) v因此选择扩容的第一个空白bank就是0x0E号bank。0 C. t2 l6 z4 ^& W& G& i

( s1 m: V  s- A, [1 d2 o6 H! K7 b9 S$ y0 r8 v$ Y
然后去调试器找RESET中断中可以放下切页程序的地方:
: i+ b! I( |- `5 }7 N首先长度要小于等于自己写的切页程序。7 H+ R9 t) S9 U8 c
可以看到FF7C可以使用(一般找程序中已经禁用中断后的地方,也就是找使用了SEI指令后的地方)  U" I7 m) ^2 |% J1 X) ], g4 @

4 ?) T# b2 R3 V) z4 y! f; W$ C然后跳转到$FF7C:) P- n: C% {  f4 u( O

1 A) @+ ^4 U9 P# E) G& U4 M/ b. h
! `: S* T  W9 k2 x1 u/ i4 L7 t% E* `6 p( G5 I! c8 v& o
8 |$ ]6 B" ~4 C" h1 {# ?6 y, v
复制下可以被替换用于写切页的程序:
& G- Z$ V+ }& J& @  z, H* E先选中,再复制:" j9 j# K( }( Y: `( a( V

$ g! X' H( f$ G& f* u4 `% v" s7 c+ H; ]/ ]% [( W* t3 e
在Hxd中新建一个文件,把复制的数据粘贴上去:5 U- t2 g# L+ ]: R

  E6 Q2 V  t0 a' K6 n; d( P  ?1 t/ g7 K5 M; k) C) J- Y

; J$ r7 H7 T8 D
: d. y/ Z/ f" F然后回到十六进制模拟器:+ `$ B' I/ ?2 Z  M. ?$ N$ C# ]% A
转到$FF7C对应的ROM地址:: s9 b# Q& P, z% n: {, o1 R
/ [7 ]/ B) @; X$ r6 g, d2 {

: K7 O- n0 L; r7 Y- n然后复制Hxd的切页程序,粘贴到这里:
5 \/ U  \/ E9 h- B2 d5 d+ M0 W4 ?0 k1 g* p  k

6 b5 i9 G4 j$ U/ e# C! h' I1 {3 T
% t1 a6 n+ {% b, G/ ~没有覆盖的用EA覆盖:
" v* @% a. G1 Y" P- c# O' a. h  k. z: g# C9 Q0 Y' B, N1 U3 G5 \
打开调试器可以看到变化:% V4 O6 ]1 W# S" n* G2 _
: }" p. e$ U  ~, Z7 X
然后添加$FF7C执行断点:. V# W0 B( m" n9 T# `
& B3 z4 V' r( ?1 O4 X& y

2 @$ W5 s  J8 s* Y. q
1 n) T- Z$ u/ B% V# e' k单击运行:
6 u( \& `  A0 w+ U) _5 o. f然后程序在$FF7C这里停下来了。+ x% w: V6 E, C$ H- g
; Q2 @5 ?. r1 V- |7 Z/ N) d6 e, g
然后单击单步进入慢慢跟踪,直到跳转到$8000:& F) M  v! r9 j& r/ `

& c* d& `  b/ a: U8 X然后打开6502_Simulator:
4 Y, F4 B0 Q! m5 \% Y5 [  H( p3 g1 A4 o2 D6 A$ w
再打开我写的数据搬移程序:* n# V! ^! S; M# y5 v2 n1 y3 m

4 |3 l: a+ p( U% Y$ N) d0 A
0 Y, [* \" I& p) I然后修改对应的数据:4 J- x9 O: G& t
程序开始地址:修改比如$8100就修改为 .ORG $8100- z1 }& ~! E2 A% w
复制到什么地方就修改Addr_To,比如复制到$7000: .BYTE $00,$70! L7 O" g5 B' s2 i' O# ^3 ^6 e
从哪里开始复制就修改Addr_Begin,比如从$8200开始复制: .BYTE $00,$82
' }, w( m, Y+ ?" E- j: r想到哪里结束复制就修改Addr_End,比如复制的数据源地址到$91FF: .BYTE $FF,$918 j& @* A, W4 w5 h( @
也可以直接修改为 .BYTE $FF,$FF(复制目的地址到7FFF时会结束复制的)。+ q9 m! k/ `# c1 [; w* Y# K9 s
如果复制的数据最终超过7FFF,那么程序会结束复制,7FFF后面是程序块,不能乱搞。( V6 d1 k) R5 X( I0 D4 F# G
中断地址可以不管,因为在RESET中中断已经禁用了,程序不会被中断。
( L3 h9 x  W0 N, Y1 c+ @+ k后面的不用管。1 K6 Y3 }, N. z, A# P
' B$ @' e) Z; S: s  Q2 `
设置完数据后单击编译:
6 y/ j9 f- e& p. }6 g2 L$ r" Y7 p; S6 d9 Q; s  a# z7 j, }* X
然后保存编译文件:# [' \. t0 X3 f/ L8 p# h0 ]

, b( r8 ^2 X. [, D' [& o选择二进制方式保存:
$ y, N5 }3 m# h5 |
9 Z; D9 A) j" J3 k$ y2 b: W1 x* J" G1 X0 {
用Hxd打开保存的二进制文件:0 M5 _0 `) K1 m$ V; D8 n; P

0 M& R1 W; c/ e1 e. H; C
, M5 K) Q5 c' j5 b跳转到设置的程序开始$8100:: O8 R* L( |8 c/ o5 O+ K" H
, c4 C  B5 x9 a. J8 P1 {

1 [. W9 t( V0 ~8 D# x3 h. ?+ B' r  H" W# y4 w2 V$ C) L) i" P
回到FCEUX,转到NES内存的$8100对应的ROM地址:
' s' F; e9 h1 z- @/ {) P
* E# ?$ C& Z, i+ c2 J3 }# ?, W7 b9 z9 y0 }
7 C1 U3 F% l8 U; L! u4 {

6 I: c1 f' q( v. H# a+ T& D
% I$ T( B9 p1 v7 j+ i然后把Hxd里编译的数据搬移程序复制后粘贴到ROM里:
4 ^( P: b1 l3 [. K
7 ?9 g: y8 E5 P% I* @" F" O5 [- F( p: ]8 s1 v$ C) |7 p; E; \& U3 G
% l( r5 c; n7 u' i7 k7 {+ u8 ~
然后转到NES内存的$8000对应的ROM地址:9 P8 O" v# X8 ?5 ?6 a

6 q3 x' O/ N3 g0 g1 {7 K7 [% N4 ^5 Q! j( e. m( y$ \! u6 Q

% v; Y! A' p$ r% U. X# l& i/ J. E4 v' w
写上如下程序:
1 n0 m" C, u. G! u+ XA9 80 8D 01 A0 20 00 81  / H$ F# U* M5 K" |, S7 z) f' R
LDA #$807 Q2 Y8 o/ w  k# S4 r
STA $A001 可写方式开启SRAM" L' v8 k( o* y( T+ s
JSR $8100 跳转到子程序$8100( v  T3 W0 Y# _4 z+ G5 D+ v
3 V5 f8 l( ^. n; o' |7 A
然后把Hxd里被覆盖的程序复制过来粘贴在后面:
; o% c: m9 x) E- G+ w0 s$ G8 Z/ }5 k" K& L- M# R7 N
1 o0 d( C0 \+ Q
' p7 g4 d7 Z" Y' \. |% x) N- ]9 O
末尾补上一个0x60:
6 |2 F. `4 ]! URTS 子程序返回  n  L$ {, I0 ^6 I4 P, [6 [

2 C% u: ^/ p. F# R然后单击运行,ROM音乐响起,正常运行:! `# x, ~* J* ?8 F0 l  F
5 G7 \& r$ g: u9 x9 k9 p* \
/ [1 Y4 @& u6 a3 X7 ]1 M
然后转到NES地址$7000:
6 b& h7 b, _( h6 {2 {( l2 Q7 A/ [0 y' L) p3 O
5 y' @% v9 }8 \# q- t6 l0 K$ N5 _, e
7 H' \- O0 g5 w" W, X" O

* r+ X3 t# t! H* P" h# r
& T1 O( C) P" l4 r可以看到,$7000-7FFF都被复制了一片数据。
" x- Q4 ]) J. B% \测试没有问题,然后保存文件:
5 e/ m1 @) a  i6 L- s. {" a. R% Y: _7 t! N; }, X
以上就是Mapper 4 的复制数据到SRAM($6000-$7FFF)教程的全部讲解。
3 I+ E. c) a; }6 L% f$ P后期修改ROM时遇到没有空间写程序时可以使用此方法进行扩容,将自己的其他程序放在 复制开始地址 至 复制结束地址 之间,这样ROM载入模拟器后会执行一次数据复制程序,把自己的程序复制到$6000-7FFF之间,后面的修改只需要跳转到$6000-$7FFF之间自己写的程序那儿就可以了。; s) g4 B! t0 o1 _' Z
游客,如果您要查看本帖隐藏内容请回复

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

签到天数: 2060 天

[LV.Master]伴坛终老

发表于 2017-4-29 01:07:47 | 显示全部楼层
好多内容没有心情看了、、、、、
[发帖际遇]: liujunbtx为灾区捐款 6 个 柠檬. 幸运榜 / 衰神榜

签到天数: 1184 天

[LV.10]以坛为家III

发表于 2017-4-29 21:00:16 | 显示全部楼层
火纹外传扩容就可以汉化?

签到天数: 1800 天

[LV.Master]伴坛终老

发表于 2017-4-29 22:24:54 | 显示全部楼层
非常好的教程!👍

该用户从未签到

发表于 2017-7-25 15:03:46 | 显示全部楼层
{:4_104:}来学习学习感谢分享
回复 支持 反对

使用道具 举报

签到天数: 30 天

[LV.5]常住居民I

发表于 2017-7-27 21:47:15 | 显示全部楼层
看一下楼主的教程,非常好。
回复 支持 反对

使用道具 举报

头像被屏蔽

签到天数: 5 天

[LV.2]偶尔看看I

发表于 2018-2-8 21:36:34 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回复 支持 反对

使用道具 举报

签到天数: 57 天

[LV.5]常住居民I

发表于 2018-10-23 13:32:34 | 显示全部楼层
好东西,看看
回复 支持 反对

使用道具 举报

签到天数: 208 天

[LV.7]常住居民III

发表于 2024-3-22 21:01:24 | 显示全部楼层
进来学习。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|国治模拟精品屋 ( 沪ICP备15012945号-1 )

GMT+8, 2026-1-9 13:31 , Processed in 1.073243 second(s), 23 queries , Gzip On.

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表