商业产业 Hacker News AI 2026-10-11T02:02 正文 ok 2229 words

5000 亿 Token 之后:让 AI Agent 反编译一款第一人称射击游戏

英文标题:🔶 500B Tokens Later: Letting AI Agents Decompile a First-Person Shooter

作者与社区成员用 Claude 和 Codex 驱动的 AI Agent,在约三个月内尝试将一款热门第一人称射击游戏精确反编译为可编译的 C++。项目初期虽然实现了游戏启动、菜单渲染和地图加载,但 Agent 生成的代码存在大量语义、类型和架构错误。团队随后构建了基于 OBJ 与原始 EXE/PDB 字节比对的自动验证机制,使 Agent 能获得明确的 PASS/FAIL 反馈,最终实现 99% 函数重建、83% 函数字节级一致。作者认为,对自主 Agent 而言,可机器验证的正确性标准远比单纯提升吞吐量、减少 Token 消耗或扩大 Agent 数量更重要。
打开原文相关 3 · 机会 1
media61.6 KB media128.4 KB media147.0 KB media139.3 KB media69.1 KB media202.2 KB

中文

过去 3 个月里,我花了一部分时间和 Token 反编译一款热门的第一人称射击游戏。目标并不只是达到一个简单的概念验证状态;我们真正想要的是对这款游戏进行准确、稳定且功能完整的复刻。

我的博客忠实读者可能已经注意到,我此前写过两篇如今已被删除的文章。其他人现在可能会想知道,我说的究竟是哪款游戏。对于这两类人,我只能说:美国企业界来扫兴了。

不过,这没关系。本文并不是关于这款游戏,也不完全是关于反编译流程。它更多是在讨论 AI 编排,以及如何优化基础设施、配置和 Agent harness,以获得最佳结果。

该项目是在 RektInator、Future、st0rm 以及其他社区成员的帮助下完成的。非常感谢他们所有人。

我们的目标是什么?

我们的目标是将游戏准确地反编译为 C++。除显而易见的语义正确性之外,我们还有不少额外要求:我们希望得到可读、可编译的 C++ 源代码。考虑到游戏的年代较早,我们还希望修复安全问题和 Bug,并改进可移植性。最好能让游戏运行在 Linux、macOS、浏览器等平台上。

之后,我们暂缓了现代化和可移植性工作,转而完全聚焦于还原原始行为。

显然,总体目标是学习如何在数月时间跨度内,有效编排自主 AI Agent。

初始配置

我们起初使用 Claude Max(20x),之后加入 Codex Pro,并同时使用这两项订阅。模型选择变化很大。大多数时候我们使用 Sonnet 5,但也频繁使用 Opus 5.5、Luna、Sol 和 Terra。后文会进一步说明。

Claude Agent 运行在 Claude Code CLI 中,Codex Agent 运行在 Codex CLI 中。我们也尝试了其他 Agent harness,但选择几乎没有影响,因此最终沿用了默认方案。

进度跟踪

Agent 使用 GitHub CLI 管理 GitHub issue 来跟踪进度。每个翻译单元(.cpp 文件)对应一个 issue。此外,标签用于对 issue 进行分组和确定优先级。

通信

Agent 通过 Discord 通信。所有 Agent 都能访问同一个频道,并且都可以在其中发送和读取全部消息。

Discord 支持 Agent 与 Agent 之间的通信,也支持人类与 Agent 之间的通信。因此,其他参与者无需访问机器,也能与它们交流。

一个 GitHub webhook 会将 CI 失败信息推送到共享频道,因此 Agent 能在出现问题时收到通知。

反汇编与反编译

Agent 几乎全程都在使用 Hex-Rays 官方的 ida-mcp。它的表现很好:极其稳定,支持无头运行,并且具备该项目所需的一切功能。我只能推荐它。

第一个月

当时我们运行了 4 个 Agent。

其中 3 个是负责反编译和提交代码的工作 Agent,另有 1 个审查 Agent,负责被动协调并审查提交,以标记 Bug。

