跳转至

使用 VF 表钩子

调用约定确定、回调骨架就绪后,是时候进入有趣部分:实际使用 VF 表钩子。指向类 VF 表的指针是每个类实例的第一个成员,所以放置 VF 表钩子只需要类实例地址和要钩函数的索引。利用这两条信息,你只需要少量代码就可以放置钩子。这是一个示例:

DWORD hookVF(DWORD classInst, DWORD funcIndex, 
DWORD newFunc)
{
    DWORD VFTable =  readMemory<DWORD>(classInst);
    DWORD hookAt = VFTable + funcIndex * 
sizeof(DWORD);
        protectMemory<DWORD>(hookAt, 
PAGE_READWRITE);
    DWORD originalFunc = readMemory<DWORD>(hookAt);
    writeMemory<DWORD>(hookAt, newFunc);
    protectMemory<DWORD>(hookAt, oldProtection);
    return originalFunc;
}

hookVF() 函数通过读取类实例的第一个成员①并存储在 VFTable 中找到 VF 表。由于 VF 表只是DWORD 大小地址的数组,这段代码通过把函数在 VF 表中的索引(本例中的 funcIndex)乘以 DWORD 的大小(即 4),并把结果加到 VF 表地址上来找到函数地址。从那里,hookVF() 的行为类似于调用钩子:它通过设置合适的保护确保内存可正确访问,存储原始函数地址供以后使用,写入新函数地址,最后恢复原始内存保护。

你通常会钩住游戏实例化的类的 VF 表,对 VF 表钩子调用 hookVF() 这样的函数看起来像这样:

DWORD origVFFunction =
    hookVF(classInstAddr, 0, 
(DWORD)&someNewVFFunction);

像往常一样,你需要提前找到 classInstAddr 和 funcIndex 参数。

VF 表钩子有一些非常小众的用例,而且很难找到正确的类指针和函数。因此,与其展示牵强的用例,我会在第一百七十五页「把跳转钩子和 VF 钩子应用到 Direct3D」中回到 VF 表钩子,等我讨论了其他类型的钩子再说。

如果你想在继续阅读前玩玩 VF 钩子,给本书资源文件中示例类添加新的虚函数,练习钩住它们。你甚至可以创建另一个从 someBaseClass 派生的类,在它的虚表上放置钩子,演示如何在继承相同基类的两个类上放置两个完全独立的 VF 钩子。