// Hook a single COM vtable slot by overwriting its function pointer; the original is called // through the saved pointer. We use this instead of SafetyHook's inline hooks for COM methods // because, on x86, MMDevApi/AudioSes/DirectInput prologues use dynamic stack alignment // (`and esp,-8`) with EBP-relative argument access, which SafetyHook's trampoline relocation // mishandles -> the original runs with garbage args and faults. Swapping the vtable entry leaves // the original code untouched, so it runs with a pristine stack regardless of prologue shape. // Every instance of a COM coclass shares one vtable, so a single swap intercepts all of them. // See the project's stdcall-x86 note. Shared by the audio render-hook (audio_hook.cpp) and the // input-side DirectInput hook (mkb_hook.cpp). #pragma once #include namespace coop::hook { class VtableHook { public: bool install(void* com_object, unsigned index, void* detour) { if (m_vtable != nullptr) { return true; // already installed (shared vtable covers every instance) } auto** vtable = *reinterpret_cast(com_object); DWORD old_protect = 0; if (!VirtualProtect(&vtable[index], sizeof(void*), PAGE_READWRITE, &old_protect)) { return false; } m_original = vtable[index]; vtable[index] = detour; // aligned pointer store -> atomic vs. a concurrent caller VirtualProtect(&vtable[index], sizeof(void*), old_protect, &old_protect); m_vtable = vtable; m_index = index; return true; } void remove() { if (m_vtable == nullptr) { return; } DWORD old_protect = 0; if (VirtualProtect(&m_vtable[m_index], sizeof(void*), PAGE_READWRITE, &old_protect)) { m_vtable[m_index] = m_original; VirtualProtect(&m_vtable[m_index], sizeof(void*), old_protect, &old_protect); } m_vtable = nullptr; // Keep m_original valid: a detour already running on the game's thread may still call // original() after we restore the slot. The original lives in the loaded module, so the // pointer stays valid; a re-install re-reads it. m_index = 0; } template Fn original() const { return reinterpret_cast(m_original); } explicit operator bool() const { return m_vtable != nullptr; } private: void** m_vtable = nullptr; unsigned m_index = 0; void* m_original = nullptr; }; } // namespace coop::hook