关于所谓FC超级马里奥兄弟初代(以下简称SMB1)隐藏关卡的问题,出自当年都市传闻,如果经常看敖厂长视频的应该有所了解,这是事实上存在的:
- 先玩一会儿SMB1
- 快速拔掉卡带换成网球玩一会儿
- 再拔掉卡带把SMB1卡带插回去
- 按RESET
- 进入标题后,按START+A开始游戏
就能进入“随机”隐藏关。但这些关卡似乎在“设计”上有很多问题,要么进去就死,要么玩着玩着就卡死,要么就是没有路或者死循环反正不能过关,再要么就是过关后卡死或者回到标题。
FC吧主金属狂人对此也做过验证,用了不同版本的主机和游戏,该问题基本上能复现,除了以下两种情况:
- 美版NES由于锁区问题换卡时相当于按Power重启
- 使用欧版《SMB1+打鸭子》合卡时无法进入
在这里先说结论:隐藏关在设计上并不存在。任天堂在SMB1中没有从来没设计过这些隐藏关,而是由于程序bug设置了错误的关卡ID,因此引用错误的数据去创建场景。这些关卡应该叫“bug关卡”。于是我产生了以下疑问:
- “bug关卡”触发的原理是什么?
- 为什么欧版合卡无法进入“bug关卡”?
- 既然复现步骤这么复杂,那怎样在(无法热拔插卡带的)模拟器上玩到这些“bug关卡”?
初期探索
首先是使用模拟器的RAM Search功能(当然,也可以翻翻金手指大全什么的),找到了SMB1存放关卡ID的内存:
当前玩家
大关 $075F
小关 $0760
另一个玩家
大关 $0766
小关 $0767
标题界面将$075F
设置为#$00~#$07
代表正常的World 1到World 8,超过这个范围就是开局进入9-1、A-1……Z-1或者各种奇怪的符号-1什么的bug关卡。
不过之后在用$075F
写入作为断点调试网球的时候,发现这个地址从头到尾都没有动过。既然网球不会操作这个地址,那就存在其他网球用过的地址和SMB1的重合,并在后续RESET时没有被清理,最终传入了$075F
。为了验证这个想法,还需要看看RESET及内存初始化相关的代码。
关于内存初始化
首先跟踪初始化后$075F的变化,先设置$075F写入为断点,然后RESET:
1 | ; 初始化 |
这段程序会被多次调用,第一次执行时,栈里只有两个字节,2D,80
,很明显就是$802B附近调用(JSR)了这段程序,SMB1的RESET向量是$8000:
1 | 8000: SEI |
第二次执行时,栈顶指针是$8FD3
,说明在$8FD1处调用的:
1 | 8FCF: LDY #$6F |
同理,第三次调用:1
28FE4: LDY #$4B
8FE6: JSR $90CC
由此可见,三次初始化中范围最大的一次是$0000~$07D6
(排除$0160~$01FF
),后面越来越小。
特殊入口
进入标题后,直接按START开始游戏,并不会触发写入$075F的断点,但是注意,bug复现步骤中明确提到,要按START+A开始游戏,看来游戏对有没有按下A键有不一样的处理。RESET后回到标题画面启用断点,START+A,果然,程序停在了$830E
:
1 | 82E0: LDA $07FD |
至此,一个新的内存地址$07FD
出现在了我们眼前。
按下START+A时,会先从$07FD
取出数据,然后设置到$075F
和$0766
,完成World的初始化。$07FD
怎么来的暂时不重要,重要的是,刚才发现不管你怎么捅RESET,只要不断电重启机器,三次初始化都不会对$07FD
这个值造成影响。那么,只要能在游戏过程中写入$07FD
这个地址,任意一个游戏都能替代网球,成为触发bug的扳机。
为什么欧版合卡无法触发bug
吧主大人的解释是欧版合卡的基板与网球不大一致,不兼容,导致无法触发。这样的说法显然不能让我满意,最合理的解释当然就是合卡可能在RESET时对$07FD
进行了初始化。要验证这个猜想,只要下个断点就知道了:
1 | 80EF: LDA #$00 |
很明显了,合卡RESET时,会对$00~$FF
、$0400~$07FF
进行初始化,其中当然包括$07FD
,所以插上这张合卡,按下RESET,续关记录就没了,所以无法触发bug。
关于模拟器选关
实际上GoodNES中收录了各种改版的SMB1,数量不小,有951个,其中就有一些开局进入bug关卡的沙雕hack版。“俄国喷神”Kiraman在一期视频《Dendy编年史#1:超级马里奥》中就带我们玩了一下一盘1994年国产的9999合1卡带(GoodNES中也有收录),上面有不同关卡开局的SMB1,根据前文的结论,其实就是将$07FD
设为不同值然后引导进入游戏。
不用Hack版也可以用金手指锁定$07FD
的方式,或者用Hex Editor手动改一下$07FD
,只要掌握了原理,方法就多种多样,随意选择。
关于ROM修改
SMB1对于ROM的利用率非常高,放眼望去几乎没有几处连续的00或者FF,即使有也就几个字节,完全不够修改,查阅了国外大佬提供的一篇关于SMB1内存映射相关文档,上面也表示几乎没有连续的未使用。因此要改程序的话,要么扩容(比如刚才那个SMB1+打鸭子),要么加到trainer中,反正都是在模拟器上运行,就放在trainer中吧。
有机会的话,再做个标题界面按上下键选关玩玩吧。
附录:关于$07FD
的原本用途
在随后的测试中,发现只会在GAME OVER以后写入$07FD
:
1 | 9237: LDA #$00 |
回到标题画面以后,按START+A就能续关,如果不小心按了START也没关系,只要不GAME OVER,按下RESET还是能续关,我猜测不清理$07FD
以及START+A组合键的原本作用也许就是开发人员用来测试的。