ran-j/PS2Recomp

GitHub: ran-j/PS2Recomp

将 PlayStation 2 的 MIPS R5900 指令集静态重编译为 C++ 代码,配合运行时环境实现 PS2 游戏的 PC 原生移植。

Stars: 2837 | Forks: 85

## PS2Recomp: PlayStation 2 静态重编译器 (实验性) [![Discord](https://img.shields.io/badge/Discord-Join%20Server-5865F2?logo=discord&logoColor=white)](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 移植, 模拟器开发, 游戏移植, 游戏逆向, 跨平台编译, 运行时环境, 逆向工程, 静态分析, 静态重编译器