84 天反编译一部 N64 游戏:Snowboard Kids 达成 100% 匹配
很高兴地宣布,最初的 Snowboard Kids 现已完成 100% 反编译!这意味着游戏中的所有函数1都有对应的 C 实现,编译后可以生成与原版完全一致的机器码。
这显然不是一个人能够完成的工作。特别感谢 inspectredc、Bl00D4NGEL 和 queueRAM 为项目作出的重要贡献。再多的 AI 也无法取代他们。2 另外,也要感谢 iFuzzle、JamesBLewis 和 douglasjv 为这项工作贡献了 AI token。
我希望完整的反编译成果能为 Snowboard Kids 社区提供帮助。尤其是速通玩家,他们长期以来一直专注于初代作品。可运行的源代码有助于解释一些从外部观察到的现象,例如 CPU 的路径规划,以及影响玩家速度的具体因素。彻底理解源代码,也将为未来的静态重编译和更具野心的 Mod 制作提供基础。
项目完成的速度同样值得一提。Snowboard Kids 只用了 84 天就完成反编译,而 Snowboard Kids 2 花了 596 天,耗时大约只有后者的七分之一。3
是什么造成了这样的差异?现在毕竟是 2026 年,所以 AI 至少是部分答案。但如果把差异完全归因于 LLM,那就过于简单粗暴了。
有哪些不同
显而易见,我并不是从零开始。到那时,我已经在类似项目上投入了近两年,效率远非刚开始时可比。这一优势很难量化,而且还被一些新挑战部分抵消,比如需要使用另一种编译器。
总体来看,约有 4.8% 的匹配提交需要专家介入。4 我之前已经感谢过这些出色的贡献者,但这里仍值得再次提及:如果没有反编译社区的大力协助,这个项目根本不可能完成,尤其要感谢 inspectredc、queueRAM 和 Bl00D4NGEL <3。
难点通常不在于弄清楚某个函数的作用5,而在于理解这套逻辑是如何用 C 表达的,以及编译后会生成什么样的代码。Snowboard Kids 使用的是 IDO 5.3 编译器,而不是 Snowboard Kids 2 所用的 GCC 2.7.2。
大多数程序员都熟悉 GCC。它是一款广泛使用、至今仍在持续开发的开源编译器。相比之下,IDO 是 SGI 开发的专有编译器,而 SGI 的发展历程又与 Nintendo 64 密不可分。6 由于 IDO 没有公开源代码,其最初的开发环境也依赖早已过时的 SGI 硬件和软件,如今要使用并真正理解它,反编译社区不仅需要逆向工程并反编译部分编译器工具链,还要将 IDO 5.3 和 7.1 套件静态重编译,使其能够运行在现代硬件上。这段封闭的历史,也让 IDO 比 GCC 这样的开源编译器更难分析。
IDO 会在多个不同的编译阶段之间分担优化和代码生成任务,并在过程中对代码进行相当激进的转换。7 因此,C 代码中极其细微的改动,都可能层层传导,最终导致完全不同的寄存器分配结果。
社区对 IDO 及其各种特性的理解已经取得了长足进展,但这件事依然更像一门艺术,而不是一门科学。LLM 和我都不太擅长复现它的输出。无论是我还是各个 agent,通常的工作流程都是先弄清楚某个函数的作用,再编写能够近似实现这一目的的 C 代码。接下来,再借助 permuter 做一些小幅调整,找出剩余的差异。不过,并非所有情况下都能靠不断尝试排列组合来实现匹配,尤其是在底层结构本身就不正确时。IDO 的行为让这一流程变得更加难以预测。
一支积极主动、具备合适专业知识和直觉的人类团队,完全有可能达到甚至超过 Snowboard Kids 反编译项目的进度。Pilotwings 64 的反编译只用了 74 天就完成了。8 Pilotwings 64 的函数数量比 Snowboard Kids 少 16%,但编译后的代码总量更多,因此两者也不能进行简单比较。

