跳转至

编写回调和蹦床

即使你可以从 VF 表获得 EndScene() 地址,EndScene() 函数实际上并不遵循 __thiscall 约定。Direct3D 类是 C API 周围的简单包装器,所有成员函数调用都转发到把类实例作为第一个参数的 __stdcall 函数。这意味着你的蹦床只需从栈抓取设备,把它传给回调,然后跳回 EndScene()。回调只需在返回蹦床前移除跳转钩子。

这个跳转钩子的回调和蹦床的最终代码看起来像这样:

LPDIRECT3DDEVICE9 discoveredDevice;
DWORD __stdcall reportInitEndScene(LPDIRECT3DDEVICE
9 device)
{
    discoveredDevice = device;
    unhookWithJump(EndSceneAddress, originals);
    return EndSceneAddress;
}
__declspec(naked) void endSceneTrampoline()
{
    __asm {
        MOV EAX, DWORD PTR SS:[ESP + 0x4]
        PUSH EAX // 把设备交给回调
        CALL reportInitEndScene
        JMP EAX // 跳转到 EndScene 的开头
    }
}

用 hookWithJump() 函数,你可以在 EndScene() 上放置一个跳转钩子,调用 endSceneTrampoline() 函数。游戏设备调用 EndScene() 函数时,蹦床函数调用 reportInitEndScene() 函数①。reportInitEndScene() 函数把捕获的设备指针存储到名为 discoveredDevice 的全局变量,通过调用 unhookWithJump() 移除钩子,并把 EndScene() 的地址返回给蹦床。最后,蹦床直接跳到 EAX,它会持有报告函数返回的地址。

 你可以用跳转钩子完全跳过我将展示的 VF 表钩子,但在常被钩住的 API 函数上使用「笨」跳转钩子非常不可靠。只用跳转钩子持续获得良好结果需要专业钩子库我更愿意教你完全靠自己完成。

此时,剩下要做的就是钩住 discoveredDevice 的 VF 表来破解游戏。接下来的两节将带你完成对 EndScene() 和 Reset() 函数的钩子,如果你想获得稳定钩子,这两个是必需的。