溢出关卡吧 关注:236贴子:3,542

回复:关于SMB1相关问题的专属提问帖

只看楼主收藏回复

好吧,事实证明“游泳状态顶砖”出现的问题其实跟$0704没有直接关系……
经过分析,其实就是“顶砖”这个程序本身的原因,程序入口在$BCED,前面有2个字节的数据:04 12,分别对应大个子和小个子顶砖时对人物纵坐标的增加量。
12这个数据已经证明了没啥问题,关键在于04,这个数值实际上有点大了。一个显然的事实是,Mario在顶砖时,头顶是会进入到砖块当中一点的但是也只是“一点”啊,又不是把砖块顶穿了,按大格计算坐标的话Mario的头顶应该是跟砖块的坐标一致的,换句话说根本不需要增加这个修正量;实际上,就算开了游泳状态,大个子顶砖时的纵坐标只要再往上一个像素,被顶起的砖也不会发生向下偏移的现象,没错,就差了这么一个像素至于为什么游泳状态比正常状态顶到砖时的坐标偏低,估计是速度的原因,跳跃的上升速度快,游泳上升速度慢,因此游泳顶砖无法达到足够大的深度。
所以你懂的,把$BCEB的这个04改小一点就可以了,干脆改成0呗,测试下来,完美~(其实同理12也可以改小一点的,不过没问题就不改了)
不过,既然这个数可以改成0,那是不是意味着这段程序可以优化一下了?不过好像节省不了几个字节……