Agent 成功反编译了大约 80% 的游戏,且取得了可见进展:游戏能够启动、主菜单能够显示,我们也可以加载地图。

在那 4 周中,我们花了大量时间优化配置:

我们通过更早触发上下文压缩来降低 Token 消耗。
默认的压缩阈值是上下文填充率达到 90%。我们将其降至 42%。反编译涉及大量易失性信息:一个已经完成反编译的函数不再重要,可以从上下文中移除。因此,更早压缩有助于从上下文中清除这类“垃圾”。

我们还注意到,Agent 往往会随着时间推移失去专注。即使是在单个压缩周期内,随着上下文容纳的数据越来越多,Agent 也可能偏离任务并失去焦点。

Agent 有时会在完成前一个函数之前转向另一个函数。它们还会在监视 CI 时开始空转,尽管 Discord 已经会推送失败通知。偶尔,它们会在没有彻底确认工作是否真正完成的情况下关闭 issue。

如果在终端中并排工作,你可以引导 Agent 来避免这些问题;但让它们自主工作时,就无法进行这种实时引导。

为解决这个问题,我们编写了一份文档,定义项目目标、Agent 的工作方式、它们必须避免的事项,以及特定情况的处理方法。

一个每小时运行的 cron 任务会自动注入请求,要求 Agent 重新阅读这份文档,以让指令在其上下文中保持新鲜。尽管这或许不是让 Agent 保持专注的理想方法,但它在项目结束前始终非常有效。

我们投入了大量时间来完善指令文档。不过,由于内容与该项目高度相关,在这里分享意义不大。

不过……

……尽管我们付出了大量努力来优化配置和基础设施,仍然需要谈谈工作成果的质量。

持续的进展——游戏启动、菜单渲染、地图加载——让我们相信反编译质量很高。

事实并非如此。尽管代码极其可读,但其语义是错误的。

Agent 使用了错误的函数签名、类型或结构体布局。它们会凭空发明逻辑,或在认为某些逻辑不必要时将其删除。

除了语义错误之外,Agent 还引入了不必要的架构改动。例如,游戏拥有某些通过全局变量访问的配置变量,Agent 却将这种固定内存访问改造成了哈希表查询,其开销高出多个数量级。

而这只是众多出错之处中的一个例子。

为什么会这样?

虽然审查 Agent 有助于捕捉 Bug,但它无法有效处理除此之外的问题。只要架构决策看起来符合目标,它们就不会受到质疑。

主要原因在于,我们没有客观的验收标准。我们从未恰当地定义“正确性”。因此,审查 Agent 很难判断哪些改动是正确的,哪些是错误的。

显然,游戏本身可以作为参照,但由于我们也将现代化和可移植性列入目标,某些偏离原作的行为并未被视作 Bug。
有趣的是,提交说明或代码注释会让审查 Agent 接受这些偏离,因为其中包含了工作 Agent 写下的某种理由。工作 Agent 的注释实际上构成了无意的提示注入:审查 Agent 接受了它们的理由,而不是独立地将偏离之处与原作进行比对。

Oracle

我们需要的是一种自动化检查机制,能够告诉 Agent 重建出的函数是否与原始函数匹配。它应该验证语义是否一致。一个简单的 PASS 或 FAIL 信号就足够了,Agent 可以自行找出问题所在。

字节匹配反编译

实现这一目标最简单的方法,是进行字节匹配反编译。

我们切换到用于构建原始游戏的编译器,并编写了一个执行比对的脚本。

该脚本会读取我们重建出的 OBJ 文件,以及游戏的 EXE/PDB 文件(拥有 PDB 非常好,能让过程略微简单一些,但即使没有 PDB,这一流程同样可以正常工作)。

随后,它会从 OBJ 和 EXE 中提取函数数据,并比较全部字节。如果二者一致,函数便是精确匹配;否则验证失败,Agent 需要重新处理该函数。

对其他函数或数据的引用不一定能逐字节匹配,因为其编码值取决于目标在编译后二进制文件中的最终位置。

