From 9f0724dcd25a06c5674d30d045dc485a341253b1 Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 6 Nov 2026 16:19:25 +0100 Subject: Hooks: central vtable install with restore support Move vtable patching into hooks.cpp: one call clones the table, validates every slot, records the originals and installs the clone. Installs happen from the setup, game and paint threads, so the bookkeeping is mutex-protected. Client, CInput, IVModelRender, IStudioRender and ISurface all use it, and hooks_restore_all() can now put every interface back (previously impossible). --- src/cinput.cpp | 22 +++++++++------------- src/hl2h.cpp | 44 +++++++++++++++++++------------------------ src/hl2h.h | 1 + src/hooks.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/hooks.h | 28 ++++++++++++++++++++++++++++ src/studio.cpp | 44 +++++++++++++++++++++---------------------- 6 files changed, 138 insertions(+), 60 deletions(-) create mode 100644 src/hooks.cpp create mode 100644 src/hooks.h (limited to 'src') diff --git a/src/cinput.cpp b/src/cinput.cpp index 45e2b04..9cad30d 100644 --- a/src/cinput.cpp +++ b/src/cinput.cpp @@ -159,22 +159,18 @@ static void hkdInputCreateMove(void *self, int seq, float frametime, static bool install_input_hook() { if (!g_input) return false; - void **vt = VTABLE((void *)g_input); - if (!vt) + if (!*(void ***)g_input) return false; - constexpr size_t N = 64; - void **nvt = vtable_clone((void *)g_input, N); - if (!nvt) { - logf("CInput vtable clone failed"); + VtablePatch patches[] = { + {3, (void *)&hkdInputCreateMove, + reinterpret_cast(&oInputCreateMove)}, + {4, (void *)&hkdInputExtraMouseSample, + reinterpret_cast(&oInputExtraMouseSample)}, + }; + if (!hooks_install_vtable((void *)g_input, 64, patches, 2)) { + logf("CInput vtable patch failed"); return false; } - oInputCreateMove = (InputCreateMoveFn)vt[3]; - nvt[3] = (void *)&hkdInputCreateMove; - if (N > 4) { - oInputExtraMouseSample = (InputExtraMouseFn)vt[4]; - nvt[4] = (void *)&hkdInputExtraMouseSample; - } - VTABLE((void *)g_input) = nvt; logf("CInput CreateMove hooked: slot 3 orig=%p; ExtraMouseSample slot 4 " "orig=%p", (void *)oInputCreateMove, (void *)oInputExtraMouseSample); diff --git a/src/hl2h.cpp b/src/hl2h.cpp index 6697778..354b3e6 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp @@ -1125,20 +1125,17 @@ static bool hkdCreateMove(void *self, int seq, float frametime, bool active) { // DrawPrintText (slot 22) hooked as a fallback text-draw trigger. static int isurface_hooked = 0; if (!isurface_hooked && g_surface) { - void **svt = VTABLE(g_surface); - constexpr size_t VT_SIZE = 256; - void **newsvt = vtable_clone(g_surface, VT_SIZE); - if (!newsvt) { - isurface_hooked = 1; // don't retry every tick - logf("ISurface hook: vtable unavailable"); + VtablePatch patches[] = { + {22, (void *)&hkdDrawPrintText, + reinterpret_cast(&oDrawPrintText)}, + {114, (void *)&hkdPaintTraverseEx, + reinterpret_cast(&oPaintTraverseEx)}, + }; + isurface_hooked = 1; // one attempt only + if (!hooks_install_vtable(g_surface, 256, patches, 2)) { + logf("ISurface hook: vtable patch failed"); return ret; } - oDrawPrintText = (DrawPrintTextFn)svt[22]; - newsvt[22] = (void *)&hkdDrawPrintText; - oPaintTraverseEx = (PaintTraverseExFn)svt[114]; - newsvt[114] = (void *)&hkdPaintTraverseEx; - VTABLE(g_surface) = newsvt; - isurface_hooked = 1; logf("ISurface hooked: DrawPrintText slot 22 orig=%p, PaintTraverseEx slot 114 orig=%p", (void *)oDrawPrintText, (void *)oPaintTraverseEx); } @@ -1147,21 +1144,18 @@ static bool hkdCreateMove(void *self, int seq, float frametime, bool active) { } static void install_hook() { - void **vt = VTABLE(g_iface.client); - constexpr size_t SLOTS = 256; - void **newvt = vtable_clone(g_iface.client, SLOTS); - if (!newvt) { - logf("hook install: client vtable unavailable"); + VtablePatch patches[] = { + {21, (void *)&hkdCreateMove, reinterpret_cast(&oCreateMove)}, + {35, (void *)&hkdFrameStageNotify, + reinterpret_cast(&oFrameStageNotify)}, + }; + if (!hooks_install_vtable(g_iface.client, 256, patches, 2)) { + logf("hook install: client vtable patch failed"); return; } - oCreateMove = (CreateMoveFn)vt[21]; - newvt[21] = (void *)&hkdCreateMove; - oFrameStageNotify = (FrameStageFn)vt[35]; - newvt[35] = (void *)&hkdFrameStageNotify; - VTABLE(g_iface.client) = newvt; - logf("hook installed: CreateMove %p -> %p (orig %p), FrameStageNotify %p " - "-> %p (orig %p)", - vt[21], (void *)&hkdCreateMove, (void *)oCreateMove, vt[35], + logf("hook installed: CreateMove -> %p (orig %p), FrameStageNotify -> %p " + "(orig %p)", + (void *)&hkdCreateMove, (void *)oCreateMove, (void *)&hkdFrameStageNotify, (void *)oFrameStageNotify); } diff --git a/src/hl2h.h b/src/hl2h.h index f1a1ea0..541b31b 100644 --- a/src/hl2h.h +++ b/src/hl2h.h @@ -11,6 +11,7 @@ #include #include +#include "hooks.h" #include "overlay.h" // Shared float-precision constant. More digits than a float can hold are diff --git a/src/hooks.cpp b/src/hooks.cpp new file mode 100644 index 0000000..215ecfd --- /dev/null +++ b/src/hooks.cpp @@ -0,0 +1,59 @@ +// hooks.cpp — vtable hook installation and teardown bookkeeping. +#include "hl2h.h" + +#include +#include + +namespace { + +struct Installed { + void *iface; + void **old_vt; +}; + +std::mutex g_hook_mu; +std::vector g_installed; + +} // namespace + +bool hooks_install_vtable(void *iface, size_t slots, const VtablePatch *patches, + int count) { + if (!iface || !patches || count <= 0) + return false; + void **old = *(void ***)iface; + if (!old) + return false; + for (int i = 0; i < count; i++) { + if (patches[i].slot < 0 || (size_t)patches[i].slot >= slots) { + logf("hooks: slot %d out of range (table has %u)", patches[i].slot, + (unsigned)slots); + return false; + } + } + + void **clone = vtable_clone(iface, slots); + if (!clone) + return false; + for (int i = 0; i < count; i++) { + if (patches[i].original) + *patches[i].original = old[patches[i].slot]; + clone[patches[i].slot] = patches[i].replacement; + } + + std::lock_guard lock(g_hook_mu); + *(void ***)iface = clone; + g_installed.push_back({iface, old}); + return true; +} + +void hooks_restore_all() { + std::lock_guard lock(g_hook_mu); + for (auto it = g_installed.rbegin(); it != g_installed.rend(); ++it) + *(void ***)it->iface = it->old_vt; + g_installed.clear(); +} + +int hooks_installed_count() { + std::lock_guard lock(g_hook_mu); + return (int)g_installed.size(); +} diff --git a/src/hooks.h b/src/hooks.h new file mode 100644 index 0000000..084698f --- /dev/null +++ b/src/hooks.h @@ -0,0 +1,28 @@ +#ifndef HL2H_HOOKS_H +#define HL2H_HOOKS_H + +#include + +// One patched vtable slot. `original` receives the untouched function pointer +// (it may be null if the caller does not need it). +struct VtablePatch { + int slot; + void *replacement; + void **original; +}; + +// Clone `iface`'s vtable (preserving the Itanium ABI header), apply all +// patches, and install the clone on the object. All slots belong to the same +// interface. Returns false and leaves the object untouched if cloning fails or +// any slot is out of range. +bool hooks_install_vtable(void *iface, size_t slots, const VtablePatch *patches, + int count); + +// Restore every patched interface's original vtable in reverse install order. +// Intended for tests and shutdown; the cheat normally lives as long as the +// process does. +void hooks_restore_all(); + +int hooks_installed_count(); + +#endif diff --git a/src/studio.cpp b/src/studio.cpp index 8b0a710..275bfba 100644 --- a/src/studio.cpp +++ b/src/studio.cpp @@ -597,15 +597,17 @@ void chams_init() { (void *)g_force_override, (void *)g_orig_model_exec); return; } - void **nvt = vtable_clone(g_iface.modelrender, 32); - if (!nvt) { - logf("chams: vtable clone failed"); + VtablePatch patches[] = { + {1, (void *)&hkdForceOverride, + reinterpret_cast(&g_real_force_override)}, // track forced passes + {19, (void *)&hkdModelExecute, + reinterpret_cast(&g_orig_model_exec)}, + }; + if (!hooks_install_vtable(g_iface.modelrender, 32, patches, 2)) { + logf("chams: vtable patch failed"); return; } - nvt[1] = (void *)&hkdForceOverride; // track engine-forced passes - nvt[19] = (void *)&hkdModelExecute; g_modelrender = g_iface.modelrender; - *(void ***)g_modelrender = nvt; logf("chams: IVModelRender %p hooked (draw=%p override=%p)", g_modelrender, (void *)g_orig_model_exec, (void *)g_force_override); } @@ -641,24 +643,22 @@ void studio_props_init() { logf("studio: interface has no vtable"); return; } - void **vt = vtable_clone(sr, 49); - if (!vt) { - logf("studio: vtable clone failed"); + VtablePatch patches[] = { + {27, (void *)&hook_set_color, + reinterpret_cast(&g_orig_set_color)}, + {28, (void *)&hook_set_alpha, + reinterpret_cast(&g_orig_set_alpha)}, + {29, (void *)&hook_draw_model, + reinterpret_cast(&g_orig_draw_model)}, + {30, (void *)&hook_draw_static_prop, + reinterpret_cast(&g_orig_draw_static)}, + {46, (void *)&hook_draw_array, + reinterpret_cast(&g_orig_draw_array)}, + }; + if (!hooks_install_vtable(sr, 49, patches, 5)) { + logf("studio: vtable patch failed"); return; } - - g_orig_set_color = (SetColorFn)vt[27]; - g_orig_set_alpha = (SetAlphaFn)vt[28]; - g_orig_draw_model = (DrawModelFn)vt[29]; - g_orig_draw_static = (DrawStaticFn)vt[30]; - g_orig_draw_array = (DrawArrayFn)vt[46]; - vt[27] = (void *)&hook_set_color; - vt[28] = (void *)&hook_set_alpha; - vt[29] = (void *)&hook_draw_model; - vt[30] = (void *)&hook_draw_static_prop; - vt[46] = (void *)&hook_draw_array; - - *(void ***)sr = vt; g_installed = true; logf("studio: IStudioRender %p hooked (color=%p draw=%p static=%p " "array=%p)", sr, (void *)g_orig_set_color, (void *)g_orig_draw_model, -- cgit v1.2.3