DS2Revival/mods/common/vtable_hook.h

73 lines
2.8 KiB
C++

// One hooked vtable slot, for every vtable the hooked objects turn up with.
//
// Each vtable keeps its own previous entry, so a hook always forwards to what that object's table had before us:
// a second device class (or a driver that copies its table) cannot make the first one's hook call the wrong
// function, and a table copied from one we already hooked (it holds our hook already) is not hooked again - its
// "previous entry" would be our own hook, and the two would call each other until the stack runs out.
#pragma once
#include <windows.h>
namespace ds2 {
class VtableHook {
public:
VtableHook(int index, void* fn) : index_(index), fn_(fn) {}
// Hooks obj's vtable unless this one is done already (A, B, then A again is still done once). A table we hooked whose
// slot has been rewritten since (the native Direct3D 9 on current AMD drivers rewrites a new device's table for its
// first frames) gets our function put back, keeping the predecessor recorded at the first install.
void Install(void* obj) {
void** vtbl = *static_cast<void***>(obj);
if (Find(vtbl)) {
if (vtbl[index_] != fn_) {
DWORD old;
if (VirtualProtect(&vtbl[index_], sizeof(void*), PAGE_READWRITE, &old)) {
vtbl[index_] = fn_;
VirtualProtect(&vtbl[index_], sizeof(void*), old, &old);
}
}
return;
}
if (n_ == kMax) return; // never happens with D3D9; leave it unhooked
if (vtbl[index_] == fn_) { // a copy of a table we hooked: same predecessor
if (n_ == 0) return;
entries_[n_++] = {vtbl, entries_[n_ - 1].orig};
return;
}
DWORD old;
if (!VirtualProtect(&vtbl[index_], sizeof(void*), PAGE_READWRITE, &old)) return;
entries_[n_++] = {vtbl, vtbl[index_]};
vtbl[index_] = fn_;
VirtualProtect(&vtbl[index_], sizeof(void*), old, &old);
}
// The entry obj's vtable had before our hook. Only called from the hook, so obj's table is one of ours;
// the fallback (the last table hooked) covers an object whose table pointer was swapped after the call.
template <class F>
F Original(void* obj) const {
const Entry* e = Find(*static_cast<void***>(obj));
return reinterpret_cast<F>(e ? e->orig : entries_[n_ - 1].orig);
}
bool Installed() const { return n_ > 0; }
private:
struct Entry {
void** vtbl;
void* orig;
};
static constexpr int kMax = 16;
const Entry* Find(void** vtbl) const {
for (int i = 0; i < n_; ++i)
if (entries_[i].vtbl == vtbl) return &entries_[i];
return nullptr;
}
int index_;
void* fn_;
Entry entries_[kMax] = {};
int n_ = 0;
};
} // namespace ds2