欢迎光临散文网 会员登陆 & 注册

对于@八百里的烈焰SMB1改版的修复过程

2021-09-12 19:40 作者:SKY2008_233  | 我要投稿

(文章由SKY2008_233 UID:402759619原创)

黄色字体为21.9.21晚补充内容。

深蓝色字体为21.11.27晚最终补充内容。

首先送上ROM:

链接:https://pan.baidu.com/s/110ukUojcZIwq7SwyRYYUrg
提取码:hack

通关视频:

作者原专栏:


下面则是我的修改过程

材料:FCEUX(UP用的2.2.0.2776)

建议没有基础的先看后面的修改

(可能需要一定的汇编基础,可见下面视频)


有不懂的点可以和UP主私信!周末一定会回!

FCEUX修改工具使用教程:

直接修改字节码来该指令需要查询6502汇编表,比较麻烦,为了提高效率,FCEUX自带调试+写入程序的功能。可以从主界面调试-调试器进入界面。

界面中,左侧是显示文件指令反汇编后的内容,右侧显示了程序实时运行的各个数值,可以添加断点和书签。改指令时,可以输入右上的“搜至”按钮右侧地址,然后按下按钮,便可定位到指令处。要对指令进行修改,可点击指令左侧的小格(图中涂红),会弹出来行内汇编器。

在上方方框中输入指令,然后按下回车,点击提交,即可完成更改。然后再调试-十六进制编辑器中点击文件-保存ROM,即可存入文件。

一、转移刺的程序

原来的改版中,4-1城堡音乐及其诡异。据作者自述,这段音乐是因为刺的程序在这里,自$FBA4-$FBF1,总计78字节,是按照MMM的程序修改而来。(下面黄色字解释原因)改版中没有水关,则可以用水关音乐来替代这段程序的位置。

刺的程序,后面还有一句JMP $9A44

观察这段程序,由于把地址写全的指令只有JMP和JSR,BEQ等B开头的跳转指令(如BEQ $FBBB),而所使用的的JMP、JSR除了$JMP $FBD0都是跳转到正常程序的位置($8000-$F90C)(JSR $2026后面会讲),则可以直接将整段程序复制到水关音乐的位置,改变$FBD0的值即可。

BEQ等B字开头的指令属于分支跳转,类似于高级语言中的if语句,因为这里跳转是指“跳转到此指令后的第几个字节”,所以可以在移动位置后不用修改。

那么问题来了,如何确定水关音乐的位置?这里就需要讲一下了,在游戏内,BGM的触发器地址为$FB,输入01,02,04,08分别是地上,水下,地下,城堡,而音乐数据会通过LDY $F7/$F8/$F9;JMP($F5),Y读取,则可以得出,$F5所写的地址就是音乐数据地址。我们只需要直接对$FB写入02,看$F5、$F6的值就行了。

将02写入$FB我们发现,$F5和$F6依次为52 FD。FC中,地址是要倒着读的,也就是说,实际指向的地址是$FD52,这就是我们要找的地址。但这还没有完,不要急复制代码!

我们需要确保水关音乐长度足够78个字节。如刚才所说,$F7,$F8,$F9作为音乐的偏移值,它们的值加上$FD52不会超过音乐范围。78用16进制表示是4E,也就是说,$F7-$F9中有任何一个数值在$4E及以上,空间就是绝对够的。

显然,音乐刚开始,$F8就是7X,$F9甚至是$AX,远远超过$4E,所以空间是完全够的。这一步虽然现在很简单,但是在其他实例中就不一定了,需要检查。

也许对于很多人,城堡音乐是循环且周期短的,占地空间可能不是很大;但是只要一仔细听,你会发现这是快节奏的曲子,按乐谱储存会有很多音符叠加,导致最终音乐数据很大。

现在,我们将$FBA4后的程序整个复制到$FD52,将$FBD0改成$FD7E。可以了吗?还没有。我们需要将程序的入口也进行更改!打开FCEUX自带的十六进制编辑器,打开编辑-查找,对程序的几个入口进行搜索。我们要改哪几个入口呢?这就需要一定的读程序能力了。