《Pilotwings 64》是一款别具魅力的飞行模拟游戏,既是 Nintendo 64 的首发作品,如今也依然是一部邪典经典。
Snowboard Kids 的规模也小于续作:前者包含 2,145 个函数,而 Snowboard Kids 2 有 2,995 个。函数数量是衡量难度的粗略指标,但需要反编译的函数确实更少。
Agent 发挥作用的地方
我之前已经在另一篇文章中写过如何使用 agent 反编译函数。这里采用的基本流程相同,因此我会重点介绍其中的变化。与上一个项目不同,这次一开始就能使用前沿模型和功能完善的 agent harness。
库代码及其他容易处理的部分
我很想看看,智能体在项目早期阶段究竟能发挥多大作用。其中一个表现特别出色的领域,是匹配标准库代码。理论上,这是几乎所有 Nintendo 64 反编译项目中最容易处理的一部分:这些代码并非游戏独有,网上也能找到相应版本。Snowboard Kids 包含一百多个来自 Nintendo libultra 的源代码片段,以及音频库 libmus 中的函数。借助 N64Sym 等工具,还可以识别 ROM 中可能属于库函数的代码。
这一轮的效果相当不错。最大的难点,是让智能体直接利用现有的库源码,而不是重新从头反编译同一批函数。这需要更强的提示。确认某个函数很可能来自库后,我会要求智能体以对应的源代码为起点,先穷举可能的 SDK 版本、编译器选项和条件编译路径,只有这些都行不通时,才尝试自行实现。
另一个优化方法,是让一个智能体编写脚本,对所有尚未匹配的函数运行 m2c,并自动整合所有完全匹配的结果,而不是让智能体逐个处理这些函数。9 这个脚本在 1,830 个函数中只匹配了 17 个,成功率低得惊人,仅为 0.93%;但凡是通过这种方式匹配上的函数,都比消耗智能体 token 便宜。
IDO 工具链与技巧
IDO 很奇怪,但它的奇怪之处往往会反复出现。成功匹配一个函数,可能就会暴露出一个同样适用于许多其他函数的编译器特性。借助本地记忆等功能,Codex 也越来越擅长把一个任务中学到的经验带到其他任务里。为了让这些经验不局限于单个智能体,我要求智能体把观察到的 IDO 行为记录在 DECOMPILATION_LEARNINGS.md 文件中。发现具有普遍意义的编译器特性后,智能体就可以把相关证据记录下来,供后续尝试参考。这样便形成了一个有用的反馈循环:智能体帮助整理 IDO 的规律,而这些文档又让后续智能体更擅长匹配 IDO 代码。
但最有用的资源还是 N64 Decomp Workbench。它提供了一套工具和文档,专门用于调试 MIPS 反编译后期的匹配问题:既能对差异进行分类、处理重定位,也能重放单个编译器阶段,帮助区分结构问题和寄存器分配问题。重放编译阶段需要准备相关的编译器二进制文件,并完成针对项目的配置;但配置好之后,它能提供原始汇编 diff 无法呈现的信息。原始 diff 只能告诉你两个函数存在差异,而 Workbench 能让智能体更清楚地了解差异产生的原因,以及可能通过哪类修改来修复。
Worktree 与同步
在这个项目中,我通过四个 Git worktree 运行反编译测试框架。每个 worktree 都有一份独立的仓库副本,因此可以并行尝试多个函数。
另一个虽小却很实用的改进,是为每项任务设置明确的截止时间,并将这个时间告知智能体。在 Snowboard Kids 2 项目中,智能体经常难以有效使用 permuter,因为它会一直运行,直到找到 100% 匹配或被手动停止。明确的截止时间让智能体可以设置合理的超时时间,在排列尝试和其他解题方式之间做出取舍。根据我的观察,这也帮助它们判断面对困难函数时应该坚持多久、何时放弃。随着简单函数逐渐完成,剩下的工作越来越难,我还可以相应延长时间配额。
另一个在 Snowboard Kids 2 中就已存在、但随着 worktree 增多而更加明显的问题,是同步。正如我在上一篇文章中所述,我会给每个智能体分配一个待反编译函数,同时提供一组已经匹配完成的相似函数。这些函数可以作为有用的参考,帮助复现特定的 IDO 指令模式。我们使用 Nigel 的 --shard 选项在各个 worktree 之间分配任务;该选项通过基本哈希,将候选项划分给指定数量的工作进程。
这样一来,更多工作可以并行进行,也避免了重复劳动;但随着各个 worktree 逐渐分叉,又出现了新问题。比如,一个 worktree 可能成功反编译出某个函数,与另一处正在尝试的函数有 99% 的相似度,但在改动合并之前,其他 agent 看不到这个新的参考对象。定期将所有改动合并到主分支,再重新同步各个 worktree,可以解决这个问题;但同步全部四个 worktree 有时要花一个多小时。同步过于频繁会浪费时间,等得太久又会导致分歧越来越大。
为了降低同步的重要性,我更新了相似度搜索功能,让它检查所有 worktree。新匹配出的函数可以立刻成为其他 agent 的参考,无需等到改动进入主分支。于是,harness 会输出类似下面的消息。
1✓ Candidate ["func_80094A94", "func_80094FF4 (../sbk-c)",
2 "func_80094808", "func_8009491C (../sbk-a)",
3 "func_8009469C (../sbk-c)", "func_80093144"] was fixed!
随着简单函数逐渐被解决,这种方式让整个流程依然保持高效。同步仍然是汇总并推送改动所必需的,但 agent 不再需要依赖同步才能相互借鉴。
模型选择
我试用了 GPT-5.5、GPT-5.6、Claude 4.5、Claude Fable 和 GLM 5.2。这完全算不上科学测试:测试所用的困难函数集合不断变化,有时某个模型还会接手另一个模型已经取得部分进展的函数。不过总体来看,Codex 依然胜过 Claude,就像上一个项目接近尾声时那样。Sol xhigh 上线后尤其好用。
由 z.ai 提供服务的 GLM 5.2 令人非常失望。我以前一直很喜欢 GLM。它虽然算不上前沿模型,但实际效果不错,加上慷慨的使用额度,足以弥补与顶尖模型之间的差距。可随着额度越来越不 generous,而延迟依旧糟糕,这种取舍就变得没那么划算了。反馈周期实在太长,我后来不再给它分配任务,最终也取消了订阅。
接下来做什么?
眼下的首要任务,是进一步完善游戏文档。100% 匹配意味着我们已经为每个函数都还原出了 C 代码,但不代表我们理解每个函数的作用。还有不少自动生成的名称需要替换,未知的结构体字段需要确认,难以处理的匹配需要清理,以及大量数据需要记录和说明。
Snowboard Kids 的重编译工作也在进行中。幸运的是,第一代游戏存在不少与Snowboard Kids 2:Recompiled相同的问题,而这些问题此前已经通过补丁解决。

