N64Recomp/N64Recomp
GitHub: N64Recomp/N64Recomp
一款将 N64 游戏二进制文件静态重编译为跨平台原生 C 代码的移植与逆向工程工具。
Stars: 7876 | Forks: 432
# N64: Recompiled
N64: Recompiled 是一个将 N64 二进制文件静态重编译为 C 代码的工具,这些 C 代码可以编译后在任意平台上运行。它可用于制作移植版或开发工具,并且其模拟行为的速度远快于解释器或动态重编译。更广泛地说,在任何你想于独立环境中运行 N64 二进制文件某一部分的场景下,它都可以派上用场。
这并非首个对游戏主机二进制文件使用静态重编译的项目。一个著名的例子是 [jamulator](https://github.com/andrewrk/jamulator),它针对的是 NES 二进制文件。此外,这甚至不是首个将静态重编译应用于 N64 相关项目的案例:[IDO 静态重编译](https://github.com/decompals/ido-static-recomp) 在现代系统上重新编译了 SGI IRIX IDO 编译器,以促进 N64 游戏的匹配反编译。本项目在某些方面的工作原理与 IDO 静态重编译项目相似,那个项目也是我开发此工具的主要灵感来源。
## 目录
* [工作原理](#how-it-works)
* [Overlays](#overlays)
* [使用方法](#how-to-use)
* [单文件输出模式](#single-file-output-mode-for-patches)
* [RSP Microcode 支持](#rsp-microcode-support)
* [计划中的功能](#planned-features)
* [构建说明](#building)
## 工作原理
重编译器的工作原理是接收一个符号和元数据列表以及二进制文件,其目标是将输入的二进制文件拆分为多个函数,每个函数单独重编译为一个 C 函数,并根据元数据进行命名。
指令会被逐条处理,并在每条指令处理完毕时输出相应的 C 代码。这种翻译非常直接,以保持较低的复杂度。例如,指令 `addiu $r4, $r4, 0x20`(将寄存器 `$r4` 低位字节中的 32 位值加上 `0x20`,并将符号扩展后的 64 位结果存回 `$r4`)会被重编译为 `ctx->r4 = ADD32(ctx->r4, 0X20);` `jal`(jump-and-link)指令会被直接重编译为函数调用,而那些被识别为尾调用优化的 `j` 或 `b` 指令(无条件跳转和分支)也会被重编译为函数调用。分支延迟槽则通过根据需要复制指令来进行处理。某些指令还有其他特定的行为,例如,如果重编译器发现 `jr` 指令与跳转表一起使用,它会尝试将其转换为 switch-case 语句。重编译器主要针对由旧版 MIPS 编译器(例如 mips gcc 2.7.2 和 IDO)以及现代针对 mips 的 clang 构建的二进制文件进行了测试。现代的 mips gcc 可能会因为其执行的某些优化而导致重编译器出错,但这种情况大概可以通过设置特定的编译标志来避免。
目前,重编译器创建的每个输出函数都会被输出到其独立的文件中。未来可能会提供一个选项,将多个函数组合输出到同一个文件中,这应该能通过减少构建过程中的文件 I/O 来加快重编译器输出的构建速度。
重编译器的输出可以使用任何 C 编译器进行编译(已使用 msvc、gcc 和 clang 测试过)。预期该输出将与一个运行时配合使用,该运行时需提供必要的功能和宏实现以使其运行。[N64ModernRuntime](https://github.com/N64Recomp/N64ModernRuntime) 中提供了一个运行时,您可以在 [Zelda 64: Recompiled](https://github.com/Zelda64Recomp/Zelda64Recomp) 项目中看到它的实际应用。
## Overlays
静态链接的 overlays 和可重定位的 overlays 都可以由该工具处理。在这两种情况下,工具都会为 jump-and-link-register(即函数指针或虚函数)输出函数查找功能,所提供的运行时可以通过任意类型的查找表来实现该功能。例如,指令 `jalr $25` 会被重编译为 `LOOKUP_FUNC(ctx->r25)(rdram, ctx);` 随后,运行时可以维护一个列表,记录哪些程序段已加载以及它们所在的地址,从而确定在运行期间触发查找时要运行哪个函数。
对于可重定位的 overlays,该工具会修改拥有重定位数据的受支持指令(`lui`、`addiu`、加载和存储指令),方法是输出一个额外的宏,使运行时能够重定位该指令的立即数字段。例如,如果地址 `0x80BFA100` 开头的段中,存在一条针对地址为 `0x80BFA730` 符号的重定位,那么指令 `lui $24, 0x80C0` 就会被重编译为 `ctx->r24 = S32(RELOC_HI16(1754, 0X630) << 16);`,其中 1754 是该段的索引。随后,运行时可以实现 RELOC_HI16 和 RELOC_LO16 宏,以便根据该段当前加载的地址来处理立即数的修改。
未来将提供对 TLB 映射重定位的支持,这将增加提供 MIPS32 重定位列表的功能,以便运行时在加载时对其进行重定位。将此功能与用于可重定位 overlays 的功能相结合,应该能够允许运行大多数 TLB 映射的代码,而不会在每次访问 RAM 时造成性能损耗。
## 使用方法
重编译器通过提供一个 toml 文件来进行配置,以设定重编译器的行为,该文件是提供给重编译器的第一个参数。在这个 toml 文件中,你可以指定输入和输出文件路径,还可以选择对特定函数进行打桩处理、跳过特定函数的重编译,以及修补目标二进制文件中的单条指令。我们还计划添加一项功能,通过将钩子添加到 toml 中(即下方链接的 toml 文件中的 `[[patches.func]]` 和 `[[patches.hook]]` 部分),在重编译器输出中生成这些钩子,但此功能目前尚未实现。目前还没有关于重编译器提供的每个选项的文档,但你可以在 Zelda 64: Recompiled 项目[这里](https://github.com/Mr-Wiseguy/Zelda64Recomp/blob/dev/us.rev1.toml) 找到一个 toml 示例。
目前,提供所需元数据的唯一方法是向此工具传递一个 elf 文件。获取此类 elf 最简单的方法是建立目标二进制文件的汇编或反编译工程,但未来将支持通过自定义格式来提供元数据,从而免去进行这一步的需要。
## 单文件输出模式(用于补丁)
该工具还可以通过配置 toml 中的选项,被配置为以“单文件输出”模式进行重编译。这会将提供的 elf 中的所有函数输出到同一个输出文件中。此模式的目的是能够编译目标二进制文件中函数的修补版本。
此模式可以与几乎所有链接器(ld、lld、MSVC 的 link.exe 等)提供的功能结合使用,用修改后的版本替换原始重编译器输出中的函数。这些链接器只会在先前输入文件中未找到符号时,才去静态库中查找符号。因此,如果在提供原始重编译器输出之前先将重编译后的补丁提供给链接器,这些补丁的优先级就会高于原始重编译器输出中具有相同名称的函数。
这在对目标二进制文件的补丁进行迭代时能节省大量时间,因为你无需再对目标二进制文件重新运行重编译器,也无需编译原始的重编译器输出。在 Zelda 64: Recompiled 项目[这里](https://github.com/Mr-Wiseguy/Zelda64Recomp/blob/dev/patches.toml) 可以找到用于此目的的单文件输出模式示例,而用于构建这些补丁 elf 的相应 Makefile 则在[这里](https://github.com/Mr-Wiseguy/Zelda64Recomp/blob/dev/patches/Makefile)。
## RSP Microcode 支持
RSP microcode 也可以使用此工具进行重编译。目前尚不支持重编译 RSP overlays,但如果需要,未来可能会添加此支持。关于如何使用此功能的文档即将推出。
## 计划中的功能
* 自定义元数据格式,用于提供符号名称、重定位信息和任何其他必要的数据,以便在无需 elf 文件的情况下运行
* 每个输出文件生成多个函数,以加快编译速度
* 支持记录 MIPS32 重定位,以允许运行时对其进行重定位以用于 TLB 映射
* 能够将其重编译为动态语言(例如 Lua),以便在运行时加载代码来提供 Mod 支持
## 构建说明
此项目可以使用 CMake 3.20 或更高版本以及支持 C++20 的 C++ 编译器进行构建。本仓库使用了 git 子模块,因此请务必递归克隆(`git clone --recurse-submodules`)或在克隆后递归初始化子模块(`git submodule update --init --recursive`)。接下来,构建过程与任何其他 cmake 项目相同,例如,在目标构建文件夹中运行 `cmake` 并将其指向本仓库的根目录,然后从该目标文件夹中运行 `cmake --build .`。
## 使用的库
* [rabbitizer](https://github.com/Decompollaborate/rabbitizer) 用于指令解码/分析
* [ELFIO](https://github.com/serge1/ELFIO) 用于 elf 解析
* [toml11](https://github.com/ToruNiina/toml11) 用于 toml 解析
* [fmtlib](https://github.com/fmtlib/fmt)
标签:Bash脚本, MIPS, N64, Python安全, 云资产清单, 游戏移植, 编译器, 逆向工程, 静态重编译