《莎木 (Shenmue) (1999)》逆向工程揭示可能的太阳位置疏忽
逆向工程探险 #3:Bug 还是特性?
到目前为止,我们只接触了一些使用一个游戏示例进行逆向工程的基础知识,但这次我们将研究一些不同的东西。
《莎木 (Shenmue) (1999)》以其对现实主义的追求而广为人知,至今仍是一个具有影响力的系列,其第三部作品于 2019 年发布。 这种对现实主义的追求并非无意之举,而是他们在开发初期做出的关键设计选择之一。 在 SEGA Dreamcast 上发布了两款备受追捧的经典之作,在这篇文章中,我们将研究游戏中的一些代码,这些代码揭示了一个谜团,以及如何使用逆向工程来理解游戏如何试图捕捉现实主义的本质,甚至可能了解原始开发者的意图。
逻辑
一般来说,逆向工程实际逻辑背后的概念大致相同。 我们必须用我们看到的代码进行推理,并尝试将这种推理应用于代码。 机器不执行 C 代码,它们读取和执行机器代码,而机器代码是在编译本身的有损过程中生成的。 这为逆向工程师和感兴趣的开发者创造了一个有趣的地方,因为能够逆向工程一些代码、数据甚至 bug 的技能,最终会提高你作为开发者的技能,从而引导你进入不同的职业道路。 一个明显的例子是安全性:恶意软件逆向工程和网络安全领域的其他领域都 inherent 需要能够准确识别一段代码正在做什么。
函数从其 C 源代码转换为“机器代码”,机器代码是汇编指令的汇编形式。 C 源代码被转换为汇编代码,然后汇编成机器代码,这个过程本质上是一个有损过程。 因此,在尝试逆向工程逻辑时,我们经常发现底层代码与原始形式略有不同,这是为了使代码更高效。
考虑到这一点,根据原始源代码、使用的编译器和工具链以及一些其他因素,真正理解开发人员的原始意图可能具有挑战性,尤其是在编译过程中通常会删除所有变量、函数名称和注释的情况下,我们必须尽可能地自己重新创建这些内容。 这是最复杂的逆向工程形式,大量的时间、精力和耐心才能获得回报。
谜团
在现实生活中,地球的轴向倾斜在确定一年中任何给定时间的太阳位置方面起着至关重要的作用。
在《莎木 (Shenmue)》中,模拟一切将是一项艰巨的任务,但通过这些,他们当然可以完成大部分工作。 在游戏中,非常强调遵守时间表和日常生活中的细节,这在下面的逻辑中得到了证明,我们将对其进行剖析。
这是经过数小时的逆向工程会话的结果,并且是前一段时间发现的,我们注意到了一些非常有趣的事情。 为了演示的目的,我省略了一些东西,这将在本文后面变得清晰。 如前所述,这款游戏主要基于日常生活,因此,我发现这段代码负责计算太阳的位置和方向,以便照亮场景。 逆向工程本质上是一个迭代过程——随着你的进步,你对先前函数或某些数据的理解可能会发生变化——因此,上面的截图显示了我当时的理解。
这一行将当前时间转换为旋转,我们知道这一点是因为 DEG_TO_RAD 在这种情况下是 π 除以 180:一种将度数转换为弧度的常用方法。 但 12 和 -15 是什么?
转到下一行,我们基本上有更多的证据表明该值是一个旋转值,因为它在被 HALF_PI
减去后传递到 sin
中。 我们继续到下一行,这是计算的核心所在。
这一行浓缩了很多逻辑,我们通过大量使用 π 并在度数中来回移动,从而更多地证实这是一个正在构建的旋转。 不过,这里有些特别之处。 到目前为止,这些幻数大部分都有意义,尤其是考虑到它们与 sin
的使用。 但现在我们有 365.0 要添加到我们的神秘数字列表中,以及一个数组:
这个数组正被当前月份直接访问,并且在仔细检查后,我们还可以看到数组末尾的 365,并且这个数组实际上似乎是一个映射一年中每个月的累计天数的数组。 有 12 个数字,每个数字代表该月已经过去的总天数。 我们将此数组重命名为 g_accum_days
以反映我们当前的研究。
现在我们对这里发生的事情有了一点更清晰的了解,上一行代码的部分表达式正在计算游戏当前在一年中的进展:
g_accum_days[month-1] - day / 365
然后借助更多幻数将其转换为旋转。
就像在之前的文章中一样,此时出现了一个新的理论:游戏似乎正在执行一种计算,该计算基于游戏当前在一年中的进展,通过近似其轴向倾斜(大约 23.4°)来得出太阳的位置。
第二次迭代
到目前为止,这只是一个理论,而且我们只研究了 Shenmue,但 AM2 也在不久之后发布了 Shenmue II,并且鉴于两款游戏具有相同的理念,也许如果我们深入研究它的实现,我们可以发现一些差异,并希望澄清其余幻数背后的剩余谜团。
在查看其实际代码实现之前,我们确认并重命名日期累积数组。 它与第一款游戏完全相同,但它的使用方式显示了其他内容:
这一行显示了第一款游戏中计算中的额外语句。 现在,游戏不再直接引用 202,而是直接引用日期累积数组的第 5 个元素。 检查第 5 个元素的值会得到 181。这可能会导致对夏至的略微偏差的近似值,夏至发生在一年中的第 ~172 天左右。 但是,更有可能的是,代码经过重构,可以直接在日期累积数组中引用它。
最初我们认为这可能是一个 bug,但尽管省略了 202,但两个实现仍然有些等效……
我们在这里看到的一些更改可能是编译器优化和代码更改的一部分。
首先,游戏本质上是将时间转换为旋转,其中正午 (12pm) 直接位于上方,每个小时代表 15 度,称为太阳时角。 然后,游戏根据地球的倾斜度 23.5°,使用给定的纬度 tiltAngle
来近似地球的轴向倾斜。
这一步本质上允许游戏基于当前日期和时间,以及给定的纬度,尽可能真实地模拟太阳的位置。
发现
为了总结所有这些,我们发现游戏正在正确地近似太阳的位置,其方式与游戏界最受欢迎的系列之一的设计目标和理念相一致。 但到目前为止,我一直避免显示一个关键变量:tiltAngle
为了执行此计算,您需要纬度,并且由于 Shenmue 位于日本横须贺,因此用于此计算的纬度应该是 35°,而 Shenmue II 应该是 22°,正如现实生活中一样。
但是,在这种情况下,这似乎可能完全颠倒了(双关语):
Shenmue I
Shenmue II
最终,如果没有开发者的澄清,就不可能知道,但在这种情况下,目的是展示逆向工程代码如何是一个迭代过程,并且你对原始逻辑的理解总是随着你对细节的深入研究而不断变化。
我希望你觉得这篇文章内容丰富,并且读起来像我投入大量时间逆向工程这些游戏一样有趣。 如果你从未看过它们,我强烈建议你这样做。
完整的代码将在不久的将来提供。