PS2Recomp:把 PS2 二进制静态重编译成 C++,但别指望开箱即玩
该项目围绕「ran-j/PS2Recomp」构建,面向真实业务场景提供可复用的开源实践方案,支持稳定落地与可扩展的项目实践。
秒懂
- 它是什么?
- PS2Recomp 是一套把 PS2 ELF 静态翻译为 C++ 并附带运行时的工具链。它面向愿意深入调试和补桩的移植开发者,离一键原生 PC 版还有明显距离。
- 适合谁用?
- 适合的对象是:已经熟悉 Ghidra、能接受逐函数手工补桩、愿意读生成的 C++ 代码并调试运行时缺失的移植开发者。不适合的对象是:期待一条命令把 ISO 变成可玩原生版的玩家,或者没有逆向经验、只想快速跑通 Demo 的团队。
- 能商用吗?
- 可以,但有条件。GPL-3.0 是 copyleft 许可证:如果你分发包含它的软件,就必须以同一许可证公开该软件的源代码。只在内部运行、不对外分发,则不会触发这项义务。
- 还在维护吗?
- 在维护。仓库最近一次提交在 2 天前。
- 用什么语言写的?
- 主要是 C++(依据 GitHub 的语言统计)。
以上回答依据项目的 GitHub 数据(最近同步于 2026年9月15日)和我们的分析,不构成法律意见。
开源项目深度解析
它解决的不是模拟,而是翻译
PS2Recomp 的路线和模拟器完全不同。模拟器在运行时解释或动态编译 MIPS 指令,而 PS2Recomp 在编译期把 R5900 指令逐条翻译成 C++。README 给出的例子很直白:addiu $r4, $r4, 0x20 变成 ctx->r4 = ADD32(ctx->r4, 0X20);。这意味着生成的代码是静态的、可读的、可以像普通 C++ 工程一样编译链接。目标用户是那些想给 PS2 游戏做原生 PC 移植的人,他们需要的是能长期维护的代码库,而不是一个黑盒模拟层。这个思路的好处是,翻译后的代码可以直接用现代调试器、性能分析器和编译器优化。代价是,所有 PS2 硬件行为都必须由运行时来模拟,而运行时目前只是雏形。
三条流水线:分析、重编译、运行时
项目拆成三个模块,职责分得很清楚。ps2xAnalyzer 扫描 ELF 和函数,输出 TOML 配置,里面包含 stubs、skip 和指令补丁。ps2xRecomp 读取 TOML 和 ELF,解码 R5900 指令,生成 C++ 文件。ps2xRuntime 负责运行时的内存模型、函数注册、syscall 分发和硬件桩。另外还有一个 ps2xIOP 模块,提供可移植的、实例持有的 IOP HLE 服务,支持游戏配置文件和 C 插件 ABI。数据流是:ELF 加配置进重编译器,出 C++ 代码,再链接运行时。配置是中间的关键产物,它决定了哪些函数被翻译、哪些被跳过、哪些被绑定到已知 handler。
Ghidra 是推荐路径,原生分析器是备胎
README 明确区分了两条工作流。首选路径是在 Ghidra 里打开 ELF,运行 ExportPS2Functions.java 脚本,导出 TOML 和 CSV 函数映射,然后用导出的 TOML 调用 ps2_recomp config.toml。备选路径是直接跑 ./ps2_analyzer your_game.elf config.toml,但文档警告说,这个原生分析器在零售版剥离符号的游戏上准确度较差,更容易漏掉内部可调用入口点。这个取舍很实际:Ghidra 的函数边界分析比一个快速扫描器可靠得多,但需要人工介入。对于带调试符号的 ELF,原生分析器可以快速起步,但 README 建议只用于临时实验。这个项目把 Ghidra 当作一等公民,而不是可选附件,这是它和很多重编译器不同的地方。
stub 与地址绑定:处理剥离符号的关键机制
零售版游戏通常没有符号表,函数名是未知的。PS2Recomp 用两种方式处理。第一种是 stubs 条目,它生成包装函数,调用已知的运行时 syscall/stub handler。第二种是地址绑定,格式是 handler@0xADDRESS,例如 sceCdRead@0x00123456,直接把一个剥离函数的起始地址映射到运行时 handler。还有三个临时返回 handler:ret0、ret1、reta0,用于快速 triage,让某个地址直接返回固定值。文档特别强调,地址必须精确匹配该 ELF 构建中的函数起始位置,而且不同游戏、区域、构建之间不通用。另外,重编译器现在会在 J/JAL 调用点尝试基于重定位符号的自动绑定,如果符号已知,比如 sceCdRead,就可以直接调用运行时 handler,不需要手工映射地址。这个自动绑定机制减少了手工配置量,但并不能覆盖所有情况。
配置项里藏着内存和并发的实际考量
config.toml 的主字段暴露了工程上的真实约束。general.low_memory_mode 通过避免在内存中保留反汇编字符串、强制串行输出生成来降低峰值内存,代价是生成速度变慢。general.output_worker_threads 控制输出生成的工作线程数,0 表示用 nproc - 1,1 强制串行,正值则精确使用该数量。这些选项说明,大型 ELF 重编译时内存和 CPU 占用是实际瓶颈,不是理论问题。还有一个值得注意的字段:general.patch_syscalls,README 直接建议设为 false。这意味着默认情况下 SYSCALL 指令不会被补丁,而是走运行时分发。运行时先尝试编码的 syscall ID,失败则回退到 $v1 寄存器。这种回退逻辑说明 syscall 识别并不完全可靠,依赖具体游戏怎么设置寄存器。
运行时和 IOP:最薄弱的环节
ps2xRuntime 目前提供的功能清单很短:访客内存模型、函数分发表、一些常见内核 ID 的 syscall 分发器、基础的 GS/VU/file/system 桩。README 的措辞是 foundation to expand and port your game,翻译过来就是,剩下的全靠你。ps2xIOP 提供配置文件选择和可选的 .dll/.so 插件发现机制,用于游戏特定的 IOP HLE。游戏覆盖钩子(game overrides)是运行时侧的、构建范围的补丁模块,在 loadELF 期间运行,可以按地址替换特定游戏构建的 EE 函数绑定。IOP RPC/DMA 行为则属于 ps2xIOP profile,不放在 game override 里。这个分层是合理的,但也意味着一个游戏要跑起来,可能需要同时写重编译配置、IOP profile 和 game override 三处代码。
限制与替代方案的现实对比
最明显的限制是,这个项目明确标注 experimental,而且运行时只覆盖了 PS2 硬件的一小部分。GS 只有基础桩,VU 只有基础桩,没有提到 SPU、DMA 通道的完整实现。syscall 分发器只覆盖 common kernel IDs,游戏特有的 syscall 需要自己写 handler 并注册到 PS2_SYSCALL_LIST。另一个限制是,翻译后的代码非常字面化,每条 MIPS 指令对应一段 C++ 操作,这意味着生成的代码量巨大,而且优化空间有限,编译器可能无法还原原始代码的意图。替代方案是 PCSX2 这样的动态重编译器,它在运行时翻译并缓存代码块,不需要静态分析函数边界,但生成的是机器码缓存,不是可维护的 C++ 源码。另一个思路是手动重写游戏逻辑,就像 OpenGOAL 项目那样,但那需要完全理解游戏代码,工作量完全不同。PS2Recomp 的定位是中间路线:保留原生代码的逻辑结构,但把它变成现代工具链可以处理的 C++。
维护成本与许可证边界
项目使用 GPL-3.0,这意味着如果你分发基于生成代码构建的移植版,整个衍生作品可能都要以 GPL-3.0 发布。这不是法律建议,但任何商业移植项目在采用前都应该咨询律师。维护成本方面,重编译器本身迭代很快,v0.2 到 v0.4 只隔了不到一个月,这意味着配置格式和运行时 API 可能还在变动。README 没有提供迁移指南,也没有说明旧配置是否兼容新版本。对于长期项目,你需要锁定一个版本,或者准备跟随上游改动。另外,构建要求是 CMake 3.20+ 和 C++20 编译器,目前主要在 MSVC 上测试,Linux 和 macOS 的支持情况未在文档中明确。SSE4/AVX 是部分向量路径的硬性要求,没有这些指令集的主机无法编译所有代码。
编辑结论
适合的对象是:已经熟悉 Ghidra、能接受逐函数手工补桩、愿意读生成的 C++ 代码并调试运行时缺失的移植开发者。不适合的对象是:期待一条命令把 ISO 变成可玩原生版的玩家,或者没有逆向经验、只想快速跑通 Demo 的团队。采用前必须先验证三件事:一是确认你的 ELF 是零售版还是带调试符号,零售版必须走 Ghidra 导出流程,ps2xAnalyzer 的自动扫描会漏内部入口;二是核对目标游戏的 syscall 和 stub 名称是否已在 PS2_SYSCALL_LIST 或 PS2_STUB_LIST 中,不在列表里的 handler 绑定会直接失败;三是确认你的主机支持 SSE4/AVX,否则部分向量路径无法编译。这个项目目前明确标注为 experimental,v0.4 是 2026 年 4 月的版本,运行时只提供基础 GS/VU/file 桩,游戏能跑到什么程度完全取决于你愿意补多少代码。
社区笔记