一般情况下,我们以RTS来切分程序,因为RTS是程序的结束处,再往后就是另外一段程序了。通过这个方法,我们可以确定原来的入口至少有$FBA4,$FBB6,$FBBB,$FBC0,$FBD0。分别查找这几个入口(地址全要倒着写,比如$FBD0就是D0 FB),看前面是不是4C或是20(即JMP和JSR);如果不是,可以往前翻,看能不能在120字节内找到JSR $8E04(20 04 8E);如果还找不到,那就说明这不是跳转地址,接着往下找。查找要这样重复几次直到ROM到$8010后或内存的$FFFA前。

以下是查找后结果:

$DFA9:4C A4 FB    $F95D:FB A4(舍)

$DFAC:4C B6 FB

$DE05:4C C0 FB

此外都不是跳转位置。

所以,我们将这三处指令分别改成JMP $FD52;JMP $FD64和JMP $FD6E,测试一下实际效果。我们只需要看人撞刺能不能正常去世,如果可以,就说明修改成功了!

当然,修改时出BUG是很容易的,如果你改后出现了下图效果:

这张图里人是动不了的,游戏卡死,只能重置

请检查你的地址是不是哪里输错了,以及是否保存ROM。特别是程序里的那个啊JMP $FBD0一定要记得改!(之前我就是把$FD7E写成$FE7E就崩溃了然后找了很久问题)

可能你会问:$FE7E怎么会崩溃呢?其实$FCxx往后一片基本全是音乐,而这本来就不是代码,指针移动到这里很可能会引起不可预知的错误。比如往后走的$FE96是字节22——JAM!因为除了#$A2,所有以十六进制2结尾的指令码都是JAM,指针一碰就死循环,然后丝毫不动,游戏卡死。JAM的出现率高达12/256(4.69%,不要小看),加上一些异常的JSR,JMP,这种数据区而非代码区可以说是很高概率会崩溃。

别忘了修改初衷,让城堡关音乐回归正常!而我们只要从原版里复制$FBA4-$FBA1的数据到这里的$FBA4,就可以了。别忘了保存ROM!

为什么要提到这个JSR $2026呢?2026所在的区域是显存区,这一部分是不可能塞代码的,所以在程序里出现这个,要么是修改数据残留,要么是特殊数值。在这里,因为是按照MMM修改的,就有了差异,MMM在这里是3个FF,即不会用到的代码,所以是修改残留,就不需要管它。

二、设置刺猬速度

1-1关尾有三只刺猬,但是有可能它们都会跑走,也有可能一个光速刺猬飞过来把你鲨了。这其实是因为敌人槽的数据残留X轴速度。正常快乐云丢下来的刺猬速度应该是F8/08,而在敌人槽空时,它们X速度都是00,即静止。这一次,我们找到存这些数据的地方需要一个文件,Memory map,内存表。下载地址:https://pan.baidu.com/s/1gdxkBJl 文件名就叫MemoryMap.txt

通过查阅,我们可以发现,$16-$1A存的是敌人ID,而$58-$5C存的是X轴速度。走到关卡加载出两个敌人的地方,我们能看到$16,$17为00 1D,即绿乌龟和火棍,$58在F8和08来回切换,$59则一直在快速变化,即火棍速度有特殊值。到了关卡后部分,我们发下$59定下来了,加载刺猬后发现,$16-$1A全填充了12,即刺猬,一会$1A又成了11,即快乐云,$5C被重新设置。这时,我们点击调试器断点区的“添加”按钮,按下图设置数值。

地址写成0058,勾上写,然后对$16重复这一操作(不要忘记打钩)
断点添加后效果

先将这两断点双击,关闭断点,然后重置,开始游戏,打开两个断点。

跑了一段路程后,游戏暂停,调试器窗口弹出,在暂停两次并继续后,程序对断点$CABF和$CAC5成了每一帧执行。

