游戏逆向实战:我会梯云纵
引言:从手残到空中舞者
作为一个又菜又爱玩的玩家,我经常在游戏里遇到复杂地形时频频扑街,严重影响心情。尤其是我钟爱的平台跳跃类和银河恶魔城类游戏,往往几个难度极高的跳跃点就能硬控我一整周。
有没有什么办法能让跳跃变得简单一些?
今天我们就来讨论如何让游戏主角学会武当轻功 "梯云纵" ------也就是 二段跳 ,让手残的我也能顺利通关。
研究对象依然是 Cheat Engine 自带的 Tutorial Game 3。我们将通过逆向分析二进制代码,找到控制跳跃的核心逻辑并修改,最终实现下面这样在空中二次起跳的效果:

第一步:从按键状态函数入手
最直接的想法是:找到跳跃按键的处理逻辑,分析它判断了哪些条件,然后有针对性地修改。
说干就干,祭出大杀器 ------ IDA Pro 。
检查导入函数表
逆向分析二进制文件时,第一步通常是查看 导入函数表(Imports) ,从中寻找合适的切入点。
在 Windows 系统中,键盘相关的 API 大多集中在 User32.dll 里。我们先去看看有没有熟悉的按键相关函数:

果然,在导入表中找到了按键相关函数 GetKeyState 。双击进入反汇编视图,使用交叉引用(Xrefs) 看看谁在调用它:

函数调用跟踪
顺着调用链一路追踪,我们发现了一个功能为IsKeyPressed 的函数(函数名是笔者自己加的),用于判断某个键是否被按下。
继续查看 IsKeyPressed 的交叉引用,我们来到了一个非常可疑的函数 ------ 它包含了大量的按键检测逻辑:
C
if ( !IsKeyPressed(a1, 87) || !IsKeyPressed(a1, 73) || !IsKeyPressed(a1, 78) ) // WIN
{
if ( IsKeyPressed(a1, 68) && IsKeyPressed(a1, 73) && IsKeyPressed(a1, 69) ) // DIE
{
// 调用虚函数偏移 288
LOBYTE(v8) = (*(__int64 (__fastcall **)(_QWORD))(**(_QWORD **)(a1 + 40) + 288LL))(*(_QWORD *)(a1 + 40));
return v8;
}
if ( IsKeyPressed(a1, 70) && IsKeyPressed(a1, 67) && IsKeyPressed(a1, 75) ) // FCK
{
// ...
}
// 继续处理常规按键...
}
啊哈!居然发现了隐藏的作弊码!
- W + I + N → WIN(直接胜利)
- D + I + E → DIE(自杀或重置)
- F + C + K → FCK(貌似处理用户注入的代码,没仔细研究)
当然,我们的目标是二段跳,作弊码先放一边。继续往下看:
C
if ( !a3 )
{
sub_100039ED0();
if ( keyCode < 0x20u )
goto LABEL_52;
if ( keyCode == 32 ) // 空格 → 跳跃
goto LABEL_36;
if ( keyCode != 37 )
{
if ( keyCode == 38 ) // 上箭头 ↑ → 跳跃(我们的目标!)
goto LABEL_36;
if ( keyCode == 39 ) // 右箭头 →
goto LABEL_34;
if ( keyCode != 65 ) // 'A'
{
if ( keyCode != 68 ) // 'D'
{
if ( keyCode != 80 ) // 'P'
{
if ( keyCode != 87 ) // 'W'
goto LABEL_52;
LABEL_36:
*(_BYTE *)(a1 + 116) = 1;
if ( !*(_BYTE *)(a1 + 124) )
*(_DWORD *)(a1 + 120) = 0x3FB9999A;
goto LABEL_52;
}
// 'P' 键处理...
}
// 'D' 键处理...
}
// 'A' 键处理...
}
// 左箭头处理...
}
其中 keyCode == 38 就是我们的目标 ------ 38 是方向键"上"的虚拟键码(VK_UP) ,也是游戏中的跳跃键(W 键和空格同样会触发跳跃)。
按下跳跃键后,程序跳转到 LABEL_36 :
C
LABEL_36:
*(_BYTE *)(a1 + 116) = 1; // 设置"跳跃请求"标志
if ( !*(_BYTE *)(a1 + 124) ) // 关键!只有在"未跳跃"时才执行
*(_DWORD *)(a1 + 120) = 0x3FB9999A; // 设置跳跃力度(1.45f)
goto LABEL_52;
第二步:分析跳跃逻辑
如果你也是游戏开发者,那么一定清楚跳跃逻辑的伪代码大概长这样:
scss
if ( 跳跃按键按下 )
{
if ( !是否在空中 ) // 只有在地面才能跳
{
是否在空中 = true;
修改Y轴速度;
}
}
对照伪代码我们的发现:
| 偏移 | 含义 | 推测 |
|---|---|---|
| a1 + 116 | 跳跃请求标志 | 按键按下时设为 1,主循环检测后执行跳跃 |
| a1 + 124 | 是否在空中 | 0 = 在地面,1 = 在空中 → 这就是我们要改的目标! |
| a1 + 120 | 跳跃力度参数 | 0x3FB9999A = 1.45f(单精度浮点数) |
结论 :a1 + 124 就是阻止二段跳的"元凶"。只要让程序不再检查这个标志,二段跳就实现了。
第三步:动手术
3.1 定位关键指令
在 IDA 中找到判断 a1 + 124 的汇编指令:

图中可以看到,关键跳转指令位于地址 1000434EC :
asm
loc_1000434EC:
cmp byte ptr [rcx+7Ch], 0 ; 检查 a1+124 是否为 0
jnz loc_100043598 ; 如果不为 0(在空中),跳过跳跃逻辑
3.2 用 Cheat Engine 打补丁
打开 Cheat Engine,附加游戏进程,跳转到地址 1000434EC:

我们的目标是让程序永远认为"在地面" ,即无论 a1+124 是什么值,都不跳转。
最简单的方法:把 jnz(条件跳转)替换为 NOP(无操作) 。
在 CE 中右键 → "Replace with code" → 输入 NOP,然后确认:

3.3 验证效果
回到游戏,起跳,在空中再次按下跳跃键------ 
成功了!
现在角色可以在空中任意跳跃,真正的"梯云纵"神功已成。再复杂的地形也拦不住我了!
总结与思考
| 步骤 | 操作 | 工具 |
|---|---|---|
| ① | 定位按键处理函数 | IDA Pro + 交叉引用 |
| ② | 找到跳跃逻辑(LABEL_36) | IDA Pro 伪代码分析 |
| ③ | 识别"空中判断"标志(a1+124) | 代码逻辑推理 |
| ④ | 定位关键跳转指令(1000434EC) | IDA Pro 汇编视图 |
| ⑤ | 将 jnz 替换为 NOP | Cheat Engine 内存补丁 |
核心收获:
- 二段跳的本质是绕过"是否在空中"的判断
- jnz → NOP 是绕过条件判断的通用技巧
- 同样的方法可以用于修改"冲刺次数"、"技能冷却"等限制
下期预告
这次我们让角色学会了二段跳,下次挑战更进阶的修改------
《突破限制:修改空洞骑士护符槽,实现"全都要"》
如果你也喜欢这类游戏逆向的内容,欢迎在评论区告诉我你想修改哪款游戏的什么机制,说不定下期就是为你量身定制的!
感谢阅读,我们下期见!