mcpField
X
xenonrecomp

xenonrecomp

本地服务
github.com/hedge-dev开发工具6.6k64 周前更新

关于 xenonrecomp

XenonRecomp 将 Xbox 360 可执行文件转换为 C++ 代码,供处理 Xbox 360 游戏重编译的开发者使用。它还提供 XenonAnalyse 来检测跳转表;转换结果仍需运行时支持,且项目不提供运行时实现。

主要功能

  1. 01将 Xbox 360 可执行文件转换为 C++
  2. 02分析并导出跳转表配置
  3. 03通过 TOML 配置重编译
  4. 04支持函数钩子和汇编中段钩子
  5. 05提供可选寄存器优化

适用场景

  1. 01为 Xbox 360 游戏生成 C++ 代码
  2. 02分析 XEX 文件中的跳转表
  3. 03为重编译项目配置函数边界和钩子

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

暂未收录安装方式

我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。

代码仓库

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

XenonRecomp

XenonRecomp 是一款可将 Xbox 360 可执行文件转换为 C++ 代码的工具,之后可针对任意平台重新编译。

本项目深受 N64: Recompiled 启发;后者是用于 N64 可执行文件的类似工具。

**免责声明:**本项目不提供运行时实现。它只会将游戏代码转换为 C++,没有运行时支持,代码将无法正常运行。让游戏运行起来是你的责任。

实现细节

指令

指令会直接转换,不会尝试将其转换成类似反编译代码的形式,因此输出的代码不太易读。每个 PPC 函数都会接收 CPU 状态作为参数,其中包含每个 PPC 寄存器的定义及其执行时的当前值。第二个参数是基址指针,因为 Xbox 360 CPU 使用 32 位指针。

目前已实现相当多的 PPC 指令,缺失的指令主要是已实现指令的变体。部分指令(例如 D3D 解包/打包指令)不支持所有操作数类型。遇到未实现的情况时,会生成警告,或在转换后的 C++ 代码中插入调试中断。

指令实现使用小端值。不过,由于 Xbox 360 是大端机器,内存加载指令会在读取值时交换字节序,而内存存储指令则会在写入前将其转换为大端序。所有内存加载和存储操作都标记为 volatile,以防止 Clang 进行不安全的代码重排。

向量寄存器的字节序处理更为复杂。重编译器不会交换单个 32 位元素,而是反转整个 16 字节向量。指令必须考虑这种反转顺序,例如在点积中使用 WZY 分量而不是 XYZ 分量,或在向量打包指令中使用顺序相反的参数。

FPU 要求非规格化数保持不变,而 VMX 指令总会将其刷新。为此,当前浮点状态会存储在 CPU 状态结构体中,并在执行每条指令前按需启用或禁用非规格化数刷新。

大多数 VMX 指令都使用 x86 内置函数实现。ARM64 支持通过 SIMD Everywhere 实现。

MMIO

MMIO 通常用于 XMA 解码等硬件操作,目前尚未实现。虽然有一项未完成的 MMIO 实现尝试,但支持它可能并非易事,并且可能需要对指令进行高级分析。

间接函数

虚函数调用通过在运行时创建“完美哈希表”来解析:对 64 位指针(使用原始指令地址乘以 2)进行解引用,即可得到重编译后函数的地址。此前,这一功能通过创建 8 GB 虚拟分配来实现,但会造成过大的内存压力。现在,它依赖于将函数地址放在基址内存指针中有效 XEX 内存区域之后。这些区域会作为宏导出到输出文件 ppc_config.h 中。

跳转表

至少在较旧的 Xbox 360 二进制文件中,跳转表通常具有可预测的汇编模式,因此无需虚拟机即可通过静态分析轻松检测。XenonAnalyse 包含检测 Sonic Unleashed 中跳转表的逻辑,不过其他游戏中的差异(可能是 Xbox 360 编译器更新所致)可能需要修改检测逻辑。目前,还没有完全通用的跳转表处理方案,因此可能需要更新检测逻辑以支持其他游戏。