$16的设置
$58初始值设置,速度是有符号数,F8即15*16+8-256=-8
此后每一帧都会在这暂停,还有两个鲜明可见的STA $58,X

然后,关闭断点,重置游戏,手动设置$075B的值为02(重置后游戏会从第2页开始读取),开始游戏,开启断点,往前跑,游戏再一次暂停了。

$16设置,同上
$58每帧设置程序,同上

注意到了吗?$58没有初始设置,就被直接进入每帧设置程序了,也就是说,$58留的还是残留X轴速度。那么这是在哪里产生的呢?

重新检测加载乌龟设置$16时的程序,发现了一个之前提过的东西:JSR $8E04!

后面那群UNDEFINED,你们想到什么了吗?

为什么要强调这个程序呢?你仔细阅读可以发现,其实它的作用就是跳转到这个JSR后面程序的地址,这里相当于是“跳到C282起第Y个地址(从0开始数)”。这里的Y是$16的值,即敌人ID,所以我们知道,敌人程序就是在这里跳转的!

按照我们读出来的逻辑,我们找到直接加载刺猬的地址,即C282后72个字节指向的地址(一个地址有2个字节),即C2A6指向的地址C7A0。

通过几次调试,我们发现这个$C7A0并不直接指向那个每帧的程序,还要再过几次跳转才到。既然每帧的程序对先前的X轴速度做处理,那么我们在其前写一段代码,先改变X轴速度即可。

那哪里有这个空间呢?因为这个改版用到的地图数据很少,只有1-1~4-1四个关卡,我们可以把用不到的地图数据写一段代码。

这就需要一些关于关卡的常识了——每一关都有一个独立的空间号,存储在$0750。通过检测,我们发现,整个游戏区间,$0750只有25,26,C2,C0,60这几个可能值。添加$0750写入断点,我们可以得到,关卡指针在$E7-$EA,可以手动锁定$0750为20(为什么是20?看帖子吧,其实在一定范围内都可以,但千万别搞个66,75什么太大的数值),然后查看$E7的指向地址,为$A46D,我们在这里写一段程序:

LDA #$00;

STA $58,X;

JMP $C7A0;

我来解释一下这段程序。意思是,令 X轴速度为00,然后跳至刺猬程序。X保留的是此时是几号敌人槽,因此所有刺猬的X轴速度都会为0!

最后,我们把刚才$C2A6从A0 C7改成6D A4即可,保存ROM!

此时,1-1的三只刺猬都永远是不动的了!

此时,你可能注意到,可能有的刺猬朝向不同。如果我们要把朝向统一,满足强迫症该怎么做呢?

朝向是另外存储的,在内存46-4A,1向左,2向右。我们只要在JMP $C7A0前多写一段:

LDA #$01

STA $46,X

这里就是将这个朝向锁死成左了。

不过有一个特性,就是快乐云会出来在4号敌人槽。即$1A值会改成11。同上,我们添加$1A断点,对$1A值改成11的指令全改成NOP无操作,这样就不会有快乐云了!

看到前面那个LDA #$11了吗?这么醒目,给它和后面那个STA一起扬了!

不要忘记保存!

其实这种取消快乐云的方法有点治标不治本,因为很可能敌人的其它参数会被顺道修改,可能就看着一只刺猬突然缓慢移动什么的...这里提供一种原作者私信我的方法:

在地图后面加一个“停止持续的对象”,快乐云来了也会笋尖走掉。

三、小型修改:只显示大关号

应该知道的知识:大关数值存储在$075F,挺多人都知道的。

其实要改的根源是在显示上,那我们的断点就要写在PPU上。打开命名表(调试-命名表),可以看到这样的画面:

我们把鼠标放在上半部分的1-1,可以发现,这三个字符PPU地址分别为2073,2074,2075。在断点区加一个PPU里$2073的断点,在游戏开机时打开。下半部分不一定可以,不要选错了

可见下文关于命名表的介绍,SMB1里4个屏上下完全相同,$2000-$27BF与$2800-$2FBF完全一致,但是不确定后半部分的写入是否有效

