溢出关卡吧 关注:250贴子:3,572
  • 6回复贴,共1

【bug研究】“踩鱼变乌龟”的原理解析及修复

只看楼主收藏回复

大家好,我又来了
上次分析了一个bug,就是“敌人被顶成龟壳”,顺便提到了还有个bug就是“踩鱼变乌龟”正常来说,原版SMB是不会出现这个bug的,因为鱼是没有能力在空气中水平飞行的,如果游鱼出现在非水下场景,这本身就是bug;(但是同样是水下生物的章鱼,为什么到了2代日版就可以飞空了……而且,在1代的时候,章鱼就已经有专门的踩踏得分设定了——1000分,当初制作人员到底想把这种敌人搞成什么样子……)不过,还是会有改版让鱼变成具有空中平飞能力的“异种”,这时如果我们踩到了这种鱼,就会发现,它们一踩就会变成乌龟……这是什么伪装技巧么
上次的帖子里已经说过了,这个bug也是由一段变身程序引发的,不过不是那段“各种方法搞死敌人”用的程序,而是踩踏敌人时专用的一段程序,由于两段程序对敌人ID的判定范围不同,因此被打死时不会变身的游鱼,被踩之后反而会变身
本帖将会深入分析这个bug的相关代码,并由此得出修复方案。