幸运的是,OBJ 文件会将这些引用记录为重定位信息。我们可以从直接比较中排除重定位字节,转而验证两个版本是否以相同偏移引用了同一个符号。

脚本随后会以同样方式处理数据和类型。

这样,Agent 就可以在推送代码前使用这个脚本验证自己的工作。

重建出的函数会被记录在一组文本文件中。CI 可以利用这些文本文件验证所有已记录函数,并在发生回归时发出告警。

作弊

我们引入这一脚本后,Agent 做的第一件事就是编写内联汇编。

这显然违背了初衷。因此,我们不得不完善指令,以禁止某些构造。我们以文字规则禁止了裸函数、对象补丁、内联汇编以及在代码中嵌入字节。由于扫描这些构造非常容易,文字规则已经足够。

然而,Agent 反复尝试修改这个脚本,以将自己的函数排除在比对之外。

为了阻止它们这样做,CI 会对验证脚本进行哈希计算,并将结果与存储在 GitHub Actions secret 中的值进行比对。

权衡

缺点

函数可能很难做到匹配。寄存器选择、内联决策和调用约定都可能难以精确复现。在少数情况下,我们还观察到,相同输入会产生不同的编译器输出。如今 Agent 需要更长时间完成反编译,却不一定能产出更好的结果。某个函数即使存在特定差异,例如独立指令的顺序被调换,语义依然可能完全一致。

优点

匹配成功的函数可以保证具有相同语义。这保留了原始行为,包括其中已有的 Bug,并避免这些函数出现重建错误。此外,审查 Agent 也不再需要。

另一个好处是,成本更低、能力较弱的模型如今也能可靠地完成任务。此前,Haiku 或 Luna 等模型并不适合这项工作,产出的结果极差。但在这种严格的验收标准下,它们如今能获得足够的反馈,产出令人难以置信的结果,从而大幅降低成本,并使项目能够大规模扩展。

最终状态

借助新的验证 harness,Agent 又工作了将近 2 个月。游戏中 99% 的函数已存在于我们重建的源代码中,其中全部函数的 83% 达到了字节级精确匹配。

在最后几周中,我们大部分时间使用了 14 个 Luna Agent 和 2 个 Opus 5.5 Agent。

在这一规模下,工作 Agent 使用独立分支,并通过 pull request 提交改动。

这也是 Discord 开始无法良好扩展的阶段。如此多的 Agent 在频道里刷屏毫无意义。我们将消息限制为它们认领了哪些 issue,以及 CI 协调信息。这将通信维持在最低水平。不过,随着项目推进,我们越来越不需要与 Agent 交流。人类与 Agent 的通信已不再必要,因为它们可以完全自主地工作。对于其他同等规模的项目,我可能会选择 Discord 以外的工具。

此时,我们开始遭遇收益递减。剩余函数大多具有某些非确定性特征,或者因其他原因无法匹配,例如链接器中的相同 COMDAT 折叠,而我们无法可靠地复现这种行为。

Opus 5.5 Agent 仍然能够继续推进,并使剩余函数实现匹配。不过,游戏现在已经可以完美运行。没有明显的 Bug,原始游戏的所有功能都已具备。

剩余函数已经被反复重写。虽然它们仍未实现逐字节匹配,但我们相信其语义是正确的。

继续针对这些函数进行字节匹配会消耗更多 Token,却无法有意义地改善结果。这意味着项目现在可以被视为完成。

经验