我正在研究如何将第一代游戏的关卡及其他内容移植到第二代游戏的引擎中,不过目前还无法估计这项工作需要投入多少精力。
除此之外,我还对 PlayStation 版 Snowboard Kids Plus 的反编译感兴趣。这是日本独占的加强版,在第一代游戏的基础上加入了额外关卡和角色。
如果你能看到这里,想必你对反编译和 Snowboard Kids 都很感兴趣。不妨看看 Snowboard Kids 反编译项目。项目仍有大量清理和文档工作要做,非常欢迎大家参与贡献。
严格来说还没有完全结束。系统库中仍有少量手写汇编代码,因为汇编本来就是源代码,而不是临时的反编译占位实现。 ↩︎
我认为,即使在最理想的情况下,没有他们对 IDO 的专业经验,这个项目大概也会停在 89%~90% 左右。 ↩︎
对于 Snowboard Kids 2,我从2024 年 9 月 28 日的首次提交,一直统计到2026 年 5 月 17 日实现最终匹配函数,总计 596 天。Snowboard Kids 的 84 天是按项目整体计算的;公开仓库本身则从2026 年 5 月 28 日的首次提交持续到2026 年 8 月 19 日实现最终匹配函数,刚刚超过 83 天。 ↩︎
通过人工审查 Git 历史,我确认大约有 41 次匹配是在专家协助下完成的,约占我归类为函数匹配工作的提交总数的 4.8%。由于一次提交可能涉及多个函数,而且他人的帮助往往被直接整合进我的提交中,这个事后统计只能算近似值,也不代表投入了多少人时。需要说明的是,我没有把自己算作“专家”。 ↩︎
一个值得注意的例外是
validateControllerPakSave,它似乎曾被用于调试 Controller Pak 存档。最终版本中删去了其中大部分调试和验证逻辑。 ↩︎Nintendo 曾介绍过,Nintendo 64 采用了 Silicon Graphics 的架构。最初的开发环境同样高度依赖 SGI。Nintendo 的开发文档说明,需要将其仿真器板卡连接到 SGI Indy 工作站,而 IDO 是支持的编译器之一。因此,这种关联不仅体现在主机底层架构上,也延伸到了开发游戏所用的机器和工具。 ↩︎
IDO 采用多阶段编译流水线。其
uopt文档显示,符合条件的循环默认会展开四次。巧合的是,游戏中也正好有四名竞速者。因此,迭代四次的循环可能会被完全展开,而这类循环出现得相当频繁 🫠。 ↩︎从2026 年 1 月 26 日加入进度跟踪,到2026 年 4 月 10 日完成最后一批匹配函数,整个反编译过程前后几乎正好用了 74 天。Pilotwings 64共有 1,791 个函数,少于 Snowboard Kids 的 2,145 个;但代码量约为 838 KB,高于 Snowboard Kids 的 732 KB。 ↩︎
用这种一次性脚本并不是什么新鲜想法。我很确定自己是从别处照搬来的,可能参考了 Diddy Kong Racing 的 m2c_all.sh。 ↩︎