跳转钩住 EndScene()¶
由于每个设备都会在每帧结束时调用 EndScene(),你可以用跳转钩子钩住 EndScene(),并从你的钩子回调拦截游戏设备。独特的设备可能有独特 VF 表,但不同的表仍然指向相同的函数,所以你可以在任意设备的 VF 表中找到 EndScene() 的地址。使用标准 Direct3D API 调用,你可以像这样创建自己的设备:
LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION);
if (!pD3D) return 0;
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
LPDIRECT3DDEVICE9 device;
HRESULT res = pD3D->CreateDevice(
D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &device);
if (FAILED(res)) return 0;
解释 Direct3D 中一切工作原理超出了本书范围,所以只要知道你可以复制这段代码,创建包含 EndScene() 函数作为成员的 Direct3D 设备即可。EndScene() 地址位于 device 的 VF 表索引 42 (参阅「设备、Direct3D 和 VF 钩子的含义」框了解如何找到该索引),你可以用第一百五十九页「使用 VF 表钩子」中 VF 表钩子代码的一个子集读取它,如下所示:
DWORD getVF(DWORD classInst, DWORD funcIndex)
{
DWORD VFTable = readMemory<DWORD>(classInst);
DWORD hookAddress = VFTable + funcIndex *
sizeof(DWORD);
return readMemory<DWORD>(hookAddress);
}
DWORD EndSceneAddress = getVF((DWORD)device, 42);
获得地址后,你的发现设备已经完成使命,可以用对 Release() 函数的调用销毁它:
有了 EndScene() 的地址,你就可以开始考虑如何在内存中放置钩子。但由于你只有函数地址,唯一的选择是在函数顶部放置跳转钩子。