解构神奇宝贝故障?

时间:2011-07-29 17:14:20

标签: decimal hex reverse-engineering

(如果这是一个错误的地方,我很抱歉。我认为这绝对是编程相关的,但如果这属于其他网站请告诉我)

我从小玩神奇宝贝红色和蓝色,这些游戏非常有趣,但因为有大量可利用的故障而臭名昭着(例如,请参阅this ridiculous speedrun of the game使用内存损坏将项目屏幕转换为十六进制编辑器)

最近,我发现了一款​​有趣的游戏速度游戏,使用了一个名为“ZZAZZ故障”的小故障来破坏重要的记忆位置并让玩家几乎立即赢得比赛。根据{{​​3}},ZZAZZ故障的工作原理如下:

  

要开始训练师之战,游戏需要加载大量数据,例如如果被击败他会承认的钱。当它加载钱时,事情会变得非常丑陋。由于超出我的原因,钱以完全不同的方式存储,游戏使用三个字节的数据结构,而不是将值转换为二进制,它将其存储在“人”表示中。例如,$ 123456将被存储为0x123456而不是0x01E240,这是正确的转换。

     

[Trainer表中的一些无效条目]指向包含无效货币数据的位置。当游戏试图在所述结构中对这些数据进行算术运算时,它会疯狂并开始覆盖大部分RAM。更具体地说,对于每个三个字节的块,其中两个将包含0x9999(教练可以给出的最大金额)。这种模式通过RAM重复多次。为了更好地看到这一点,我建议在面对ZZAZZ训练器之后在模拟器上暂停视频,并将VBA的内存查看器设置为0xD070。

这种分析是有道理的,但作为程序员,我不禁想知道程序员究竟是如何编写使这成为可能的代码的。如果输入不是有效的十六进制编码的十进制数,那么编写一个将十六进制编码的十进制数转换为十进制数的函数就不会开始用0x9999填充随机内存块。

我的问题是 - 没有专门设计算法以这种方式失败,有一个直接实现从十六进制编码的十进制到十进制的转换,当输入无效值时可能导致这种类型的内存损坏吗

再次,如果这是偏离主题,我道歉。我的想法是,这个网站上的其他程序员也可能已经长大了玩这个游戏,这听起来像是一个有趣的逆向工程练习,试图弄清楚这样的故障怎么可能。

3 个答案:

答案 0 :(得分:13)

老实说,我的猜测是,这只是一个愚蠢的,令人讨厌的故障,有人写了他们的第一场比赛中的一个目标。口袋妖怪Red / Blue是该系列中的第一个,并且有许多其他故障,任天堂通常会通过批次测试,我不知道它是如何通过的。滚动屏幕转换问题是让我感到困惑的问题。无论如何,谁知道他们在想什么。也许这个区域是通过脚本编写的,因此存储的东西不同。也许位模式0x0101用于显示内存被释放,并且该代码意外地在奇怪的地方疯狂。我可以倾注Z80代码并在该平台上重温我自己的游戏开发时间,但是。太多的工作要尝试和解密他们正在思考的火焰。

虽然确实赚了不少钱......

编辑1:

好的,你了解它。我花了一点时间倾注在我的记忆中,为你找到了一个小窍门。 GBC / DMG有一个名为DAA的操作码。十进制调整累加器(A)。这样做是将累加器中的值转换为BCD格式。您看到的内存区域已经采用BCD格式:http://en.wikipedia.org/wiki/Binary-coded_decimal

现在我可以告诉你,在我用手编码Z80汇编程序的4年左右的时间里,我从来没有一次需要这个操作码,只看到它在我们用于显示一些的棒球游戏中使用过一次分数。虽然这是一个1周期的算术指令,但在正常编码中我永远无法真正找到它。嗯。我实际上还有任天堂的DMG技术文档。去图;)无论如何,没有任何令人兴奋的事情,除了它以一些时髦的方式与许多旗帜混淆。

我的猜测是假设表格为BCD格式。将其更改为该格式之外的某些内容会导致内部数学变得非常混乱 - 当它们不应该被设置时设置Carry和Zero标志。这会导致从一列溢出到另一列,导致计算非常大的数字。如果没有直接查看有关该区域的操作码,我无法肯定地说,但我的猜测是这里有一个问题,即在BCD数学完成时仍然设置了进位设置最大值而不是存储负数或超出范围的值。那个或DAA指令在接收垃圾数据时返回0x99返回值,虽然我不太确定。

希望这会有所帮助......

答案 1 :(得分:9)

可以想到一个算法(尽管我为那些写过它的人感到遗憾):

  • 假设输入是十六进制表示的32位十进制数字,小端(例如0x56 0x34 0x12 0x00)。

  • 现在循环遍历每个字节,而未到达零字节。 (这绝不应该发生,如果确保0x999999确实是最大值......但是唉,它不是。)

  • 在每个循环中,计算实际值并将数据写回整数(或进入其他缓冲区,在此处执行“循环 - 虽然“而不是像i = 0到4”之类的东西。

如果你的值最后没有0x00(即32位“十进制”整数大于0x999999),你可以看到如何得到一个小故障。

当然,这是计算该值的一种相当模糊的方法,但我认为很可能有人为此做了一个while / do-while循环而不是有界for循环。

编辑1:

起初我认为这将有“优势”允许字符串直接显示给用户(因为它将以空终止),但当然这不起作用小端。他们可以用big endian做类似的事情,但这需要一个向后循环溢出,我觉得这个错误不太可能让某人犯错。

编辑2:

也许这是一个编译器优化,因为未定义的行为(程序员不知道,如无效的指针转换或别名问题)?

答案 2 :(得分:6)

神秘解决了!它看起来像user TheZZAZZGlitch figured out what causes this

当游戏试图计算一个非常大的整数时,会触发故障。在内部,游戏有一个例程,重复添加值来模拟乘法。它似乎写入字节,转移到输出写入位置。该代码旨在切断超过0x009999的任何值,以便玩家从训练师的战斗中获得的收入不超过9999美元(这些值以十六进制编码的十进制值存储)。但是,当发生这种情况时,游戏会忘记重置输出指针,因此如果生成一个非常大的数字,游戏将通过将写指针移位并将0x99写入每三个字节中的两个来重复在RAM上写入模式0x009999。

希望这有帮助!