IP属地:上海1楼2019-12-29 20:18回复
    先从这个变身程序开始说起吧。比起另一段变身程序,这个就简单多了:如果敌人ID大于等于09,则敌人被踩之后就会变身。两种游鱼的ID分别为$0A(绿鱼)和$0B(红鱼),被踩之后会分别变成00(绿乌龟)和01(红乌龟),颜色正好不变
    就这么简单?对,就这么简单~说到这里,这个bug的原理就已经解释完了。
    那你给我解释一下,快乐云($11)、飞鱼($14)和炮台炮弹($33)怎么被踩之后不变身呢?
    别急嘛,让我慢慢说在变身程序之前,还有一段是用来处理“一踩就死”的敌人的:如果敌人ID是$14(飞鱼)、$08(炮弹)、$33(炮台炮弹)、$0C(火球)、$05(锤子龟)、$11(快乐云)、$07(章鱼),那么敌人就会被直接踩死,其中,前4种敌人被踩死的得分都是200分,后3种敌人的得分则依次为1000、800、1000分,这是通过对寄存器Y设置不同的值,达到读取不同得分设置值的目的的(这在上次的帖子中也提到过)。小提示:锤子龟和章鱼对应的Y值不同(分别是1和3),这意味着可以简单地把这两种敌人改成不同的得分。
    且慢!火球?你告诉我这种敌人可以踩死?开玩笑呢?明明一踩上去就受伤了!?
    嗯,这说明前面肯定还有程序,是用来处理“不能踩”的敌人的这里就有必要把代码写出来了,因为有些事情需要看着代码才好解释:
    LDY $16,X ;读取敌人ID存入寄存器Y
    CPY #$2E ;如果是蘑菇等道具类敌人,就转到“吃掉”的程序,跳转代码略
    ;一些无关代码,从略
    CPY #$12 ;刺猬
    BEQ #$4E ;跳转到“可以踩”的程序(纳尼?)
    CPY #$0D ;食人花
    BEQ #$7D ;跳转到“受伤”的程序
    CPY #$0C ;火球
    BEQ #$79 ;跳转到“受伤”的程序
    CPY #$33 ;炮台炮弹
    BEQ #$42 ;跳转到“可以踩”的程序
    CPY #$15 ;如果ID>=$15(包括火焰、Boss等)
    BCS #$71 ;跳转到“受伤”的程序
    可以看到,这段程序以$15为分界,将敌人分成了“可以踩”和“不能踩”这样前后两个部分,“可以踩”的部分又排除掉了$0D和$0C,“不能踩”的部分则排除了$2E和$33。由于这里判断敌人“不能踩”之后直接跳转到“受伤”的程序了,因此对于火球,也就没有机会再去执行“踩敌人”的程序了,后面的“可以踩死”的设置就是个摆设。那为什么还要写“火球可以踩死”的判断?这跟本帖说的bug有点关系,先留个悬念,一会解释。
    不过……说真的,这个刺猬又是怎么回事?
    实际上,看看这部分代码的套路,这里的本意应该是将刺猬也从“可以踩”的敌人中排除的,然而,由于用的是分支跳转指令,刺猬所在指令的位置需要向后跳$81(十进制129)个字节,这超过了向后跳转的$7F(十进制127)的上限了……如果真的写了$81,那就变成向回跳127个字节了(视为负数)所以,只好改掉了。
    那刺猬要怎么设置成“不能踩”呢?没办法,只好在后面补一段代码:如果ID=$12,则跳转到“受伤”的程序……这个程序补在了“踩敌人”程序前面的位置,实际上是把刺猬又从“可以踩”的敌人中重新排除了。
    这么写当然没什么问题,不过……前面的那次比较,从“可以踩”的敌人中排除出一个“可以踩”的敌人?(12<15)这不是多此一举么?
    嗯,看来这次修复工作的指令空间已经确定了,由刺猬提供一部分可以删除的指令,然后就可以修bug了。


    IP属地:上海2楼2019-12-29 20:23
    回复
      开修之前,还要解释一下刚才留的一个悬念:既然火球是不能踩的,为什么还要写“火球可以踩死”的判断?
      说起来,当初制作人员其实应该考虑过要修这个bug的,或者说根本没想把它写成bug可能在当初的计划中,$08,$09,$0A,$0B这4个ID对应的都是“具有水平飞行能力的敌人”,而且都是一踩就死的,这样,相关的判定程序条件就可以写成“敌人ID大于等于$08,且小于$0C,则都能直接踩死”;结果后来$09被废弃了,制作人员给它安了一个“静止绿飞龟”的程序,这样“大于等于$08且小于$0C”的范围就不能再用了(因为飞龟不能一踩就死),为了尽量减小影响,把两个端点$08和$0C的判断条件都改成了“等于”,同时对于两种鱼,设计成只让它们在水下出现,并且Mario在水下不能碰敌人。这样,代码就写成了现在这个样子,火球的判定程序很奇怪,并且留了一个在原版中不会出现的bug。
      好了,遗留问题解释清楚了,该修bug了。不过这次,我又动了一点歪心思还记得上次做了一个“修bug的同时更改敌人$09特性”的补丁么?这让“静止绿飞龟”被打死之后不会变红了,但是如果去踩它呢?没错,现在我要把它被踩的特性也改掉!
      话虽如此,刚才提到的指令空间可是似乎有点不够用啊……别急,再看看。如果我不删前面的4个字节,而是删后面呢?嗯,可以删掉6个字节的指令呢,这么一来空间就够了这样,前面的判断就需要改写,交换一下$12和$33的位置,然后就可以让$12直接跳转到“受伤”程序了(我可以顺便吐槽一下当年程序员的智商么);至于后面“踩敌人”的部分,把条件按照当初的设想还原回来……那$09怎么办?正好可以用删掉指令的空间来个“预先排除”~
      具体处理$09的方式倒是很简单:“如果ID=$09,则视为$0E来处理”(这两个更改敌人$09特性的补丁,用的都是这种处理方式)
      其实就是用一个正常绿飞龟的ID来代替09,这样绿飞龟就不会变红了~
      这个补丁也已经上传到网盘了,各位请自取。另外,这个补丁和上次的补丁相互并不冲突,可以同时使用(上次的两个补丁也不冲突,两种修复方案改的不是同一个地方)。


      IP属地:上海3楼2019-12-29 20:26
      回复
        好了,本帖结束。


        IP属地:上海4楼2019-12-29 20:26
        回复


          IP属地:新疆来自iPhone客户端5楼2019-12-29 22:28
          收起回复
            你好,留个联系方式做个程序


            IP属地:广东来自iPhone客户端6楼2023-04-26 03:53
            回复