- 55
- 个可维护的 C 函数
- 45,380
- 与原始代码的比较
- 33
- 个编译后字节匹配的函数
项目进度截至 2026 年 10 月 7 日。进度记录 · 测试覆盖情况
当前进度
目前已还原关卡与资源处理、砖块碰撞、动画、粒子和奖励生成。当前构建提供检查工具与分析库。可玩的游戏版本还在开发,后续工作包括球和挡板的物理逻辑、道具处理及 Windows 集成。
找出声音的左右位置如何计算
砖块被击中时,它的横坐标会传给声音处理代码。这次调查要找出:屏幕坐标如何转换为声音的左右声像值?
用 REA 找出 DX-Ball 如何计算声音的左右位置。解释计算过程,并展示代码。
把本地可执行文件交给编程助手,再使用这个示例 prompt。下面展示助手为回答问题发起的 REA 查询,以及得到的证据。
助手用 REA 检查函数、追踪调用它的砖块碰撞函数,再从可执行文件中读取常量。这些结果确定了输入和计算步骤,才有了下面的 C 函数。
REA 最初返回的 0x406400 伪代码声明了 FUN_00406400(void),并调用 __ftol()。但指令中还有来自栈的输入和浮点运算。这个差异提示助手:还需要继续查。
最初的反编译结果
longlong FUN_00406400(void)
{
longlong lVar1;
lVar1 = __ftol();
return lVar1;
}
结果里能看到一次转换调用。继续阅读下面的指令,就能找出输入,以及传入这个调用前的计算。
用 REA 回答三个问题
-
找回缺失的输入
编程助手→ REA ·analyze_function{"procedure": "0x406400"}REA →编程助手·指令摘录0x406409: MOV EAX, dword ptr [EBP + 0x8] 0x40640c: MOV dword ptr [EBP + -0xc], EAX 0x40640f: FILD dword ptr [EBP + -0xc]函数结果同时提供伪代码、指令和调用方。这里的
[EBP+8]显示了反编译结果中缺失的整数输入。 -
追踪调用方,理解输入含义
编程助手→ REA ·analyze_function{"procedure": "0x411f40"}REA →编程助手·调用方摘录0x411f5b: ADD EAX, 0x14 0x411f5e: PUSH EAX 0x411f5f: CALL 0x00406400REA 在第一次结果中就给出了这个调用方。调用前的指令将
tile_x乘以 30,再加上 20,得到砖块的屏幕坐标20 + 30 × tile_x。 -
读取内存地址后面的值
助手 → REA · 两次read_bytes请求{"address": "0x420068", "length": 16} {"address": "0x4210a0", "length": 8}REA 字节结果 · 双精度浮点数解码000000000000f93f → 1.5625 0000000000407f40 → 500.0 000000000000f03f → 1.0返回的字节确定了两个运算常量,以及声像缩放系数的初始值。助手由此得到编写 C 表达式所需的数值。
从 REA 分析结果到 C 代码
下面分五步展示计算。点击每一步,原始指令和对应的 C 代码行会同时高亮。
0x406409: MOV EAX, dword ptr [EBP + 0x8]
0x40640c: MOV dword ptr [EBP + -0xc], EAX
0x40640f: FILD dword ptr [EBP + -0xc]
0x406415: FMUL double ptr [0x00420068]
0x40641e: FSUB double ptr [0x00420070]
0x406427: FMUL double ptr [0x004210a0]
0x406430: CALL 0x0041678c
0x40643e: RET
DxBallInt dxball_screen_pan(DxBallInt x)
{
double pan;
pan = (double)x;
pan = pan * 1.5625;
pan = pan - 500.0;
pan = pan * dxball_pan_scale;
return (DxBallInt)pan;
}
01 · 读取输入。 REA 返回的指令读取 [EBP+8],再通过 FILD 加载这个整数。用 REA 追踪调用方,就能确定 C 参数 x 表示屏幕坐标。
02 · 乘以 1.5625。 FMUL 读取 0x420068 处的双精度浮点数。REA 的 read_bytes 返回的字节解码后是 1.5625,C 代码使用相同常量。
03 · 减去 500.0。 FSUB 读取 0x420070 处的双精度浮点数。REA 返回的字节解码后是 500.0,让计算的中心对应屏幕坐标 320。
04 · 乘以存储的缩放系数。 第二条 FMUL 读取 0x4210a0。这个全局变量在重建源码中名为 dxball_pan_scale。REA 的第二次字节读取确认它的初始值是 1.0。
05 · 返回整数。 0x406430 处的调用进入 __ftol,REA 返回的被调用函数信息确认了这一点。C 代码把结果转换为 DxBallInt,再与原始 x86 函数比较,验证返回值。
这段汇编节选包含输入、运算和返回指令。下面的完整列表还保留了中间的存储操作,以及函数开头和结尾的处理。
完整汇编与相关证据
0x406400: PUSH EBP
0x406401: MOV EBP, ESP
0x406403: SUB ESP, 0xc
0x406406: PUSH EBX
0x406407: PUSH ESI
0x406408: PUSH EDI
0x406409: MOV EAX, dword ptr [EBP + 0x8]
0x40640c: MOV dword ptr [EBP + -0xc], EAX
0x40640f: FILD dword ptr [EBP + -0xc]
0x406412: FST double ptr [EBP + -0x8]
0x406415: FMUL double ptr [0x00420068]
0x40641b: FST double ptr [EBP + -0x8]
0x40641e: FSUB double ptr [0x00420070]
0x406424: FST double ptr [EBP + -0x8]
0x406427: FMUL double ptr [0x004210a0]
0x40642d: FST double ptr [EBP + -0x8]
0x406430: CALL 0x0041678c
0x406435: JMP 0x0040643a
0x40643a: POP EDI
0x40643b: POP ESI
0x40643c: POP EBX
0x40643d: LEAVE
0x40643e: RET
调用方传入什么
0x411f40 处的砖块碰撞函数把格子坐标乘以 30,再加上 20,然后在调用前将结果压栈:
0x411f50: MOV EAX, dword ptr [EBP + 0x8]
0x411f53: ADD EAX, EAX
0x411f55: LEA EAX, [EAX + EAX*0x2]
0x411f58: LEA EAX, [EAX + EAX*0x4]
0x411f5b: ADD EAX, 0x14
0x411f5e: PUSH EAX
0x411f5f: CALL 0x00406400
0x411f64: ADD ESP, 0x4
读取数据确认了什么
| 地址 | 数值 | 用于 |
|---|---|---|
0x420068 |
1.5625 |
第一次乘法 |
0x420070 |
500.0 |
减法 |
0x4210a0 |
1.0 最初 |
存储的声像缩放系数 |
使用 REA 4.1.0 记录,分析目标为通过哈希确定的 DX-Ball 1.07 文件。链接的调查笔记保留了 Evidence ID。
验证还原的函数
还原的函数通过了两项检查:返回值与原版一致,编译后的字节与原始可执行文件一致。
3,205 个行为测试用例
测试执行原始 x86 函数,将返回值与重建的 C 函数比较。测试覆盖 0 到 640 的所有整数位置,并分别使用五个声像缩放系数:0、0.5、1、20 和 −1。
63 字节完全匹配
C 函数使用指定的 VC4.0 工具链编译。应用经过检查的重定位,并核对引用的常量后,编译结果与原始函数的全部字节一致。
缩放系数为 1 时,计算在屏幕左边缘返回 −500,中心返回 0,右边缘返回 500。接下来要将这个值接入游戏的声音后端。
继续调查
粒子更新、爆炸队列和奖励生成也使用同样的方法:检查相关函数,还原状态与依赖,再把实现和原版比较。
如果你也想分析这个声像函数,先按 DX-Ball 项目的说明配置环境,并准备匹配的原始文件。然后运行:
scripts/rea function original/DXBALL.EXE 0x00406400 \
--snapshot .analysis/rea/dxball.snapshot.json --json
- DX-Ball 仓库当前源码、进度和本地配置。
- REA 分析流程追踪这段计算,再用同样方法分析自己的二进制文件。
- 粒子与奖励生成进一步分析状态、队列和像素写入。