查找跳转表的典型方法是搜索 mtctr r0 指令。该指令后几乎总会跟着 bctr,而前面的指令会计算跳转地址。

XenonAnalyse 会生成一个 TOML 文件,其中包含检测到的跳转表,可在主 TOML 配置文件中引用。这样一来,重编译器就能为这些跳转表生成真正的 switch 分支。

函数边界分析

XenonAnalyse 内置的函数边界分析器在大多数情况下都能正常工作。具有栈空间的函数,其边界定义在 XEX 的 .pdata 段中。对于未出现在此段中的函数,分析器会搜索分支链接指令来检测函数起始位置,并通过静态分析确定函数长度。

但是,分析器难以处理包含跳转表的函数,因为这类函数看起来像尾调用,且缺少足够的信息。目前还没有解决方案,不过扩展函数分析器以支持 TOML 文件中定义的跳转表,可能相对简单。作为变通方案,重编译器 TOML 文件允许用户手动定义函数边界。

异常

重编译器目前不支持异常。由于使用了链接寄存器,而且异常处理程序可以跳转到任意代码位置,因此这项工作颇具挑战。

setjmp

setjmp 和 longjmp 通过重定向到原生实现来实现。得益于 Xbox 360 拥有大量向量寄存器,来宾 CPU 状态结构体足以容纳 x86 CPU 状态,也可能容纳其他架构的状态。

优化

由于 Xbox 360 二进制文件通常遵循稳定的 ABI,我们可以对代码结构作出某些假设,从而让 Clang 编译器生成更好的代码。重编译器提供了多个优化选项,但建议仅在重编译能够正常运行后再测试这些选项。

如果游戏不使用异常,就可以跳过链接寄存器,因为重编译过程本身已处理函数返回行为。

假设游戏遵循 ABI,以下寄存器可以安全地转换为局部变量,因为它们不会超出函数作用域:

  • 计数寄存器
  • XER
  • 保留寄存器
  • 条件寄存器
  • 非参数寄存器
  • 非易失寄存器

局部变量优化带来的提升尤其显著,因为可以完全移除对寄存器恢复/保存函数的调用,并消除对 PPC 上下文结构体的冗余存储。在 Unleashed Recompiled 中,启用这些优化后,可执行文件大小减少了约 20 MB,帧耗时也缩短了数毫秒。

补丁机制

XenonRecomp 使用 Clang 编译器中的技术定义 PPC 函数,使其易于挂钩。通过将 PPC 函数别名为“实现函数”,并将原始函数标记为弱链接,用户可以使用自定义实现覆盖它,同时保留对原始函数的访问权限:

PPC_FUNC_IMPL(__imp__sub_XXXXXXXX);
PPC_FUNC(sub_XXXXXXXX)
{
    __imp__sub_XXXXXXXX(ctx, base);
}

此外,还可以在特定指令地址处,直接将汇编中段钩子插入翻译后的 C++ 代码中。重新编译器会插入这些函数调用,用户负责在自己的重新编译项目中实现这些函数。链接器会在编译期间解析它们。

用法

XenonAnalyse

将 XenonAnalyse 用作命令行应用程序时,可以将 XEX 文件作为输入参数,输出一个包含可执行文件中所有已检测到的跳转表的 TOML 文件:

XenonAnalyse [input XEX file path] [output jump table TOML file path]

不过,如前文所述,由于不同游戏之间存在差异,可能需要额外支持来处理不同的模式。

可以在 Unleashed Recompiled 仓库中查看跳转表示例 TOML 文件。

XenonRecomp

XenonRecomp 接受一个包含重新编译器配置的 TOML 文件,以及 XenonUtils 目录中 ppc_context.h 文件的路径:

XenonRecomp [input TOML file path] [input PPC context header file path]

可以在 Unleashed Recompiled 仓库中查看重新编译器 TOML 文件示例。

Main
[main]
file_path = "../private/default.xex"
patch_file_path = "../private/default.xexp"
patched_file_path = "../private/default_patched.xex"
out_directory_path = "../ppc"
switch_table_file_path = "SWA_switch_tables.toml"

所有路径均相对于 TOML 文件所在目录。

