解构神奇宝贝故障?
Posted
技术标签:
【中文标题】解构神奇宝贝故障?【英文标题】:Deconstructing Pokémon glitches? 【发布时间】:2011-10-16 03:21:22 【问题描述】:(如果问错地方了,我深表歉意。我认为这肯定与编程有关,但如果这属于其他网站,请告诉我)
我从小就玩 Pokémon Red and Blue,这些游戏非常有趣,但因存在许多可利用的故障而臭名昭著(例如,请参阅 this ridiculous speedrun of the game,它使用内存损坏将项目屏幕变成十六进制编辑器)。
最近,我发现了一个有趣的游戏加速运行,它使用称为“ZZAZZ 故障”的故障来破坏重要的内存位置并让玩家几乎立即赢得游戏。根据the author's description of the speedrun,ZZAZZ 故障的工作原理如下:
要开始训练师对战,游戏需要加载大量数据,例如 [...] 如果被击败他将让出的钱。当它加载钱时,事情会变得非常丑陋。由于我无法理解的原因,金钱以完全不同的方式存储,游戏使用三个字节的数据结构,而不是将值转换为二进制,而是以“人类”表示形式存储它。例如,$123456 将存储为 0x123456 而不是 0x01E240,这是正确的转换。
[Trainer 表中的一些无效条目] 指向具有无效货币数据的位置。当游戏试图用所述结构中的这些数据执行算术运算时,它会发疯并开始覆盖大量 RAM。更具体地说,对于每三个字节的块,其中两个将包含 0x9999(培训师可以提供的最大金额)。这种模式通过 RAM 重复多次。为了更好地看到这一点,我建议在面对 ZZAZZ 训练器后暂停模拟器上的视频,并将 VBA 的内存查看器设置为 0xD070。
这种分析是有道理的,但作为我的程序员,我不禁想知道程序员到底是如何编写使这成为可能的代码的。如果输入不是有效的十六进制编码的十进制数,我想不出任何方法来编写将十六进制编码的十进制数转换为十进制数的函数会开始用 0x9999 填充随机内存块。
我的问题是 - 没有专门设计算法以这种方式失败,是否有一个从十六进制编码的十进制转换为十进制的简单实现,当输入无效值时可能导致这种内存损坏?
再次,如果这是题外话,我很抱歉。我的想法是,这个网站上的其他程序员可能也是玩这个游戏长大的,这听起来像是逆向工程中的一个有趣的练习,试图找出这样的故障是如何发生的。
【问题讨论】:
越野车携带传播? 你提供的link中不是已经给出答案了吗? 255 标记消失了,字节移位还在继续…… @ring0- 这是指 speedrun 如何使用 255 标记被 ZZAZZ 故障破坏的事实来完成以后的目标。 speedrun 的工作原理是使用 ZZAZZ 破坏一个重要字节,然后在另一段代码中导致缓冲区溢出。我要问的问题更多是关于为什么会发生 ZZAZZ 故障,以及为什么它会首先导致该字节被破坏。 @templatetypedef 我明白了。最好的可能是对汇编代码进行逆向工程。由于它在模拟器中运行,因此代码很容易获得。我没有勇气这样做! @ring0- 我很想尝试一下,但我不知道从哪里开始。你有关于如何做到这一点的任何资源吗? 【参考方案1】:老实说,我的猜测是,这只是一个愚蠢的、令人讨厌的小故障,有人在目标中编写了他们的第一款游戏。口袋妖怪红/蓝是该系列的第一个,并且有很多其他故障,任天堂通常会退出批量测试,我想知道它是如何通过的。滚动屏幕移位问题是让我感到困惑的问题。反正,谁知道他们在想什么。也许这个区域是通过脚本写入的,因此存储的东西不同。也许位模式0x0101
用于显示内存已被释放,并且该代码意外地在奇怪的地方变得疯狂。我可以倒在 Z80 代码上,在那个平台上重温我自己的游戏开发时间,但是,嗯。尝试解密他们的想法的工作量太大。
确实赚了很多钱...
编辑 1:
好的,你赏金了。我花了一点时间倾注我的记忆,为你找到了一个花絮。 GBC/DMG 有一个名为DAA
的操作码。小数调整累加器 (A)。它的作用是将累加器中的值转换为BCD
格式。您看到的内存区域已经是BCD
格式:http://en.wikipedia.org/wiki/Binary-coded_decimal
现在我可以告诉你,在我为游戏手动编码 Z80 汇编程序的 4 年左右的时间里,我从来没有需要过这个操作码,只在我们制作的棒球比赛中使用过一次分数。虽然它是一个 1 周期算术指令,但在正常编码中我永远无法真正找到它的好用处。唔。实际上,我仍然拥有来自 Nintendo 的 DMG 技术文档。去看看 ;) 无论如何,除了它以时髦的方式与许多标志混淆之外,没有什么令人兴奋的。
我的猜测是该表假定为BCD
格式。将其更改为该格式之外的内容会导致内部数学变得非常混乱 - 进位和零标志在不应该设置时设置。这会导致从一列溢出到下一列,从而导致计算非常大的数字。如果不直接查看读取该区域的有问题的操作码,我不能肯定地说,但我猜这里有一个全部检查,表示在完成 BCD
数学时是否仍然设置进位,设置一个最大值而不是存储负值或超出范围的值。那个或DAA
指令,当接收垃圾数据时返回0x99
的返回值,虽然我不太确定。
希望这会有所帮助...
【讨论】:
【参考方案2】:谜团解开了!看起来像user TheZZAZZGlitch figured out what causes this。
当游戏试图计算一个非常大的整数时会触发故障。在内部,游戏有一个重复加值以模拟乘法的例程。它似乎在写入字节,移动输出写入位置。该代码旨在切断任何超过 0x009999 的值,这样玩家在训练师战斗中的收入不会超过 9999 美元(这些值以十六进制编码的十进制存储)。但是,当发生这种情况时,游戏会忘记重置输出指针,因此如果生成了一个非常大的数字,游戏将通过移动写指针并将 0x99 写入每三个字节中的两个来在 RAM 中重复写入模式 0x009999。
希望这会有所帮助!
【讨论】:
【参考方案3】:我可以想出一个算法(尽管我为可能编写它的人感到抱歉):
假设输入是十六进制表示法的 32 位十进制数字,小端(例如 0x56 0x34 0x12 0x00)。
现在循环遍历每个字节,当您还没有达到零字节时。 (这绝不应该发生,如果 0x999999 确实保证是最大值......但可惜不是。)
在每个循环中,计算 实际 值并将数据写回整数(或其他一些缓冲区,您可以在其中执行“loop-while”而不是而不是像“for i = 0 to 4”之类的东西)。
如果您的值末尾没有 0x00(即 32 位“十进制”整数大于 0x999999),您可以查看如何出现故障。
当然,这是一种相当模糊的计算值的方法,但我认为很可能有人为此做了一个 while/do-while 循环,而不是有界的for
循环。
编辑 1:
起初我认为这将具有允许将字符串直接显示给用户的“优势”(因为它会以空值结尾),但这当然行不通与小端。他们本可以使用大端进行类似的操作,但这需要一个 backwards 循环来溢出,我发现这是一个不太可能犯的错误。
编辑 2:
也许这是由于未定义行为(程序员不知道,例如无效的指针转换或别名问题)导致的编译器优化?
【讨论】:
这个时代的GBC/DMG游戏,没有值得用的编译器。这都是手工编码的汇编程序。没有其他方法可以让代码大小足够高效,以适应 4K 存储和极其有限的堆栈空间。 @Michael:那正好支持我的第一个假设。 很可能他们认为它是空终止的,只是在汇编程序中传递指针。 @Mehrdad- 感谢您提出这个建议。我不确定错误的实际 来源是什么,但这是可能导致此类故障的转换代码的非常合理的实现。如果我找到了真正的答案,我一定会告诉你的。感谢您的回答! @templatetypedef:谢谢!如果您弄清楚实际来源,我绝对感兴趣。 :) phhhhhhhhh!拿赏金:)以上是关于解构神奇宝贝故障?的主要内容,如果未能解决你的问题,请参考以下文章
假如大数据组件中的动物都变成神奇宝贝,那会变成什么样?(大数据的组件动漫化)