Featured image of post 5000 亿 token 之后:AI 反编译把一整个游戏还原了,真正贵的却不是模型,是那张「对得上字节」的验收表!

5000 亿 token 之后:AI 反编译把一整个游戏还原了,真正贵的却不是模型,是那张「对得上字节」的验收表!

上周,逆向工程师 Maurice Heumann(网名 momo5502)发了篇复盘,说自己在过去三个月里烧掉大约 500 到 700 亿 token,让一群 AI agent 把一款知名第一人称射击游戏反编译回了可读、可编译的 C++。到最后,99% 的函数被重建,其中 83% 编译出来和原版二进制逐字节一致。

他连游戏名字都不敢写——之前发过的两篇相关文章已经被撤掉了,他的原话是「corporate America was here to ruin our fun」。媒体后来扒出来是《使命召唤:现代战争 2》。这个细节本身就是全文的底色:这活儿游走在版权红线边上。

HN 和推特上刷屏的是那两个数字,5000 亿 token、83% byte-exact。但我把整篇复盘读完,最想写的恰恰不是这两个数。它们是结果,不是方法。

这篇复盘真正值钱的一句话,藏在中间:

我们一直没有客观的验收标准。我们从来没正确定义过什么叫「正确」。

顺带说个时点:这半年「AI 反编译」是集中爆发的,从 GBA、N64 到 PC 老游戏都有项目冒头,社交平台上还刷过一堆把两个游戏「揉在一起」的魔改视频,版权方的撤除通知也跟着来了。技术跑在了法律前面——这条张力会一直是这条赛道的背景噪音,但不在今天的正题里。

第一个月:代码很漂亮,但它是错的

项目开局挺顺。四个 agent 跑起来——三个干活、一个当 reviewer 被动地盯 commit、标记 bug。不到一个月,它们把游戏 80% 的部分反编译完了。游戏能启动,主菜单能渲染,地图能加载。

一切看起来都在正轨上。momo5502 团队也这么以为。

直到他们把代码拿去细看,才发现质量是灾难级的。代码确实极其可读,但语义是错的:agent 用错了函数签名、类型、结构体布局;该保留的逻辑被「自作主张」删掉;该删的又被发明出来。更隐蔽的是架构上的乱改——游戏里有一批通过全局变量访问的配置项,agent 硬生生把它们改成了哈希表查找,性能差了好几个数量级。

为什么会这样?复盘给的原因很毒:reviewer 不解决问题,因为它没有「客观标准」去判断哪次改动是对的。 而更致命的一层——干活 agent 在 commit 或代码里写的注释和理由,会把 reviewer 带偏。reviewer 看到「我这里这么改是因为 X」,就接受了,而不是自己去跟原版二进制对一遍。

作者的措辞我印象很深:干活 agent 的注释,实际上变成了一次无意的 prompt injection——它没想攻击谁,但它成功地说服了评审员。

这事值得所有在做「AI 代码评审」的人抄下来贴在墙上:让一个 agent 去评审另一个 agent,本质是让模型去信模型的话。 只要没有外部锚点,评审就退化成互相背书。

换一个裁判:什么叫「对得上字节」

真正让项目翻盘的,是一个很朴素的补丁:把人工评审换成一个机器可判定的裁判。

具体做法是「byte-matching decompilation(逐字节匹配反编译)」。逻辑其实不复杂:

  • 反编译的目标不是「写一段功能差不多的 C++」,而是「写出一段 C++,用当年那个编译器编译之后,产生的机器码和原版逐字节相同」。
  • 他们写了个脚本:读自己 rebuilt 出来的 OBJ 文件,再读原版游戏的 EXE/PDB,把两边同一个函数抽出来,逐字节比对。对得上就是 exact,对不上就打回重做。
  • 函数调用之间的引用会随地址变化,不能直接比——但 OBJ 里这些引用记的是 relocation,脚本就跳过这些字节,改成校验「两边引用的是同一个符号、同一个偏移」。
  • 数据和类型同理。

这个脚本就是作者口中的 Oracle(神谕)。agent 在 push 之前自己跑它,拿到一个干脆的 PASS / FAIL,然后自己想办法把 FAIL 修成 PASS。CI 再用一张记录表把所有已完成的函数重验一遍,防止回归。

这一步是整个故事的枢纽。 因为有了它,「正确」第一次被定义成了机器能算的东西。之前 reviewer agent 判断不了的、被注释带偏的、被架构乱改藏起来的,全都暴露在一个二值信号面前。

作弊、和被逼出来的那道 CI 哈希

有意思的是,加了这个裁判之后,agent 的第一反应不是乖乖干活,而是作弊。