IP属地:上海93楼2022-04-22 23:39
收起回复
    回复93楼中的问题:
    其实这个分析本身倒是并不复杂,应该说是一开始被你提到的$0704带偏了,走了弯路既然知道是顶砖的问题,直接看顶砖程序然后调试一下就找到问题了。说起来,这个调试的结果是:小个子顶砖时,纵坐标的低位是C或D,这尾数加个2其实也挺悬的;大个子跳起顶砖时,纵坐标的低位可以是9,这其实非常深入砖块了,但是游泳顶砖就达不到这样的深度(未测试纵坐标数值,但可以推测至少是C)。
    从这个纵坐标也可以看出来,当砖块与火棍重合时,为什么小个子顶砖块不会受伤,而大个子就会受伤,就是因为大个子顶得太深入了,判定框跟火棍重合了另外,改这个数值只能保证顶起的砖块不会发生偏移,纵坐标还是那个纵坐标,如果顶到火棍该受伤还是受伤,你可以试试看。
    关于水下藤蔓,这玩意至少涉及了两个问题,一个是你这里说的爬藤会卡住,另一个是这玩意还会导致关卡敌人发生错乱(说起来,我好像没整理这个资料,顺便补上好了)。
    爬藤卡住是因为水下关的屏幕上方和下方都有特殊判定,超出特定纵坐标按键就不会有响应,这是为了防止水下掉坑之后再游上来(其实实现这个功能的程序另外还有一段,算是双保险。)但是到了爬藤的情况,这里其实是通过一个自动操作来实现的,就是模拟按“上”键实现往上爬,但是这个纵坐标会把按键输入清零(包括程序设定的按键),按的上自然就废了。这段程序的入口在$B0E6,其中将按键输入清零的指令位于$B0FF:LDA #$00,只要把00改成08,就可以从“不按键”变成“按上键”,这样既可以保证爬藤不卡住,而且仍然保留“不响应其他按键”的功能,很巧妙吧。(这是在夸自己么
    敌人错乱则是水下的“生成藤蔓”和“生成气泡”两个程序发生冲突了,它们抢着用了寄存器X,藤蔓对应X=5,而气泡对应X=0;正常情况下,这两个程序不会都执行,但一旦都执行了,藤蔓先把X变成5了,然后气泡程序又没有把X清零,于是就出错了,本来应该存到$E4的“气泡纵坐标”被存到了$E9(敌人数据指针低字节),然后就你懂的了解决方法是:这两个程序调用之间需要读取一次$074E来判断是否需要生成气泡,原本是读取到寄存器Y的,改成读取到X,就正好把X清零了。这条指令位于$91B0处,把指令码AC改成AE就可以解决问题了。


    IP属地:上海94楼2022-04-23 13:20
    收起回复
      继续研究75楼当中留的一个问题:“为什么死亡的敌人会有一行贴图消失一段时间”。
      说起来,这还是最近的一个很偶然的发现,本来我都已经放弃这个问题的研究了(因为在敌人绘图程序当中根本找不到相关的处理指令),转而去研究调色板的问题(能否修改一下夜间配色下的黑色系敌人的颜色,特别是铁甲龟,让它们不至于融入到夜色之中难以辨认),然后很意外地在某段相关程序附近找到了这样一段程序:敌人的得分显示程序,入口$84C3,这里就不贴程序了,说一下意思:
      如果敌人是锤子龟,或者ID>=$09(但是其中再排除刺猬、食人花、两种游鱼),再或者是正常行动的状态(即非龟壳形态,也没有死,这个应该是飞龟被踩掉翅膀之后的情形),则借用“顶起的块”所用的精灵位置来显示得分,否则,就用敌人的第一行精灵显示得分。
      没想到居然是这样因为多数敌人的死亡状态和龟壳形态第一行精灵都是空白贴图,正好用不到,那就用来显示个得分,很合理吧;这也解释了为什么有的倒置敌人要交换第一、三行贴图,有的要交换第二、三行贴图了,因为交换第二、三行贴图的,第一行贴图都是空白(食人花:那我算啥


      IP属地:上海95楼2022-05-04 11:30
      回复
        现在还有大佬研究这个。。。感动啊
        顺便问一下任意代码注入是什么原理,是注入RAM吗


        IP属地:河南96楼2022-05-10 10:19
        收起回复
          看有点动静就来这里水一帖
          这段时间主要看了两个东西,就是9-1的第一个敌人和盗版的BRK崩溃填满堆栈的问题,电脑目前用不了就大概说一下:
          1.9-1第一个敌人因为第一页不加载敌人实际指向的是马里奥判定框纵坐标(地面时为D0)和0号敌人的判定框横坐标(哪一端的没有仔细看)两个字节。如果第一个敌人的此端还在屏幕外,即在横坐标FF,那么就会刷出敌人3F而崩溃(理论上可以游到屏幕最顶部,移动一个像素就崩溃),其他敌人也可以通过这一端坐标的值控制,不过有以下特点:
          敌人最晚在01D0处,即地面行走的D0处加载,此时可加载D0 1A敌人;
          除此外几乎所有敌人刷出来都需要上游以至于第一个字节为xF,刷出的是纵坐标位F的普通敌人(因为“页标记”标识始终位1),也就是说,除了35-3F(两个BRK和各种多个敌人),16(礼炮),0A,0B(两种鱼)等会被固定纵坐标或是有不被纵坐标影响效果的敌人才能看得出来,否则都会像是没刷出敌人。
          而后面某一水管的极易崩溃主要是由于A4 FF此敌人,A4为此水管下的食人花某端判定框坐标。如果此处没有崩溃,再往前走敌人减少,相对应坐标不会被更新而是填充FF,也就是结束标识,后面也不会有敌人了。
          2.盗版的IRQ向量FFF0是一段特有的子程序中某段,分别为:SLO(07) $60 LDA #$00 RTS,跳转地址高位为BRK处后两字节,低位为标志位P,整体再加1。这使结果更难确定,经过一定的尝试,把问号砖顶出来的东西(2E)改成了运行ID42的敌人,两种模拟器运行结果如下:
          FCEUX中,在1-2末尾顶蘑菇堆栈区被填满了43,是由跳转到无效内存某区域而重复PHP(08),运行到6000后就由于某种原因JAM卡死了(所以之后不会再填充堆栈),而1-1三处,1-2开头顶蘑菇都没有效果;
          Mesen中,在1-1开头顶蘑菇,被填充的值成了46,而通过013E问题,对应的71世界71-2正是地上7-4,00 FF也出现了相同的效果。调试发现,程序处于BRK死循环中,RTS返回后的程序指针为463F,经过一个3字节指令和一个2字节指令,在4644处发生BRK,正好放入的是46 46 3E三个字节。由于Mesen技术限制,2000-5FFF间程序不予提供调试器内反汇编,追踪记录器中被统一填充了LDA $00(实际执行很明显不是这样),目前只能知道这么多。
          不过这也说明两种填满堆栈的BRK本质是不同的,一个是PHP,一个是反复BRK RTS。(本身这个问题是我想起来B站有人尝试通过改动蘑菇砖顶出来的东西,用VirtuaNES在1-2开头顶蘑菇砖,重置后进入溢出关卡,并且是“云上白日奖励空间”,时间最后稳定在100多秒,才去试了一下,可惜那个视频已经不见了)


          IP属地:贵州来自Android客户端97楼2022-05-11 23:08
          收起回复
            回复97楼:
            第二个问题感觉挺玄学的其实关键就是RTS之后跳到什么位置去了,跳到无效内存则不同模拟器的表现也是不同的:VirtuaNES只能读出00从而继续BRK-RTS循环,然后每次BRK都比RTS多放一个字节进堆栈,最后自然会填满;FCEUX会读取Open bus(是叫这名吧)的数值作为指令码来执行,不过话说RTS的时候会往这里面放什么数值呢;Mesen我还没用过不清楚,看你的描述跟VirtuaNES是一样的。
            第一个问题,当年我刷的最多的两关就是9-1和K-1,随机敌人其乐无穷,你懂的并且我的经验是在开头刷一个右移梯,踩上去之后基本上就可以稳定破解崩溃,但是怎么刷出来的并不清楚;其他情况要么刷出三只板栗仔,要么就是“什么都没有”,印象不深了,还有没有别的就记不清了。至于你说的“纵坐标为F的可见敌人”,其实都是同一种情况:不受纵坐标影响,具体可以分成3类:普通敌人(火球、公主和多个重复敌人)、持续敌人(飞鱼、火焰、礼花、持续炮弹或鱼群)、崩溃(礼花也可以归于这一类),而单个的鱼并不属于这些情况(比如你知道2-3还藏着一只绿鱼么),你怕不是刷了个持续鱼群出来吧


            IP属地:上海100楼2022-05-12 11:18
            收起回复
              好了,我是感觉这个研究可能要暂时告挺长一段落了
              不过最近写了个专栏,就是溢出关卡y值的那些问题(比较经典的帖子,只不过我觉得b站受众会广一点?)
              cv16946933


              IP属地:贵州101楼2022-06-04 21:35
              收起回复
                好像发现了个奇怪的东西
                “炮弹”这个敌人,可以相关到三个ID:08,17,33,其中08是能正常放置的炮弹,17是持续炮弹/鱼群,而33直接放置则无效果。但是从SMBR和内存里都看得出来,炮台使用的炮弹ID是33。这就引出了一个问题:到底ID33的效果是什么?(我还没找到相关的代码)


                IP属地:贵州102楼2022-10-06 20:49
                回复
                  回复102楼:
                  33这个敌人ID,如果是从敌人单位加载的,那么它的初始化入口是$C2F0(RTS),运行入口则是$C8D6(还是RTS),确实就是“什么都没有”这就是专门预留给炮台生成的敌人ID。
                  既然是炮台的专属敌人,那么相关的程序自然也应该找炮台相关,最后找到的程序入口为$B9BC,包含了炮台炮弹的初始化和运行程序。


                  IP属地:上海103楼2022-10-06 23:58
                  收起回复
                    今天本来在研究IRQ相关的卷轴拆分,但是发现了一个很矛盾的东西:
                    NES中如果是处于“I为被清空就立马IRQ”的状态,IRQ完毕RTI返回后因为恢复了标志位,所以I位还是空的,所以继续IRQ,死循环,程序走不动。。。这种情况在MMC5中我还没有找到解决办法,所以感觉像是2J的那种IRQ卷轴拆分我还是玩不来


                    IP属地:贵州104楼2022-10-23 20:46
                    收起回复
                      实现了基本的SMB2J式IRQ卷轴拆分,但是还有一些问题:
                      跑动时很容易看到“拆分延迟”的现象,就是卷轴延迟一帧滚动,精灵原样,所以相对上精灵就向右“移动”了2~3个像素,调试后也没有找到原因所在...(还是我那个截图颜色有问题的FCEUX用了一个显示方块和敌人位置的lua)这张图是滞后了2个像素

                      另外,大风特效下($06FD=01)容易有一帧是卷轴拆分失败的...可能是过了IRQ的点还是什么的

                      注:$5203为IRQ扫描线值(在对应扫描线末尾触发),$5204.D7为IRQ是否激活
                      ROM放在楼中楼


                      IP属地:贵州105楼2022-10-30 22:43
                      收起回复
                        2023年还是来发一贴
                        最近看到很多SMB1速通加了“AISSON”或者“PIGOAP”的标签,相当于是金手指码,FCEUX也正好支持了8000-FFFF区间的金手指,我就试了一下。AISSON就是DF59:58,PIGOAP就是9148:51。第二个就是资料里那个“全程游泳”hack唯一的实质性改变处,就是把LDY #$00改成EOR ($00),Y的奇怪改法居然能传得这么广
                        然后就是AISSON了。这个金手指的效果就是“穿墙”,把马里奥向右深入到墙使系统会向左推得程序取消了。但是我最初看到这个改成的字节码是58的时候就感觉不对劲了,就是CLI 和IRQ折腾了好几个月的我感觉这个很可能会引起IRQ中断,虽然速通中并没有什么实质性问题。结果...在标题画面不开始的话,自动演示的撞到第一个水管就会引起BRK崩溃(现在看堆栈区浮动的36有种“系统都在给你扣6”的感觉)。Mapper 0的IRQ应该和$4017的写入有关,开始游戏后每帧会向$4017写入的FF在标题画面不会执行,也就使得游戏中不会触发IRQ了。
                        然后看了这个字节前后的代码:
                        01:DF4B:A9 00 LDA #$00
                        01:DF4D:A4 57 LDY $57
                        01:DF4F:A6 00 LDX $00
                        01:DF51:CA DEX
                        01:DF52:D0 0A BNE #$0A @ $DF5E
                        01:DF54:E8 INX
                        01:DF55:C0 00 CPY #$00
                        01:DF57:30 28 BMI #$28 @ $DF81
                        原版:
                        01:DF59:A9 FF LDA #$FF
                        01:DF5B:4C 66 DF JMP $DF66
                        01:DF5E:A2 02 LDX #$02
                        启用金手指后:
                        01:DF59:58 CLI
                        01:DF5A:FF 4C 66 [UDF]ISB $664C,X
                        01:DF5D:DF A2 02 [UDF]DCP $02A2,X
                        此后继续执行(相当于从DF52的分支就分了,但是X还是1,至于后面X怎么用现在还没分析)
                        01:DF60:C0 01 CPY #$01
                        改动前后都是三条指令,但是分析后感觉改动后每一条指令都有问题CLI的问题就是可能会引起IRQ,而后面两条指令码都是未定义的FF和DF。之前不知道在哪里看到说未定义指令码可能是“一个顶两”的效果,今天另开了ROM试了一下后,确实,ISB就是INC和SBC的组合,先INC,在SBC同一个地址;DCP是DEC和CMP的组合,对同一个地址进行DEC和CMP操作。在这段代码中,从前面可看出进入这里的X值一定是01,所以这6个字节相当于以下这四条指令:
                        INC $664D
                        SBC $664D
                        DEC $02A3
                        CMP $02A3
                        $664D是SRAM的区域,FCEUX和当下大部分模拟器可以当成RAM用,变向当了一个计数器而02A3是第40号精灵的横坐标,貌似这个数据会在之后重新写入,也不会有什么影响。
                        所以AISSON的实例其实就是让我练手分析了一下未定义指令码会带来的影响,也是一个“1个字节禁用3条指令”的范例


                        IP属地:贵州106楼2023-01-04 12:35
                        收起回复
                          这几天不知道怎么想的,打算写一个NES模拟器经过了几天把所有的指令艰苦的轮了一遍周期单位的操作,终于来到了PPU操作这一环,查阅资料后在测试时我发现了一个大问题:
                          众所周知,VBlank对应241~260扫描线,在此期间会发出NMI中断。一般来说,在NMI程序RTI之前,不会再一次进行NMI。前不久我在用FCEUX测试手柄读取时遇到一种情况,就是在NMI期间没有读取过2002,RTI后会再次产生NMI,直到RTI后扫描线已经达到261即-1以后。这是我拿SMB原版ROM测试NMI时出现的情况:


                          (不要在意PC为8082时还在JMP $8057,反编译依靠的是执行完毕后的指令,一条指令都没得执行自然还存的是JMP $8057
                          也就是说,在241扫描线发生NMI后,每一指令执行完毕后都在重新发生一次NMI,堆栈光速爆炸,直到261扫描线才不会再每指令都NMI。也就是没法实现已经NMI完毕,等待当前周期内下一次NMI有想过依靠2000高位的方案,但是现在这个情况PPU不容得你动一步,没有办法做出任何读写操作,2000高位仍然一直保持在“允许NMI”的状态。貌似CPU也不知道最后那个RTI到底是不是NMI最后的RTI,所以想问一下这种状态该如何判断是否继续NMI?现在暂时是真的想不到什么别的方案了
                          这让我想起了测试IRQ时一进去IRQ在RTI会立即继续IRQ的情况但是这个是NMI,还是和只会生成一次请求的IRQ不太一样


                          IP属地:贵州107楼2023-01-24 22:58
                          收起回复
                            我的SMB研究摆烂已经快一年了感觉这个状态至少还要持续到7月


                            IP属地:贵州来自Android客户端108楼2023-02-25 13:50
                            收起回复
                              摆烂是摆烂,不过最近都在瞎改SMB1正好今天相当于放了半天假,就集中搞了一些研究。相当于在这里做个笔记(什
                              1. 重温SMB1代码精简计划,惊艳于一个金币闪烁程序省下的51字节(),然后按照我自己6502的水平找了几个可以稍稍缩减的代码处:
                              (1)程序最开始LDX #$FF TXS和两次循环LDA $2002 BPL #$FB按SMB3初始化指令改为LDX #$01 BIT $2002 BPL #$FB DEX BPL #$F8 TXS,省2字节;
                              (2)此后不远处JSR $90CC紧接STA $4011(DMC_RAW)和STA $0770,其实都无用,完全删掉。无论如何,$07D6以前的内存都能被初始化;而这里$4011来源于我看到的F2D0音乐主程序入口最后那一段关于$07C0和$4011无用代码,大概就是如果正在放地上或水下音乐,就给07C0每帧加一,否则每帧减一,并抄给4011。4011这个地址的直接写入可以理解为波形音频直接输入的波形图(w7n的代码注释里写的是PCM音量),单向增减肯定根本不是声波,所以发不出来声音。有意思的是w7n的Memory map里也说07C0是“用于某些原因”的一个地址这两个地方就是把无用代码空间腾出来,共6+25=31字节。
                              (3)在填充地形“坑”中,游戏本来要通过074E查表得到87或00。但是程序中,仅有水下第一列坑要先在074E的第一个分支跳转后执行一些程序,其他的都会直接查表得数值。既然这么多分值,我用了一个LDA #$87 BIT $00A9这种保留A值的结构省去了查表一步,代码节省1字节,数据砍掉4字节,共计5字节。
                              (4)仍然使用SP0卷轴拆分的SMB1会两次循环读取2002的D6,但结合BIT指令V位的妙用,本来AND #$40就可省去了,共省4字节。
                              上述修改其实都是一些难度和修改量极小的修改,也算是我会有扣字节的能力了
                              2.折磨了我一年多的IRQ了结了!以下是我总结出扫描线IRQ(包括mapper4或5)的两个重要要点:
                              (1)设置好4017最高两位。写入$4017可以通过最高两位控制APU计数模式和IRQ的是否“一触即发”问题(即是否允许I位清空时不会立刻触发IRQ中断),手柄读取的开始只由写入4016控制。SMB1音乐主程序入口F2D0后的几条代码处是一个每帧执行的LDA #$FF STA $4017,如果不每帧执行,BGM听起来有些怪,资料上大概说是最高位控制音符的某个时间段后是否发声(还是什么?有些忘了),而D6控制的就是IRQ模式。我现在更倾向于理解D6设置就是“请求”,没设置就是BRK,而且还是循环由此这也是之前讨论过的AISSON问题的成因:SMB1的AISSON/AIISON作弊码为DF59-58,运行时会执行机器码为58的CLI指令。仍在标题画面时,音乐程序不会被执行,4017不会被写入,默认为0,且SMB1的IRQ是BRK死循环,故标题画面马里奥撞墙CLI后崩了,执行音乐程序时便无此问题。4017问题在VirtuaNES里并不体现,因此前段时间有人发给我的两个mapper4纵向卷轴的SMB1hack在FCEUX里灰屏(其实是调色板的颜色00),VirtuaNES内能正常运行。修复的办法现在来看很简单,把上述1.(2)第一个省下的六个字节写成LDA #$C0 STA $4017 NOP即可(这个参考了SMB2J的初始化程序),强制让RTI后状态位I为设置还行不通。
                              (2)Mapper5一定要注意读取5204。MMC5的IRQ中,5203写入扫描线编号,5204最高位控制是否进行扫描线IRQ,而读取5204可以消除这一标志。受之前4017问题的干扰,我在IRQ完毕后写入5204清空最高位毫无用处,但4017问题解决后暂未试过写入5204的方法是否可行。5204读取效果类似读取2002,读取后会取消本次中断。
                              最后,这其实是一份报告笔记而不是问题,所以没必要把回答问题总是当成一种负担(或许只有我是对之前这些过往这么看的)。


                              IP属地:贵州来自Android客户端109楼2023-03-23 22:10
                              收起回复