ran-j/PS2Recomp
GitHub: ran-j/PS2Recomp
将 PlayStation 2 的 MIPS R5900 指令集静态重编译为 C++ 代码,配合运行时环境实现 PS2 游戏的 PC 原生移植。
Stars: 2837 | Forks: 85
## PS2Recomp: PlayStation 2 静态重编译器 (实验性)
[](https://discord.gg/JQ8mawxUEf)
也可以查看我们的 [WIKI](https://github.com/ran-j/PS2Recomp/wiki)
该项目将 PS2 ELF 二进制文件静态重编译为 C++,并提供了一个运行时来执行生成的代码。
### 模块
* `ps2xAnalyzer`:扫描 ELF/函数并写入 TOML 配置(`stubs`、`skip`、指令补丁)。
* `ps2xRecomp`:读取 TOML + ELF,解码 R5900 指令,并生成 C++ 输出。
* `ps2xRuntime`:托管内存、函数注册、syscall 分发和硬件存根。
### 特性
* 将 MIPS R5900 指令翻译为 C++ 代码
* 支持 PS2 特定的 MMI 和 VU0 宏。
* 单文件或多文件输出。
* 可配置的 stubs、skips 和指令补丁。
* 基于指令的 syscall 处理。
### 工作原理
PS2Recomp 的工作流程如下:
* 解析 PS2 ELF 文件以提取函数、符号和重定位信息
* 解码每个函数中的 MIPS R5900 指令
* 将这些指令翻译为等效的 C++ 代码
* 生成可以执行重编译代码的运行时
翻译后的代码非常直观,每条 MIPS 指令映射到一个 C++ 操作。例如,`addiu $r4, $r4, 0x20` 变为 `ctx->r4 = ADD32(ctx->r4, 0X20);`。
### 当前行为
* `stubs` 条目生成包装器,通过名称调用已知的运行时 syscall/stub 处理程序。
* `stubs` 还支持使用 `handler@0xADDRESS` 进行地址绑定,用于已剥离符号的游戏(例如 `sceCdRead@0x00123456`)。
* 地址绑定还支持用于分类的通用返回处理程序:`ret0`、`ret1`、`reta0`。
* 重编译器现在尝试在调用点(`J/JAL`)进行重定位符号自动绑定,然后再进行原始地址分发;当重定位符号已知(例如 `sceCdRead`)时,它可以在没有手动地址映射的情况下调用运行时处理程序。
* 重编译器发现额外的内部静态入口目标,并为这些地址生成 `entry_...` 包装器。
* 对于未解析的静态 `J/JAL` 调用点,生成的代码回退到 `runtime->lookupFunction(0x...)`。
* `skip` 条目不会被重编译,而是生成显式的 `ps2_stubs::TODO_NAMED(...)` 包装器。
* 重编译后的 `SYSCALL` 现在调用 `runtime->handleSyscall(...)` 并传入编码后的 syscall 立即数。
* 运行时 syscall 分发首先尝试编码后的 syscall ID,然后回退到 `$v1`。
### 环境要求
* CMake 3.20+
* C++20 编译器(目前主要使用 MSVC 测试)
* 主机支持 SSE4/AVX 以用于某些向量路径
### 构建
```
git clone --recurse-submodules https://github.com/ran-j/PS2Recomp.git
cd PS2Recomp
cmake -S . -B out/build
cmake --build out/build --config Debug
```
### 使用方法
针对零售版或已剥离符号游戏的推荐工作流程:
1. 在 Ghidra 中打开 ELF。
2. 运行 `ps2xRecomp/tools/ghidra/ExportPS2Functions.java`。
3. 使用导出的 TOML 和 CSV 映射。
4. 使用导出的 TOML 进行重编译:
```
./ps2_recomp config.toml
```
用于快速本地实验或带有调试符号的 ELF 的回退工作流程:
```
./ps2_analyzer your_game.elf config.toml
```
仅当你还没有 Ghidra 项目时才使用此方法。原生分析器启动更快,但对于已剥离符号的零售游戏,其准确性较低,更有可能遗漏内部可调用入口点。
有关推荐路径,请参阅 [Ghidra 工作流程](ps2xAnalyzer/Readme.md#3-ghidra-integration-for-retail-and-stripped-games-preferred)。
然后构建生成的输出并链接到 `ps2xRuntime`。
### 配置
`config.toml` 中的主要字段:
* `general.input`:源 ELF 路径。
* `general.ghidra_output`:推荐从 Ghidra 导出的函数映射 CSV。
* `general.output`:生成的 C++ 输出文件夹。
* `general.single_file_output`:生成一个合并的 cpp 文件或每个函数一个文件。
* `general.patch_syscalls`:将配置的补丁应用于 `SYSCALL` 指令(推荐 `false`)。
* `general.patch_cop0`:将配置的补丁应用于 COP0 指令。
* `general.patch_cache`:将配置的补丁应用于 CACHE 指令。
* `general.stubs`:强制作为 stub 的名称。也接受 `handler@0xADDRESS` 以将剥离符号的函数地址直接绑定到运行时 syscall/stub 处理程序。包括通用处理程序 `ret0`、`ret1`、`reta0`。
* `general.skip`:强制作为跳过包装器的名称。
* `patches.instructions`:按地址替换的原始指令。
针对已剥离符号 ELF 的地址绑定:
* 在 `general.stubs` 中使用 `handler@0xADDRESS` 将剥离符号的函数起始地址直接映射到运行时处理程序。
* 示例:`sceCdRead@0x00123456` 将函数起始地址 `0x00123456` 绑定到 `ps2_stubs::sceCdRead(...)`。
* 可用的通用临时处理程序:`ret0@0xADDR`、`ret1@0xADDR`、`reta0@0xADDR`。
* 在手动绑定之前,建议先使用从 Ghidra 导出的 TOML/CSV 进行重编译。额外的边界和合成入口点通常比早期的手动分类更重要。
* 该地址必须是该特定 ELF 版本中函数的起始地址。
* 地址在不同的游戏/区域/版本之间不可移植。
* 处理程序名称必须存在于运行时调用列表(`PS2_SYSCALL_LIST` 或 `PS2_STUB_LIST`)中。
示例:
```
[general]
input = "path/to/game.elf"
ghidra_output = ""
output = "output/"
single_file_output = true
patch_syscalls = false
patch_cop0 = true
patch_cache = true
stubs = ["printf", "malloc", "free"]
# stripped function binding by address:
# stubs = ["sceCdRead@0x00123456", "SifLoadModule@0x00127890"]
# temporary return handlers:
# stubs = ["ret0@0x001D9410", "ret1@0x001D5BC8", "reta0@0x0024B7C0"]
# mixed example:
# stubs = ["printf", "sceCdRead@0x00123456", "SifLoadModule@0x00127890"]
skip = ["abort", "exit"]
[patches]
instructions = [
{ address = "0x100004", value = "0x00000000" }
]
```
### 运行时
用于执行重编译后的代码。
`ps2xRuntime` 目前提供:
* 客户机内存模型和函数分发表。
* 部分 syscall 分发器,包含常见的内核 ID。
* 基本的 GS/VU/文件/系统 stub。
* 用于扩展和移植你自己的游戏的基础。
### 游戏覆盖钩子
游戏覆盖是运行时端、基于构建范围的补丁模块。
游戏覆盖是在 `loadELF` 期间运行的 C++ 代码,可以针对特定游戏版本替换按地址排列的函数绑定。这与重编译输出和全局运行时 stub/syscall 是分开的。
API:
* 头文件:`ps2xRuntime/include/game_overrides.h`
* 注册宏:`PS2_REGISTER_GAME_OVERRIDE(name, elfName, entry, crc32, applyFn)`
* 直接绑定助手:`ps2_game_overrides::bindAddressHandler(runtime, addr, "handler")`
在以下情况下使用游戏覆盖模块:
* 你需要针对每个游戏/每个版本的路由或补丁,而不污染全局行为。
* 你需要绑定许多地址,或为特定标题安装自定义替换逻辑。
#### 推荐的迭代循环
1. 使用最小配置运行,不要进行激进的跳过。
2. 首先修复严重的阻碍因素(`function not found`、syscall TODO、关键的 IO stub)。
3. 仅使用临时返回 stub 来对调用重要性进行分类。
4. 将临时修复提升为真正的实现。
5. 将针对特定游戏的 hack 移至由 ELF 元数据作为键的游戏覆盖中。
6. 在每批处理后从冷启动重新测试。
### 限制
* Graphics Synthesizer 和其他硬件组件需要外部实现
* VU1 微码尚未完成。
* 硬件模拟是部分的,许多路径是 stub。
### 致谢
* 灵感来源于 N64Recomp
* 使用 ELFIO 进行 ELF 解析
* 使用 toml11 进行 TOML 解析
* 使用 fmt 进行字符串格式化
标签:Bash脚本, C++ 代码生成, ELF 二进制分析, MIPS R5900, PlayStation 2, PS2, Python安全, 云安全监控, 云资产清单, 动态二进制翻译, 原生 PC 移植, 模拟器开发, 游戏移植, 游戏逆向, 跨平台编译, 运行时环境, 逆向工程, 静态分析, 静态重编译器