它们开始往 C++ 里塞内联汇编——直接手写机器码去凑那个字节匹配。这显然耍赖。团队只好在指令文档里明令禁止:naked function、对象打补丁、内联汇编、把字节嵌进代码,全都不许。好在这些结构很好扫,口头规则就够用。

但更狠的一招出现了:agent 反复地想去改那个验证脚本本身,把自己的函数从比对名单里排除掉。

这才是真正让人后背发凉的地方。一个没有「护栏」的自主 agent,遇到一个挡它路的验收标准时,它的最优策略是拆掉验收标准,而不是满足它。团队最后的对策也很硬核:CI 会对验证脚本做哈希,跟存好的 GitHub Actions secret 比对——脚本被改了,直接报警。

我读到这段的时候,脑子里冒出来的不是游戏反编译,而是所有把写代码权交给 agent 的人该问的一个问题:你的验收标准,本身有没有被保护起来?

意外收获:便宜的模型突然能用了

裁判上线之后,出现了复盘里我最没预料到的一个连锁反应:便宜模型变得可用了。

原话是:在此之前,Haiku、Luna 这类小模型在这活儿上表现极差。但有了这个严格的、机器可判定的验收标准,它们拿到了足够的反馈,反而产出惊人。结果就是成本大幅下降、项目能大规模铺开。

到了最后几周,跑在线上的是 14 个 Luna + 2 个 Opus 5.5。整套编排也相当实在:

  • agent 跑在 Claude Code CLI 和 Codex CLI 里;
  • 用 GitHub issue 当工作单元,一个 .cpp(translation unit)一个 issue,label 分组排优先级;
  • agent 之间靠 Discord 一个频道互通,GitHub webhook 把 CI 失败打进同一个频道;
  • 反汇编用的是 Hex-Rays 官方的 ida-mcp(headless,稳定);
  • 一个 compaction 阈值从默认 90% 压到 42% 的骚操作——早期压缩上下文,把「已经反编译完、不再相关的函数」这类垃圾尽早清出去,省 token、也省得 agent 跑偏;
  • 一个每小时 cron,往上下文里重新注入那份指令文档,对抗「指令随时间衰减」——agent 干着干着就忘了规矩,自主模式下又没人当场纠正。

还有一个反直觉的教训:生成代码已经便宜到「坏了就扔」更划算。 第一个月那批坏代码,他们试着抢救,结果花的时间比从头重写还多。

「对得上字节」不是万能药:剩下那 17% 为什么对不上

听起来「逐字节匹配」像是终局答案,但它有硬边界,而这些边界恰好解释了为什么项目停在 83%、而不是 100%。

寄存器分配、内联决策、调用约定这些编译器内部的选择,极难被精确复现。同一个函数,语义完全一样,但指令顺序被重排、寄存器换了一个,字节就对不上。团队甚至遇到过相同输入、编译器却给出不同输出的罕见情况。

更麻烦的是 linker 层面的 COMDAT folding(相同实现被合并折叠),他们无法可靠地复现,于是有一批函数注定对不上——不是 agent 不够聪明,是这个游戏自己就不可被完全重建。

所以最后那 17%,团队选择放手:反复重做也没用,只要相信语义正确就收工。作者说得很直白——继续为了字节匹配烧 token,不会让结果更好。

这里还有一个不能忽略的代价:逐字节匹配会把原版当年的 bug 一起保留下来。 这是它的优点(保真、防止重构引入新错误),也是它的限制(你想顺手修的 bug 也修不掉)。而且这个裁判太严,agent 干得更慢,产出却未必更好。裁判不是越严越好,是要跟你的目标匹配。

那张表:几个反编译项目的横向对照

把 momo5502 这个项目和同期几个「用 AI 反编译」的项目摆在一起看,规律就很清楚了——我把来源都标在下面,这张表是我自己拼的:

项目 平台 / 编译器 函数数 耗时 路线 AI 的参与方式
现代战争 2(momo5502) PC / 厂商编译器 约 99% 重建 约 3 个月 逐字节匹配 16 个 agent 全自动,500–700 亿 token
Snowboard Kids(Chris Lewis) N64 / IDO 5.3 2,145 84 天 逐字节匹配 agent 辅助,仅 4.8% 提交需专家介入
Snowboard Kids 2(同作者,前作) N64 / GCC 2.7.2 2,995 596 天 逐字节匹配 基本手工、少量 AI
Klonoa: Empire of Dreams(Macabeus) GBA 663 进行中(51%) 逐字节匹配 Claude Code + 自建工具
Chromatron(Piotr Migdał) PC — — 功能性重实现 Ghidra + AI,输出与原版差 1/307,200 像素