这个项目让我们学到了很多。以下是最有价值的经验总结:

  • 精确的指令是必要的。如果任务留下解释空间,Agent 会有作弊的倾向。

  • 必须定义正确性,并让其能够被机器检验。人类出了名地无法精确表达自己的意图。因此,我认为审查者永远不会足够。能够提供客观 PASS 或 FAIL 信号的可靠验证 harness,是 Agent 所能获得的最佳反馈。显然,不是每个项目都像反编译一样具备这种条件。但我认为,只要有足够的创造力,每个项目都可以尽可能接近这一点。

  • 指令会随时间衰减。随着时间流逝和上下文不断被压缩,Agent 会忘记某些规则,或认为某些规则不再那么重要。在交互式工作中,你可以在偏移发生时进行纠正。但当 Agent 自主工作时,这种偏移可能不会被察觉,并会损害工作质量。每小时刷新一次指令解决了这个问题。

  • 如今生成代码很便宜。如果代码不好,就扔掉它。在最初 4 周后,当我们意识到代码质量很差时,我们曾尝试挽救它。事实证明,这比从头开始花费了更多时间。

  • 正确性远比生产率重要。减少 Token 消耗、扩大 Agent 规模、优化 Agent 吞吐量等固然都很好,但如果结果很差,它们就没有太大帮助。

最后的话

这确实是一个非常有价值的项目,我从中学到了很多。我不认为仅仅写下这些经验,就能传达我们在整个过程中获得的庞大收获。

很多事情出了错,但也有很多事情做对了。随着 AI Agent 承担越来越多的软件开发工作,如何编排它们正成为人类一项新的、要求高得多的任务。

扩展到 15 个以上 Agent 后,又暴露出更多困难。由于格式错误的命令,Agent 会周期性地清空 VM。Windows 已经有沙箱解决方案,但我目前看到的方案都不符合我的需求。我已经开始扩展我的用户态模拟器 Sogen,以提供轻量、可扩展的沙箱能力。不过,距离这项工作真正变得更

English / 原文

During the last 3 months, I spent some of my time and tokens decompiling a popular first-person shooter. The goal was not to reach a simple proof-of-concept state. Instead, we really wanted an accurate, stable and feature-complete recreation of the game.

The avid reader of my blog might have noticed that I had previously written two posts that have since been removed. Everyone else might now be wondering which game I am talking about. To both of you I can only say that corporate America was here to ruin our fun.

However, that’s fine. This post is not about the game, it’s also less about the process of decompilation. It’s more about AI orchestration and how to optimize infrastructure, setup and harness for optimal results.

This project was done with the help of RektInator, Future, st0rm and other members of the community. A big thank you to all of them.

What Was Our Goal?#

We aimed at an accurate decompilation of the game to C++. Besides obvious semantic correctness, we had quite a few more requirements: We wanted readable C++ source that compiles. Given how old the game is, we also wanted security and bug fixes, but also portability improvements. It would be nice to run the game on Linux, macOS, in the browser, …

We later deferred modernization and portability to focus entirely on reconstructing the original behavior.

Obviously, the overall goal was to learn how to effectively orchestrate autonomous AI agents over the course of months.

The Initial Setup#

We started with Claude Max (20x), then added Codex Pro and used both subscriptions simultaneously. Model choice varied a lot. We had been using Sonnet 5 most of the time, but Opus 5.5, Luna, Sol and Terra were also used a lot. More on that later.

Claude agents were running in Claude Code CLI, Codex agents in Codex CLI. We also tried other agent harnesses, but the choice barely mattered, so we stuck to the defaults.

Progress Tracking#

Using GitHub CLI, the agents manage GitHub issues to track their progress. There is one issue per translation unit (.cpp file). Additionally, labels help group and prioritize issues.

Communication#

Agents communicate via Discord. All of them have access to one channel and can both post and read all messages in there.

Discord allows agent-2-agent communication, as well as human-2-agent. So other participants can talk to them, without needing machine access.

A GitHub webhook posts CI failures into the shared channel, so agents get notified when something broke.

Disassembly & Decompilation#

Agents have been using the official ida-mcp by Hex-Rays almost the entire time. It works great. It’s super stable, it’s headless and supports everything needed for this project. I can only recommend it.

The First Month#

We had 4 agents running at that time.

3 worker agents decompiling and committing and one reviewer agent that passively coordinates and reviews commits to flag bugs.

Agents managed to decompile about 80% of the game and visible progress was made. The game launched, the main menu was visible and we were able to load maps.

We spent a lot of those 4 weeks optimizing our setup:

We reduced token consumption by triggering earlier compactions.
The default compaction threshold is 90% context fill. We reduced it down to 42%. Decompilation consists of a lot of volatile information: A function that was decompiled is now irrelevant and could be removed from the context. So earlier compactions help remove such “junk” from the context.

We also noticed that agents tend to lose focus over time. Even within the span of one compaction cycle, agents can drift and lose focus, the more data the context holds.

Agents sometimes moved to another function before finishing the previous one. They also started idling while watching CI, despite receiving failure notifications on Discord. Occasionally, they closed issues without thoroughly checking whether the work was actually complete.

Working side by side in a terminal, you can steer the agents to prevent that, but when letting them work autonomously, steering is not possible.

To prevent that, we wrote a document defining our goal, how agents should work, what they must avoid and how to handle specific situations.

An hourly cron job automatically injected a request for agents to reread this document, keeping the instructions fresh in their context. While this might not be the ideal method to keep agents focused, it worked really well through the end of the project.

Lots of time was spent refining the instruction document. As the content is extremely specific to this project, it makes less sense to share it here, though.

However…#

… despite all our efforts optimizing setup and infrastructure, we need to talk about the quality of the work.

Constant progress (game starting, menu rendering, maps loading) led us to believe the quality of the decompilation was great.

It was not. Despite the code being extremely readable, it was semantically wrong.

Agents used wrong function signatures, types or struct layouts. They invented logic or removed it where deemed unnecessary.

Beyond semantic errors, agents also introduced unnecessary architectural changes. As an example, the game has certain configuration variables that it accesses via global variables. The agents had turned this constant memory access into hash tables with a lookup that was orders of magnitude more expensive.

And that’s just one example of the many things that went wrong.

Why is that?#

While a reviewer helps catch bugs, it does not work well for anything beyond that. Architectural decisions were not questioned, as long as they aligned with the goal.

The main reason for that is that we didn’t have objective acceptance criteria. We never properly defined “correctness”. Therefore it was hard for the reviewer to judge which change is correct and what’s wrong.

Obviously it had the game as reference, but given that we had modernization and portability on the list as well, certain deviations were not treated as bugs.
Interestingly, comments in the commits or code led the reviewer to accept deviations, due to whatever justification the worker agent had written down. The workers’ comments effectively acted as unintentional prompt injection: the reviewer accepted their justifications instead of independently checking the deviations against the original.

The Oracle#

What we needed was an automated check that tells agents whether a reconstructed function matches the original. It should verify identical semantics. A simple PASS or FAIL signal would be enough and the agent can figure out on its own what’s wrong.

Byte Matching Decompilation#

The simplest way to achieve this was byte matching decompilation.

We switched to the compiler used to build the original game and wrote a script that performs the comparison.

The script reads our reconstructed OBJ file and the game EXE/PDB (having the PDB is great and makes things slightly simpler, but the process can work just as well without a PDB).

It then extracts the function data from OBJ and EXE and compares all bytes. If they match, the function is exact, otherwise it fails and the agent needs to rework the function.

References to other functions or data won’t necessarily match byte for byte, because their encoded values depend on where the targets end up in the compiled binary.

Luckily, the OBJ file records these references as relocations. We can exclude the relocation bytes from the direct comparison and instead verify that both versions reference the same symbol with the same offset.

The script then does the same for data and types.

Agents can then use this script to verify their work, before pushing it.

Reconstructed functions are recorded in a set of text files. CI can then use these text files to verify all recorded functions and alert in case of regressions.

Cheating#

The first thing agents did when we introduced this script was write inline assembly.

This obviously defeats the purpose. So we had to refine our instructions to disallow certain constructs. Naked functions, object patching, inline assembly and embedding bytes in the code were forbidden verbally. Given how easy it is to scan for those constructs, verbal rules were enough.

However, agents repeatedly tried to modify this script to exclude their function from comparison.

To prevent them from doing this, CI hashes the verification script and compares it against a stored GitHub Actions secret.

Trade-Offs#

Cons#