这是一种情况。此时用的是STA写入数据的(PPU不能直接寻址,要写好PPU地址$2006和数据$2007),而A是24,明显不是1-1中的1。(#$24其实就是空格,也就是-1关左边那个图块)

此时的A是01,即显示1.就是这个时候!但是这是公用程序,不能直接改。往上翻,发现A是读取$0312得来的。所以我们可以添加$0312的断点,在重置时执行。

这里的0312是大关号+1存储的,即实际的大关号,因为存储大关号是减1的。同时,这里0312-0314是一并写入PPU的,所以只要把这个地方改成0313,0312和0314写入空位24就行了。即0304,X改成0305,X,然后写LDA #$24;STA $0304,X;STA $0306,X。

另外一种修改方法,可以看我最下面发的链接,最新的那个帖子里有

还是详细解释一下吧

仅仅是SMB中,$300-$3FF的可用255个字节可以称为“PPU写入区”,写入这里的数据会每帧以子程序(即上文的公用程序)发送到显存区显示图案。$300是一个写入指针,代表现在写入区的指针在哪里,在第几个字节。写入区的格式为:

写入地址(2字节)+写入数据的个数(1字节)+写入的数据+#$00结束字符

这里的地址牵扯到一个叫“命名表”的地址,就是卷轴。每个卷轴图块都有一个对应地址,一行32个图块,一屏992个图块共四屏,图块映射地址为PPU内的$2000-$27BF,$2800-$2FBF,调色板为$27C0-$27FF,$2FC0-$2FFF。写入地址就是PPU内的对应地址,并且一反常态,正着写($2000写出来就是20 00)。这里,"1-1"的写入字节如下:

20 73 03 01 28 01 00

综合上面内容,不难发现,第四个字节和第六个字节分别由$75F和$75C读取而来,即大关号和小关号。理论上讲,如果只覆写中间那一位,可以直接这样输入数据:

20 74 01 XX 00

XX为大关号。这样直接少了2个字节

每关,开始前的X-1如法炮制注意开启断点时间,在按下开始后帧进一步再开断点

现在引出的问题是,二周目选关时,标题界面的大关数会显示错位,在1的左边。前面提到的,$2006是一个PPU偏移值,显示时应该为74,但此时为73,所以要做的就是在此前将73写入$2006改成74写入。

最后有一个可选修改:二周目选关会选到5-1~8-1,不应该选到的关卡。先给$075F一个断点,当07FC(周目数)为01时按B就会暂停:

(一个有趣的事实:$7FC不管你打通游戏几次,都是#$01,但是有关程序代码全部写的都是读取后用BEQ和BNE是否为0来判断,完全没有用到#$01这个值,也就是说,完全可以把打通游戏的LDA #$01 STA $7FC换成INC $7FC)

这里其实是一个子程序,因为续关时也会执行,所以不能直接改。(为什么这么清楚?因为这就是盗版0-1的成因之一,可以看最下面链接里有一个帖子)注意堆栈区,最顶上两个就是返回地址,即$82A9是返回地址。82A8是JSR指令的最后一个字节,下一条指令就是地址加1

网上翻一点,发现这里的值是+1再与07作与运算。什么意思呢?就是保留二进制中的后三位,00~07。而因为这个改版只有四个大关,所以可以把这个AND #$07改成AND #$03,就是取4的余数。

如果不是2的次方数关,可以这么写:

CMP #$XX(关数)

BCC #$02

LDA #$00

BCC后面那个数在FCEUX里要在机器码那个字节改

保存!保存!保存!重要的事情说三遍

非常感谢你能看到这里!

如对文中任何内容有什么疑问,欢迎私信UP主,会在周末时间统一回复。

另外,关于FC技术的论坛、提问地可以见:

https://tieba.baidu.com/f?kw=%E6%BA%A2%E5%87%BA%E5%85%B3%E5%8D%A1&ie=utf-8(有很多你想知道的东西!极其高质量)


对于@八百里的烈焰SMB1改版的修复过程的评论 (共 条)

分享到微博请遵守国家法律