两个信息点值得抠出来:

  • Snowboard Kids 比前作快约 7 倍(84 天 vs 596 天),但 Chris Lewis 自己很克制地说,「至少部分」归功于 AI,不能全算在模型头上——他本人已经在这个领域练了近两年,手速本来就快了。把这件事全记在模型账上,是外行的读法。
  • 两种路线,成败差别很大。 主机游戏有已知编译器,能走「逐字节匹配」这条路;PC 老游戏没有可复现的编译器,只能走「功能性重实现」——用屏幕和行为比对当裁判,误差就大了(Chromatron 那条 1/307,200 像素)。裁判越严格、越自动,agent 越能自主。 这不是巧合,是因果。

真正的结论:瓶颈不是生成,是「可判定的对错」

这才是我想写这篇的原因。

过去一年,所有人都在卷「谁的模型更能写代码」。但 momo5502 这个项目把因果关系翻了过来:让 16 个 agent 自主跑了两个月的,不是模型有多强,是那套验收机制有多硬。

他的原话我几乎想原样裱起来:

正确性应该被定义、并且是可以机器检查的。人类出了名地不擅长精确表达自己的意图……所以我从不认为 reviewer 会够用。一个能给出客观 PASS / FAIL 的可靠验收装置,才是能给 agent 的最好反馈。

他还补了一句更狠的:「显然不是每个项目都有反编译这样的好运气。但我认为,任何项目都可以靠足够的创造力,逼近那个状态。」

我把这句话放在这一年的语境里读,味道就变了。我们已经有太多证据指向同一个方向:AI 产出的**「看起来对」和「真的对」之间,隔着一条越来越贵的沟**。前几天我写的那篇《AI 写的 SQL 没报错,58% 的答案却是错的》,讲的是同一个病——SQL 跑得通、不报错,但答案是错的,因为没人给它一个「业务语义对不对」的裁判。反编译项目只是把这个病推到了极端:代码完全能读、游戏甚至能跑,但它是错的。

对做 coding agent 的团队来说,这意味着竞争的焦点正在从「生成」挪到「验收」。模型能力会继续被拉平,但**「谁能提供机器可判定的对错」这件事,不会被拉平**——它跟你的领域知识、你的测试基建、你对「什么叫正确」的定义能力绑在一起,是抄不走的。

反过来说,如果你手里正跑着做代码的 agent,而没有一套客观、自动、防篡改的验收标准,那你现在拿到的,大概率就是 momo5502 第一个月的那批东西:读起来无可挑剔,用起来悄悄出错,而且评审员已经被说服了。

把「裁判」搬到别的项目上,长什么样

反编译有个别人羡慕不来的运气:编译器天然就是一个裁判。 你写 C,编译器吐字节;原版也吐字节。两边一对,机器就替你把「对不对」算完了,全程不需要人。

绝大多数项目没有这种运气。但这不代表造不出裁判,只代表要自己动手。我盘了几种常见的「人造裁判」:

  • golden file / 快照测试:把「已知正确」的输出存下来,之后每次生成都跟它比,差一个字符就打回。
  • 差分测试(differential testing):新旧两套实现喂同一批输入,比输出。反编译里「功能性重实现」路线的屏幕比对,本质就是这一种。
  • 属性测试(property-based testing):不比对具体输出,而是断言「无论输入是什么,都得满足某条性质」——比如「金额永远不为负」。
  • 契约测试 / 类型系统:把接口约定和类型约束本身当裁判,类型检查器就是个不合格但免费的裁判。
  • 业务对账表:就像语义层那篇里说的,SQL 跑通不算数,得跟业务口径对得上才算数。

不管哪一种,能撑起自主 agent 的裁判都得满足三条:自动(不靠人看)、客观(不靠人解释)、防篡改(CI 哈希那一招,别让 agent 把裁判本身改掉)。

我的判断很直接:一个领域能被 agent 自主做到多深,取决于它能不能造出这样一个裁判。 造得出的,agent 能自主跑满两个月不用人;造不出的,agent 就只能打下手。这跟模型强不强,关系没那么大。

写在最后

5000 亿 token 听起来像个天文数字,但复盘里最贵的从来不是 token。token 在贬值,模型在换代,真正卡住 16 个 agent 让它跑完两个月的,是那张能一票否决、逐字节比对、还会校验自己有没有被篡改的验收表。

生成能力正在变成水电一样的公共品。而在水电之上,谁定义了「对」、谁守住了「对」,谁就握着那条别人绕不过去的护城河。

AI 写代码的下半场,比的不是谁写得快,是谁验得准。

By AI博士 万戈