Functions can be hard to match. Register selection, inlining decisions and calling conventions can be difficult to reproduce exactly. In rare cases, we also observed different compiler output from identical inputs. Agents now need much longer for decompilation, without necessarily producing better results. A function can have identical semantics, despite showing certain divergences, e.g. independent instructions that are shuffled.

Pros#

Matching functions are guaranteed to have identical semantics. This preserves the original behavior, including any existing bugs, and prevents reconstruction errors in those functions. On top of this, the reviewer agent is not needed anymore.

Another benefit is that cheaper, less capable models can now reliably work on the task. Previously models like Haiku or Luna were a bad fit and produced extremely bad results. However, given this strict acceptance criterion, they now have enough feedback to produce incredible results, reducing the costs drastically and allowing the project to scale up massively.

Final State#

With our new verification harness, agents have been working for almost 2 more months now. 99% of the game’s functions are present in our reconstructed source, with 83% of all functions being byte exact.

For the most part, we have been using 14 Luna and 2 Opus 5.5 agents throughout the final weeks.

At that scale, workers used separate branches and submitted their changes through pull requests.

This is also where Discord stopped scaling well. So many agents spamming the channel is nonsense. We restricted messages to which issues they were taking and CI coordination. That kept communication to a minimum. However, the further we got into the project, the less we needed to talk to the agents. Human-2-agent communication was no longer needed as they worked fully autonomously. For other projects at that scale, I would likely choose something other than Discord.

At this point, we’re hitting diminishing returns. The remaining functions mostly have certain non-deterministic characteristics or can’t be matched due to other circumstances, e.g. identical COMDAT folding in the linker that we cannot reliably reproduce.

The Opus 5.5 agents are still able to move forward and get the remaining functions matched. However, the game runs flawlessly now. There are no noticeable bugs and all features of the original game are present.

The remaining functions have been reworked repeatedly. While they still don’t match byte for byte, we believe their semantics are correct.

Continuing the byte matching process for them would consume more tokens without meaningfully improving the result. That means, the project can be considered done now.

Lessons#

This project has taught us a lot. Here are the most valuable takeaways at a glance:

  • Precise instructions are necessary. Agents have the desire to cheat if the assignment leaves room for interpretation.

  • Correctness should be defined and machine-checkable. Humans are notoriously incapable of precisely articulating their intent. Therefore, I don’t think reviewers will ever be enough. A reliable verification harness that delivers an objective PASS or FAIL signal is the best feedback an agent can get. Obviously not every project has the luxury decompilation has. Yet, I think for every project it is possible to get close to that, with enough creativity.

  • Instructions decay over time. Agents forget or treat certain rules as less important, the more time passes and the more context gets compacted. During interactive work, you can correct that drift as it happens. When agents work autonomously, it can go unnoticed and undermine the quality of their work. The hourly refresh of the instructions solved that.

  • Generating code is cheap now. Throw it away if it’s bad. After the first 4 weeks, when we noticed the code was bad, we tried to salvage it. This did in fact cost us more time than starting from scratch.

  • Correctness is so much more important than productivity. Reducing token consumption, scaling up agents, optimizing agent throughput, etc. is great and all, but if the result is bad, it doesn’t help much.

Final Words#

This was honestly such a valuable project and I learned so much. I don’t think writing down these takeaways can convey the sheer amount of lessons we’ve learned throughout the process.

So many things went wrong, yet so much went right. As AI agents take on more software development work, orchestrating them becomes a new, much more demanding task for humans.

Scaling up to 15+ agents revealed even more difficulties. Agents periodically wiped the VM because of malformed commands. There are already sandboxing solutions for Windows, yet none of what I have seen currently fits my needs. I have started extending Sogen, my userspace emulator, to provide lightweight and scalable sandboxing capabilities. However, it will take a while before this becomes even remotely production ready.

Given that the VMs were wiped, we unfortunately lost a bunch of session logs. So I don’t know the exact number of tokens that were spent. My estimate is something between 600-700 billion tokens.

For obvious reasons, none of the code will be shared. Everything will stay private and I will use it for my own needs only.