跳转至

为 EndScene() 编写钩子

EndScene() 上的钩子很有用,因为它允许你在完整帧即将渲染前拦截它;你可以有效地在游戏循环内执行自己的渲染代码。正如你在第一百七十八页「跳转钩住 EndScene()」中定位这个函数地址时看到的,这个函数位于 VF 表索引 42。你可以用 VF 钩子钩住 EndScene(),如下所示:

typedef HRESULT (WINAPI* _endScene)(LPDIRECT3DDEVIC
E9 pDevice);
_endScene origEndScene =
    (_endScene)hookVF((DWORD)discoveredDevice, 42, 
(DWORD)&myEndScene);
HRESULT WINAPI myEndScene(LPDIRECT3DDEVICE9 
pDevice)
{
    // 在这里绘制你自己的东西
    return origEndScene(pDevice);
}

这段代码用第一百五十九页「使用 VF 表钩子」中的 hookVF() 函数,在 discoveredDevice 的索引 42 处钩住 EndScene(),用 myEndScene() 作为回调函数。直接的 Direct3D 设备偶尔会重新修补自己的 VF 表,恢复原始函数地址。这通常发生在 EndScene() 函数内部,意味着你还必须在调用原始 EndScene() 函数后重新修补 VF 表。你可以对这个钩子做一些更改来处理这一点,如清单 8-14 所示。

_endScene origEndScene = NULL;
void placeHooks()
{
    auto ret = hookVF((DWORD)discoveredDevice, 42, 
(DWORD)&myEndScene);
    if (ret != (DWORD)&myEndScene) // 不要指向你的钩子
}
placeHooks();
HRESULT WINAPI myEndScene(LPDIRECT3DDEVICE9 
pDevice)
{
    // 在这里绘制你自己的东西
    auto ret = origEndScene(pDevice);
    placeHooks(); // 更新钩子
    return ret;
}

清单 8-14:钩住 EndScene() 的最终代码

放置钩子的代码已移入名为 placeHooks() 的函数,这样它可以轻松多次调用。回调函数仍然把调用转发给原始函数,但它确保在返回前调用 placeHooks()。这确保钩子始终处于活动状态,即使原始 EndScene() 函数移除了它。

另一点值得注意的是,只要 hookVF() 返回的地址不是 myEndScene() 函数的地址,placeHooks() 就会在每次替换钩子时更新 origEndScene() 的地址。这做了两件不同的事情。首先它允许其他应用程序钩住 EndScene() 而不踩到它们的脚趾,因为它会把 origEndScene() 更新为 VF 表中看到的任何内容。第二它确保 origEndScene() 的值永远不会是我们的回调地址,防止潜在的死循环。否则可能发生死循环,因为 origEndScene() 并不总是修复设备的 VF 表,意味着 placeHooks() 可能在 VF 表仍然包含 myEndScene() 函数时被调用。