属性描述
file_pathXEX 文件的路径。
patch_file_pathXEXP 文件的路径。如果游戏没有标题更新,则不需要此项。
patched_file_path已打补丁 XEX 文件的路径。如果该文件不存在,XenonRecomp 会自动创建,并在后续重新编译时重复使用。如果未指定 XEXP 文件,则不会执行任何操作。可以将此输出文件传递给 XenonAnalyse。
out_directory_path用于存放输出 C++ 代码的目录路径。运行重新编译器前,此目录必须已存在。
switch_table_file_path包含跳转表定义的 TOML 文件路径。重新编译器会使用此文件将跳转表转换为实际的 switch 分支。
优化
skip_lr = false
skip_msr = false
ctr_as_local = false
xer_as_local = false
reserved_as_local = false
cr_as_local = false
non_argument_as_local = false
non_volatile_as_local = false

启用或禁用文档前文介绍的各种优化。建议在成功运行重新编译后的程序之前,不要启用这些优化。

寄存器恢复与保存函数
restgprlr_14_address = 0x831B0B40
savegprlr_14_address = 0x831B0AF0
restfpr_14_address = 0x831B144C
savefpr_14_address = 0x831B1400
restvmx_14_address = 0x831B36E8
savevmx_14_address = 0x831B3450
restvmx_64_address = 0x831B377C
savevmx_64_address = 0x831B34E4

Xbox 360 二进制文件包含专门的寄存器恢复与保存函数,其行为类似于 switch case 的贯穿执行。每个使用非易失寄存器的函数,要么内联了这些函数,要么显式调用它们。重新编译器需要在 TOML 文件中获取每个恢复/保存函数的起始地址,才能正确重新编译它们。这些函数可能可以自动检测,但目前还没有相应机制。

属性说明字节模式
restgprlr_14_address__restgprlr_14 函数的起始地址。它以 ld r14, -0x98(r1) 开始,对其余非易失寄存器重复相同操作,并在最后恢复链接寄存器。e9 c1 ff 68
savegprlr_14_address__savegprlr_14 函数的起始地址。它以 std r14, -0x98(r1) 开始,对其余非易失寄存器重复相同操作,并在最后保存链接寄存器。f9 c1 ff 68
restfpr_14_address__restfpr_14 函数的起始地址。它以 lfd f14, -0x90(r12) 开始,对其余非易失 FPU 寄存器重复相同操作。c9 cc ff 70
savefpr_14_address__savefpr_14 函数的起始地址。它以 stfd r14, -0x90(r12) 开始,对其余非易失 FPU 寄存器重复相同操作。d9 cc ff 70
restvmx_14_address__restvmx_14 函数的起始地址。它以 li r11, -0x120 和 lvx v14, r11, r12 开始,对其余非易失 VMX 寄存器重复相同操作,直到 v31。39 60 fe e0 7d cb 60 ce
savevmx_14_address__savevmx_14 函数的起始地址。它以 li r11, -0x120 和 stvx v14, r11, r12 开始,对其余非易失 VMX 寄存器重复相同操作,直到 v31。39 60 fe e0 7d cb 61 ce
restvmx_64_address__restvmx_64 函数的起始地址。它以 li r11, -0x400 和 lvx128 v64, r11, r12 开始,对其余非易失 VMX 寄存器重复相同操作。39 60 fc 00 10 0b 60 cb
savevmx_64_address__savevmx_64 函数的起始地址。它以 li r11, -0x400 和 stvx128 v64, r11, r12 开始,对其余非易失 VMX 寄存器重复相同操作。39 60 fc 00 10 0b 61 cb
longjmp 与 setjmp
longjmp_address = 0x831B6790
setjmp_address = 0x831B6AB0

这些是可执行文件中 longjmp 和 setjmp 函数的地址。重新编译器会将这些函数直接重定向到原生版本。这些函数的实现可能因游戏而异。在某些情况下,可以通过查找对 RtlUnwind 的调用来定位 longjmp;setjmp 通常紧随其后。

如果游戏不使用这些函数,可以从 TOML 文件中删除这些属性。

