反编译是怎么做的:Tenkaichi3Decomp字节级还原PS2游戏的完整教程
【免费下载链接】Tenkaichi3Decomp
PC port of Dragon Ball Z: Budokai Tenkaichi 3, built on the BT3-Decompiled project.
Tenkaichi3Decomp是 PS2 名作《龙珠Z:电光石火3》(Dragon Ball Z: Budokai Tenkaichi 3,SLUS-21678)的 PC 原生移植项目,它建立在BT3-Decompiled反编译工程之上:不是模拟器,而是把游戏原本的MIPS 汇编逐函数还原成 C 代码,再用当年的Sony ee-gcc 2.96编译器重新编译,直到产出的二进制与原版字节完全一致,最后再垫上全新的 PC 渲染、声音与输入层。本文带你走一遍这套"反编译 → 字节级验证 → PC 移植"的完整流程。
一、先搞懂:反编译和"重编译"不是一回事 🤔
很多人以为反编译就是"把机器码翻译回 C 语言"。实际上 Tenkaichi3Decomp 追求的目标更苛刻——匹配反编译(matching decompilation):
- ❌ 普通反编译:写出"逻辑上等价"的 C,能跑就行;
- ✅ 匹配反编译:写出的 C 必须编译出和原版一模一样的机器码,每一个字节、每一条指令都不能差。
为什么要这么较真?因为 PS2 的浮点运算、随机数序列、30 帧/秒的帧逻辑都高度依赖执行时序,"逻辑等价"的代码在联网对战、录像回放时会逐渐跑偏。字节级一致才能保证移植版与主机版行为完全相同。
二、项目结构:代码从哪来,到哪去?
| 目录 | 作用 |
|---|---|
| src/、include/ | 游戏本体代码(来自反编译工程),PC 改动写在#ifdef PORT里 |
| port/src/ | PC 侧新代码:Vulkan/OpenGL 渲染器、内存卡、电影播放、设置窗口 |
| port/tools/ | 构建工具:64 位指针重写、打包、光盘数据提取 |
| port/data/ | 游戏内置数据表的"形状"(只有标签和大小,数值启动时从你的光盘读取) |
| config/symbols/ | 符号表:每个函数/全局变量的名字和地址 |
| docs/ | 反编译发现的游戏机制笔记(战斗、AI、HUD、存档等) |
⚠️ 注意:仓库里不包含任何游戏数据,需要你自己准备的 USA 版光盘镜像(.iso),安装时程序会校验镜像是未修改的 SLUS-21678 版本。
三、MIPS 汇编反编译的完整工作流 🔧
整个流程记录在 docs/decomp_guide.md,可以概括为一个循环:
步骤 1:读汇编
用 splat 反汇编工具把可执行文件拆成一堆.s文件。每个函数长这样:
nonmatching FunctionName, 0xSIZE glabel FunctionName ...MIPS 指令... endlabel FunctionName步骤 2:手写 C
按地址顺序,一个 C 文件覆盖一段连续的地址范围,跳过不行、乱序也不行。类型来自 include/types.h(s8 u8 s16 u16 s32 u32 f32等)。
步骤 3:记录符号名
每取一个名字就写进 config/symbols/ 下的符号文件,格式固定:
Name = 0x00123456; // type:func gGlobal = 0x002FF080; // size:0x8猜测的名字要标// guess,且全项目不许重名。
步骤 4:fdiff 逐函数比对
运行python3 scripts/fdiff.py src/<area>/<module>.c,它会:
- 用ee-gcc 2.96按原版参数(
-O2 -fno-strict-aliasing,游戏代码-G8、CRI 库-G0)编译你的 C 文件; - 把每个函数与原版二进制并排逐条指令比对;
- 输出
OK表示匹配(重定位字段会被掩码屏蔽,所以链接前也能检查)。
步骤 5:迭代到全部 OK,再链接进大工程
.venv/bin/python configure.py && ninja cmp build/SLUS_216.78.rom disc/SLUS_216.78.rom # 必须完全静默 cmp build/DBZP.BIN disc/BIN/DBZP.BIN python3 scripts/progress.py # 统计反编译百分比只有ninja成功 + 两个镜像cmp静默,才算真正通过(这就是项目里常说的"gate")。
四、ee-gcc 2.96:为什么必须用"当年那台编译器" ⚙️
这是字节级反编译最玄学也最关键的一环。项目做过一轮完整的工具链排查(见 docs/decomp_guide.md 的 "Toolchain check" 一节),结论非常干脆:
- 游戏用的编译器是
gcc version 2.96-ee-001003-1(PS2 EE 处理器专用分支),其他所有公开 ee-gcc 版本(2.9、2.95、3.2)最多只能保住 16% 的函数匹配; - 换 118 组编译参数、361 种组合,没有任何一组能救活"卡住"的函数,反而都会弄坏已匹配的函数;
- 去掉
-fno-strict-aliasing会弄坏 1060 个函数;加-g会弄坏 84 个——所以原版就是无调试信息编译的。
这意味着你手写的 C 必须"迎合" 25 年前编译器的脾气。docs/decomp_guide.md 里沉淀了几十条"血泪经验",举几个:
| 汇编现象 | 对应的 C 写法 |
|---|---|
lui/addiu而不是gp_rel | 小全局变量(≤8 字节)必须声明成完整类型,extern s32 tbl[]不行 |
beq 1 / slti <2 / beq 2阶梯 | 那是一个switch,且显式写了case 0: break; |
sltvssltu、divvsdivu | 有符号/无符号写反了 |
多出一条move v1,s1 | 原版"重复计算"了一个表达式,别帮编译器优化 |
| 分支延迟槽里凭空多一条指令 | 看include/gcc_prelude.inc的预序列规则:Sony 汇编器会把分支前一条指令"塞"进延迟槽 |
甚至连long是 64 位、浮点字面量3.14159265f一位都不能少(fdiff 看不见.lit4常量池,必须人工对字节)这些细节都有记录。
五、fdiff 逐函数比对:反编译的"单元测试" ✅
scripts/fdiff.py 是整个工程的质量门禁,思路很巧妙:
- 掩码屏蔽重定位位:跳转目标、
%hi/%lo/%gp_rel立即数这些"链接时才知道"的字段被按R_MIPS_26 / HI16 / LO16 / GPREL16掩掉,因此函数没链接也能提前验证; - 函数级粒度:一个文件 100 个函数,99 个
OK、1 个卡住,能精确知道差在哪几条指令(原版在左、你的在右并排打印); - 盲区靠真镜像补:fdiff 看不见数据初值(比如一张颜色表写错一个字节),所以最终裁判是链接后
build/与disc/两个 ROM 的cmp全量比对——历史上 11 色调色板里 5 个颜色写错的 bug 就是这样被揪出来的。
对"怎么写都凑不齐寄存器"的疑难函数,项目还配了两件重武器:
- 差分行为测试(
build/scratch_difftest/):把原版字节和你的 C 在同一个随机内存上各跑 3000~4000 组种子,比对返回值、每一条外发调用的参数、写出的每个字节——59 个未匹配函数全部测试,只抓到 1 个真实行为差异(而且恰好是原版的复制粘贴 bug,已记录在 docs/known_bugs.md); - 语句排列器(
scripts/permute.py):对"只差寄存器分配"的函数自动搜索语句顺序,三轮共解出 30+ 个函数。
实在匹配不上的函数,会写成INCLUDE_ASM(...)保留原版汇编,并把最佳 C 尝试留在#if 0里加注释,绝不偷偷"改对"。
六、链接:最后一公里的对齐地狱 🔗
函数全 OK 之后,链接阶段还有独立的一整套坑,因为数据段的布局也是原版的字节:
- 每个 C 文件在 config/SLUS_216.78.yaml 里要登记自己的
c/.rodata/.lit4/.sdata子段起点地址; - 原版把
.rodata补齐到 16 字节、现代编译器只补 8 字节——差 8 字节时就要手工"提前 8 字节"放下一段汇编数据块; - 跳转表按对象起点做 16 字节对齐,有时得把 C 文件向前扩展、用
INCLUDE_ASM把前面的函数"拉进来"才能对齐(src/battle/btl_seq.c 就是例子); - 两个可执行文件要一起管:主程序
SLUS_216.78(约 1.8 MB 代码)+ 菜单覆盖层DBZP.BIN(737 个函数,加载在 0x334C00,开发者内部叫它PROGRESS)。
进度统计由 scripts/progress.py 给出。截至最新提交:主程序 94.96%、覆盖层 99.24% 的代码由 C 编译产出,两个镜像字节一致,约 7270 个函数全部匹配,仅剩 45 个INCLUDE_ASM。
七、从反编译到 PC 移植 🖥️
字节级一致之后,"移植"就变成了一层干净的工程:
- 渲染:
port/src/gs/里的 Vulkan 渲染器重画 PS2 的画面(描边、辉光、深度着色、远景模糊都可开关),没有 Vulkan 的机器自动落到 OpenGL 3.3; - 数学层:游戏 30 帧的战斗模拟用 PS2 VU0 数学,PC 上由
softfloat_ps2.c逐位复现,保证帧确定性; - 联网:
port/matchmaking/是一个完整的 Cloudflare Worker 匹配服务(房间码 + TURN 中继),回滚帧数由房主设定; - 数据:仓库只带数据表的"空壳"(
port/data/),启动时从你自己的光盘镜像里取真实数值。
游戏机制的发现全部沉淀在 docs/systems/:战斗帧结构、格斗家对象、AI 对手、HUD、存档块 等,每条结论都标注Verified(编译出原版字节)或Inferred(从反汇编推断)。
八、新手入门路线:如何动手参与 🚀
想上手贡献?建议按这个顺序读:
- README.md —— 项目总览、安装与源码构建(
python3 install.py --iso <你的镜像>); - docs/decomp_guide.md —— 反编译一个模块的完整方法论(核心文档);
- docs/status.md —— 当前进度交接笔记,看最新状态怎么验证;
- docs/agent_rules.md —— 并行协作的规矩;
- 参考已完成的范例:src/sys/heap.c + include/sys/heap.h。
环境上你需要:tools/ee-gcc2.96/(Sony 编译器)、tools/binutils/mips-ps2-decompals-*(MIPS 汇编器/反汇编器)、Python + splat + ninja,以及一份合法获得的 SLUS-21678 光盘镜像。日常验证三板斧:
.venv/bin/python configure.py && ninja cmp build/SLUS_216.78.rom disc/SLUS_216.78.rom python3 scripts/fdiff.py src/<area>/<module>.c九、为什么"字节级反编译"值得做?💎
- 确定性:浮点、随机数、帧时序逐位一致,联网对战和回放才不会跑偏;
- 可修改性:C 代码可以随便加功能(本项目就加了在线对战、新渲染器、自定义关卡/音乐),改完还能一眼看出影响;
- 可移植性:字节一致是移植的地基——同一份游戏逻辑跑在 Linux、Windows 甚至 Apple Silicon(实验性)上;
- 研究价值:反编译过程顺带发现了原版随机数只刷新 396/624 状态字的怪实现、碰撞检测里的死代码、以及多个"修不得"的原版 bug(docs/known_bugs.md)。
小结:Tenkaichi3Decomp 展示的是一条可复制的路——原版编译器 + 符号表 + 逐函数字节比对 + 全量镜像验证,把一个 PS2 游戏 7000 多个函数逐一"请回" C 语言世界,再在这个完全透明的地基上构建 PC 移植。如果你对 MIPS 汇编、编译器行为或游戏逆向感兴趣,docs/decomp_guide.md 里那几十条"踩坑笔记"本身就是最好的教材。
许可说明:
port/下移植代码为 AGPL-3.0(早期为 MIT),src/游戏代码版权归原作权利方所有;项目不包含游戏资源,需自备光盘镜像。
【免费下载链接】Tenkaichi3Decomp
PC port of Dragon Ball Z: Budokai Tenkaichi 3, built on the BT3-Decompiled project.
相关推荐
LuaDec51反编译实战:从字节码到源代码的完整还原方案
LuaDec51反编译实战:从字节码到源代码的完整还原方案 LuaDec51是一款专注于Lua 5.1版本的字节码反编译工具,能够将编译后的Lua字节码(.lu
开发工具LuaJIT反编译神器LJD:从字节码到源码的完整还原指南
LuaJIT反编译神器LJD:从字节码到源码的完整还原指南 你是否曾经面对一个编译后的LuaJIT字节码文件,却苦于无法理解其内部逻辑?或者需要分析某个Lua脚
bbhost反编译实践:200个游戏函数被重写为源码,位精确验证体系是怎么做到的
bbhost反编译实践:200个游戏函数被重写为源码,位精确验证体系是怎么做到的 bbhost 是让《血源诅咒》(Bloodborne)在 PC 上运行的开源移
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考