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

【研究员属性回归】空间3C/46在不同模拟器中的表现及解释

只看楼主收藏回复

好久没研究跟这里相关的玩意了,一是忙,二是没研究课题……
这次要说的这个其实也算是“历史遗留”了,不过当初这个问题并没有作为一个单独的问题被提出来,所以我自然也没有考虑过。
这个问题是最近刚刚由元老 @愿卿勿忧 在我转发的那个“火星帖”当中提出来的,描述如下:
为什么K-1和B-1(分别是地上/地下版3-4)在某些模拟器中会崩溃,而在另一些模拟器中不会崩溃?
这两关的空间编号分别为3C和46,如题。
那么,下面开始正文。


IP属地:浙江1楼2017-09-14 16:46回复
    空间3C/46,或称地上/地下3-4,地形指针当然都是一致的(3-4的地形指针,A302),而由于某条规律的存在,它们的敌人指针也是一致的(009D,为RAM区域)。由于敌人数据来源于RAM区域,因此有一定的随机性。
    上面说的是数据指针,而这个指针实际对应的范围则有256个字节(对于敌人而言,地形则是258个,不过这里暂时只考虑敌人数据),这个范围就是009D~019C。注意这个范围与【堆栈区】(0100~01FF)有重合的部分,这也就是它会在不同模拟器当中有不同表现的原因。
    这组关卡在VirtuaNES的表现我就不多说了,当初研究的主打模拟器就是它,直接放结论:极少会崩溃,脸黑的话可能在中途加载一个由随机数产生的崩溃敌人,崩溃现象是卡机,原因是跳转到了BRK指令。


    IP属地:浙江2楼2017-09-14 17:00
    收起回复
      那么,这组关卡在其他模拟器中的表现又如何呢?
      选取两个电脑模拟器FCEU和FCEUX(注意FCEUX需要是2.1以后的新版本,我用的是2.2.1),以及两个手机模拟器nes.emu和Nesoid(其中nes.emu是那个帖子中所用的模拟器,有现成结论,这里偷个懒
      过程略,直接上结论:各个模拟器都【有一定几率】在如下位置发生崩溃,注意,位置是确定的!
      (图片来源:那个“火星”研究帖子)

      只是具体的崩溃现象有所不同,FCEU是重启,其他模拟器都是卡机。


      IP属地:浙江3楼2017-09-14 18:15
      回复
        好了,下面是分析时间。
        为什么会有这样的区别?
        首先,对于VirtuaNES这个“怪胎”(),它对RAM的初始化方式并不符合后来的模拟器的一项“不成文”(或者有明确规定?反正我没找到)的通用标准:采用连续的32位0和32位1(即4个字节的00和4个字节的FF)这种模式进行初始化,先填0后填1;而是简单粗暴地将所有的RAM区域都填充了0。这同时还导致了VirtuaNES与其他模拟器的另一项重要不同:对于某个有问题的SMB1改版ROM,其他模拟器都会出现0-1问题,而只有VirtuaNES(以及一些比它还早的模拟器)不会出问题。(参考资料1)
        然后,为什么其他模拟器会在这个位置崩溃?因为这组关卡的敌人数据起点是内存009D,是个奇数,而敌人数据单位多数是2个字节为一个单位(除了“空间传送”这个例外,它需要3个字节),所以奇数地址的内存基本上都是敌人单位的第一个字节(坐标),而偶数地址的内存则是第二个字节(类型);这样,当读取到堆栈区“未使用的”部分时(对于原版SMB1是0160),就会出现这样的情况:以内存地址0163和0164的值为一个敌人单位,它们恰好是00 FF,这是一个会导致崩溃的单位。(参考资料2)需要说明的是,这个单位的“难度加大后出现”标志位是1,也就是说,在5-3之前,它是不会被加载的;而我们这里讨论的实际上是K-1和B-1这两关,所以这个敌人单位才会导致崩溃。
        图片如下:

        上面所说的参考资料中,提到FCEUX在这种崩溃时的现象是重置,但有一点需要注意,当时我用的FCEUX还是旧版(2.0.3),它的情况与FCEU一致,而新版FCEUX就不再是重置了。为什么有这样的区别?这是不同模拟器对SRAM区域(6000~7FFF)处理方式不同的结果,请直接移步参考资料3阅读详细解释,这里不再赘述。(众:没有你这么偷懒的LZ:不服来打我啊
        那么,为什么崩溃不一定会发生,而是“有一定几率”呢?这就是上面提到过的“空间传送”这种例外敌人单位的功劳了,它会使各个敌人单位数据与地址的对应关系发生偏移,本来奇数地址是敌人坐标,变成敌人类型了,而偶数地址是敌人类型,变成坐标了;这样,敌人单位偏移了一个字节之后,原本表示类型的FF就变成了表示坐标,而坐标为FF的敌人是“结束标志”,没错,敌人加载就此结束,定格在这里了。所以,如果你有幸用这些模拟器玩这关没有崩溃,你也会很失望地发现,关卡后面一个敌人也没有了……而用VirtuaNES则不会这样,甚至踩了斧子之后还能看到敌人,更甚至如果这个敌人是火棍,我们的马大叔的脚步似乎就不想停下了,会一直跑出去很远才停下……(等等,似乎又发现一个遗留的研究课题?嘛,我很忙的,没空众:拖出去打死LZ:不要啊……)


        IP属地:浙江4楼2017-09-14 19:26
        收起回复
          顶,等等,不是八秒君么


          IP属地:上海来自Android客户端5楼2017-09-14 19:30
          收起回复
            嘛,差不多分析完了,想了想还是应该来个结论
            不过,好像还有一个问题没解释:为什么崩溃的位置是确定的?
            这个么,就用一个SMBU的截图来解释吧:

            这是3-4的页面1~2的截图,可见,导致崩溃的敌人是在第3页加载的。
            也就是说,虽然地上/地下3-4的敌人有“一定的”随机性,但是带“页标识”的敌人单位只有那么几个,直接看图就很能说明问题了:(说实话我真想用这个图把4楼那个换了……)

            没错,包括最后那个崩溃敌人在内,一共只有3个带页标识的敌人,其中地形指针还贡献了一个
            ----------分割线----------
            好了,现在该上结论了:空间3C/46在不同模拟器中会有不同的表现(或者极小概率崩溃,或者大概率在某确定位置崩溃),其原因就在于该空间的敌人数据位于堆栈区的“未初始化”部分(这里的初始化是指游戏开始时内部程序对内存进行的初始化,而非模拟器启动时对内存的初始化,因此这部分内存表现为模拟器初始化后的状态),不同模拟器对RAM的初始化方式不同而导致的区别。


            IP属地:浙江6楼2017-09-14 22:21
            回复
              今天又看了一下6楼那个内存图,发现有一个地方不太对……注意00C0一行最后(即00CE、00CF两个内存),出现了80和D8两个数值,而80所在的00CE是偶数地址,对应的是敌人类型,80这个值也是带有“页标识”的……
              也就是说,我这个图的截图时机有问题;实际上,这个图是在进入关卡之后,Mario走下开场的台阶之后截的图,如果不走下台阶,00CE的值就会是50,也就不带页标识了。那么00CE是什么内存呢?根据上面的提示,各位能猜到么?
              揭晓答案:Mario的纵向坐标!
              没错,这一片包含了游戏中各种可移动对象的坐标、速度等数据,这些数据为这一关提供了敌人数据大家有没有想到什么?嗯,SMB3的ACE TAS什么的好像有人说过“要是SMB1也能实现ACE的话”之类的话呢……不过,正常的原版游戏还是无法通过内部操作实现进入溢出关卡(36-1除外)的,就算能进,能够触发的异常跳转也非常有限,除了崩溃貌似什么都做不了……
              另外,还是看这个内存图,我又想到了另一种关于“这个关卡不会崩溃”原因的解释,没错,我怎么把“跳至页面”单位给忘了呢,看看图中被框住的FD,就在它后面就是两个3F 00单位,这一“向回跳”,后面的崩溃单位不就被舍弃了么,当然就不会再崩溃了……换句话说,这组关卡不崩溃的概率还是比较大的嘛
              补充完毕,这次是真的结束了?嘛,谁要是眼尖又发现了别的问题,到时候再说吧


              IP属地:浙江7楼2017-09-15 16:13
              回复
                666666
                顺便暖场


                IP属地:湖北来自Android客户端8楼2017-10-04 00:35
                回复
                  我又回来挖坟了
                  前几天在群里有实机玩家测试了这两关,发现它们的表现跟在VirtuaNES上很接近(可以说是一致了)
                  当时我就推断这是未初始化RAM区域的锅,因为实机不是模拟器,它才不会好心给你把内存用什么特别的数值填充一遍,于是堆栈区的栈底(0160~01FF)就会全部用【真正的】随机数填充,既不是“全0”,也不是“一串0一串1”,然后自然就不会在那个特定点崩溃了(然而崩溃概率2/64也不是个小数目……),也不会走到一半敌人就没了,表现得倒像是在用VirtuaNES一样,而且随机敌人的花样还更多了
                  同时,由于仍然有不小的崩溃概率,顺便测试了崩溃之后重置(不关机)会发生什么,结果发现:三台实机有两台都会在重置之后表现得与FCEUX等模拟器有部分一致,过了特定位置不再出现敌人(但不会崩溃);还有一台则似乎不受影响……
                  好吧,崩溃之后发生了什么呢?其实是陷入了一个“BRK死循环”(BRK本身也是一个跳转指令),每次执行BRK之后都会把3个字节的数据放入堆栈区:36 FE FF,其中36是状态位标志P的值,另两个字节则是执行完BRK之后的返回地址(FFFE)。注意到其中的FF了没有?没错,敌人结束标志!所以后面的事情你们就该知道了那有人会问了,如果坐标数据指向了FE会怎样?那就更悲催了,纵坐标为E的敌人是【空间传送】,需要3个字节的数据,于是这些数据自动分组成了一个个的FE FF 36,还都带着页标识,每个单位都要占一页,一共有几十个这样的单位,而3-4的地形一共才那么几页……
                  但是……我实在没想明白剩下的那台机子为什么会有不同的表现


                  IP属地:浙江9楼2018-03-27 15:36
                  回复
                    本来很多要说的,结果删了,还是发这个表情自在


                    IP属地:上海来自Android客户端11楼2018-03-28 02:36
                    收起回复
                      不是的,我那个qq几年没用好像被腾讯回收了


                      IP属地:上海来自Android客户端12楼2018-03-28 11:45
                      收起回复
                        我是在手机上用nes.emu模拟器玩的,发现K-1比B-1崩溃概率大很多,试了好多次才终于有一次不崩溃


                        IP属地:湖北13楼2021-02-09 20:13
                        收起回复
                          既然有人问了,那就分析一下空间06(9-1)的崩溃原理。
                          空间06/3F/49(9-1和52-1)的敌人指针是相同的,都是1C9D(相当于049D),实际数据范围是049D~059C,并不是堆栈区的范围,因此不像空间3C/46,不同模拟器的崩溃现象并不会出现不同。(对了,空间03【水下3-3】与空间3C/46的敌人指针也是相同的,对应的关卡可以是1-110、1-221或208-2。)
                          这个数据范围中,有这样一些值得注意的数据:
                          04AC~04AF Mario判定框坐标
                          04B0~04C7 敌人判定框坐标
                          0500~05CF 地形判定用的数据(偶数页)
                          由此我们可以看到,9-1的第一个敌人是可以控制刷出来什么的哦,对了,这是敌人数据刷的敌人,地形的水管还会固定刷出食人花,这就不是我们可以控制的了。然后,刷出来的敌人的判定框数据会决定接下来刷的下一个敌人,这个数据似乎很容易得到一个崩溃单位。(这一关如果控制不好,崩溃概率是很大的。)
                          那52-1呢?这是个自动行走的关卡,我们就没法控制第一个敌人刷什么了(似乎刷了个“什么都没有”?那也就没有敌人判定框的事了);然后,一直往前走,会在固定高度反复刷出一个炮弹,这就是地形判定数据干的了——天空场景的云形石块的ID为88,而88 88这个单位则表示在下一页的坐标(8,8)处刷一个08(炮弹)。


                          IP属地:浙江14楼2021-02-10 11:17
                          回复
                            回复 @SKY2008_233
                            你应该搞一份Memory Map看看的(我网盘里也有)
                            内存$00F0~FF都是音乐和音效相关的:
                            $F0:音符长度查表偏移值
                            $F1:直角波1音效缓存
                            $F2:直角波2音效缓存
                            $F3:三角波音效缓存
                            $F4:主音乐缓存
                            $F5~F6:音乐数据指针
                            $F7:直角波2音乐数据偏移值
                            $F8:直角波1音乐数据偏移值
                            $F9:三角波音乐数据偏移值
                            $FA:暂停音效队列
                            $FB:主音乐队列
                            $FC:次音乐队列
                            $FD:噪音音效队列
                            $FE:直角波2音效队列
                            $FF:直角波1音效队列
                            你可以看看你的推测对了多少(不过那两条指令似乎还真写对了)


                            IP属地:浙江15楼2021-03-07 22:19
                            收起回复