显式函数边界
functions = [
    { address = 0x824E7EF0, size = 0x98 },
    { address = 0x824E7F28, size = 0x60 },
]

如果 XenonAnalyse 无法正确分析函数边界(例如包含跳转表的函数),可以使用 functions 属性显式定义函数边界。

跳过无效指令
invalid_instructions = [
    { data = 0x00000000, size = 4 }, # Padding
    { data = 0x831B1C90, size = 8 }, # C++ Frame Handler
    { data = 0x8324B3BC, size = 8 }, # C Specific Frame Handler
    { data = 0x831C8B50, size = 8 },
    { data = 0x00485645, size = 44 } # End of .text
]

在 invalid_instructions 属性中,可以定义 32 位整数值,指示重新编译器遇到这些值时跳过特定字节。例如,在 Unleashed Recompiled 中,这些值用于跳过函数之间的异常处理数据,因为这些数据不是有效代码。

中间汇编钩子
[[midasm_hook]]
name = "IndexBufferLengthMidAsmHook"
address = 0x82E26244
registers = ["r3"]
void IndexBufferLengthMidAsmHook(PPCRegister& r3)
{
    // ...
}

你可以在 TOML 文件中定义多个中间汇编钩子,让重编译器在指定地址插入函数调用。在重编译项目中实现这些函数后,链接器会自动解析这些调用。

属性描述
name中间汇编钩子的函数名。你可以重复使用函数名,以便在多个地址放置相同的实现。否则,不同的实现必须使用不同的名称。
address放置函数调用的指令地址。此操作不会覆盖指定地址处的指令。
registers要作为参数传递给中间汇编钩子的寄存器。这里使用寄存器列表,因为局部变量优化不会将优化后的寄存器保留在 PPC 上下文结构体中。
return设为 true,表示调用中间汇编钩子后,插入钩子的函数应立即返回。
return_on_true设为 true,表示如果中间汇编钩子调用返回 true,函数就应返回。
return_on_false设为 true,表示如果中间汇编钩子调用返回 false,函数就应返回。
jump_address调用中间汇编钩子后立即跳转到的地址。该地址必须位于放置钩子的同一函数中。
jump_address_on_true如果中间汇编钩子返回 true,则跳转到此地址。该地址必须位于放置钩子的同一函数中。
jump_address_on_false如果中间汇编钩子返回 false,则跳转到此地址。该地址必须位于放置钩子的同一函数中。
after_instruction设为 true,即可将中间汇编钩子放在指令之后,而不是之前。

某些属性互斥。例如,不能同时使用 return 和 jump_address,也不能混用直接返回或跳转与条件返回或跳转。如果不符合要求,重编译器会显示警告。

测试

XenonRecomp 可以重编译 Xenia 的 PPC 测试,并通过仓库中的 XenonTests 项目执行这些测试。使用 Xenia 的构建系统构建测试后,XenonRecomp 可以将 src/xenia/cpu/ppc/testing/bin 目录作为输入进行处理,并在指定的输出目录中生成 C++ 文件:

XenonRecomp [input testing directory path] [input PPC context header file path] [output directory path]

生成文件后,刷新 XenonTests 的 CMake 缓存,使这些文件显示在项目中。随后即可执行测试,将指令结果与预期值进行比较。

构建

构建此项目需要 CMake 3.20 或更高版本,以及 Clang 18 或更高版本。由于仓库包含子模块,请确保递归克隆。

尚未测试 Clang 以外的编译器,因此不建议使用,包括用于重编译输出的情况。此项目依赖特定于编译器的内建函数和技术,这些内容在其他编译器上可能无法正常工作;此外,许多优化方法都依赖 Clang 的代码生成。

在 Windows 上,你可以使用 clang-cl 工具集,并在 Visual Studio 的 CMake 集成中打开项目。

特别鸣谢

本项目的实现离不开 Xenia 模拟器,因为 CPU 代码转换过程的许多部分都大量参考了它的 PPC 代码转换器。本项目还使用了 Xenia Canary 中的代码来修补 XEX 二进制文件。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。