summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-06 16:19:25 +0100
committersalmonik <salmonik@tuta.io>2026-11-06 16:19:25 +0100
commit9f0724dcd25a06c5674d30d045dc485a341253b1 (patch)
treee7587dd03c0b164113d3b879b18d81fccf332c05 /src
parentChams: leave engine-forced material passes alone (diff)
downloadhl2h-9f0724dcd25a06c5674d30d045dc485a341253b1.tar.gz
hl2h-9f0724dcd25a06c5674d30d045dc485a341253b1.zip
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).
Diffstat (limited to '')
-rw-r--r--src/cinput.cpp22
-rw-r--r--src/hl2h.cpp44
-rw-r--r--src/hl2h.h1
-rw-r--r--src/hooks.cpp59
-rw-r--r--src/hooks.h28
-rw-r--r--src/studio.cpp44
6 files changed, 138 insertions, 60 deletions
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,
159static bool install_input_hook() { 159static bool install_input_hook() {
160 if (!g_input) 160 if (!g_input)
161 return false; 161 return false;
162 void **vt = VTABLE((void *)g_input); 162 if (!*(void ***)g_input)
163 if (!vt)
164 return false; 163 return false;
165 constexpr size_t N = 64; 164 VtablePatch patches[] = {
166 void **nvt = vtable_clone((void *)g_input, N); 165 {3, (void *)&hkdInputCreateMove,
167 if (!nvt) { 166 reinterpret_cast<void **>(&oInputCreateMove)},
168 logf("CInput vtable clone failed"); 167 {4, (void *)&hkdInputExtraMouseSample,
168 reinterpret_cast<void **>(&oInputExtraMouseSample)},
169 };
170 if (!hooks_install_vtable((void *)g_input, 64, patches, 2)) {
171 logf("CInput vtable patch failed");
169 return false; 172 return false;
170 } 173 }
171 oInputCreateMove = (InputCreateMoveFn)vt[3];
172 nvt[3] = (void *)&hkdInputCreateMove;
173 if (N > 4) {
174 oInputExtraMouseSample = (InputExtraMouseFn)vt[4];
175 nvt[4] = (void *)&hkdInputExtraMouseSample;
176 }
177 VTABLE((void *)g_input) = nvt;
178 logf("CInput CreateMove hooked: slot 3 orig=%p; ExtraMouseSample slot 4 " 174 logf("CInput CreateMove hooked: slot 3 orig=%p; ExtraMouseSample slot 4 "
179 "orig=%p", 175 "orig=%p",
180 (void *)oInputCreateMove, (void *)oInputExtraMouseSample); 176 (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) {
1125 // DrawPrintText (slot 22) hooked as a fallback text-draw trigger. 1125 // DrawPrintText (slot 22) hooked as a fallback text-draw trigger.
1126 static int isurface_hooked = 0; 1126 static int isurface_hooked = 0;
1127 if (!isurface_hooked && g_surface) { 1127 if (!isurface_hooked && g_surface) {
1128 void **svt = VTABLE(g_surface); 1128 VtablePatch patches[] = {
1129 constexpr size_t VT_SIZE = 256; 1129 {22, (void *)&hkdDrawPrintText,
1130 void **newsvt = vtable_clone(g_surface, VT_SIZE); 1130 reinterpret_cast<void **>(&oDrawPrintText)},
1131 if (!newsvt) { 1131 {114, (void *)&hkdPaintTraverseEx,
1132 isurface_hooked = 1; // don't retry every tick 1132 reinterpret_cast<void **>(&oPaintTraverseEx)},
1133 logf("ISurface hook: vtable unavailable"); 1133 };
1134 isurface_hooked = 1; // one attempt only
1135 if (!hooks_install_vtable(g_surface, 256, patches, 2)) {
1136 logf("ISurface hook: vtable patch failed");
1134 return ret; 1137 return ret;
1135 } 1138 }
1136 oDrawPrintText = (DrawPrintTextFn)svt[22];
1137 newsvt[22] = (void *)&hkdDrawPrintText;
1138 oPaintTraverseEx = (PaintTraverseExFn)svt[114];
1139 newsvt[114] = (void *)&hkdPaintTraverseEx;
1140 VTABLE(g_surface) = newsvt;
1141 isurface_hooked = 1;
1142 logf("ISurface hooked: DrawPrintText slot 22 orig=%p, PaintTraverseEx slot 114 orig=%p", 1139 logf("ISurface hooked: DrawPrintText slot 22 orig=%p, PaintTraverseEx slot 114 orig=%p",
1143 (void *)oDrawPrintText, (void *)oPaintTraverseEx); 1140 (void *)oDrawPrintText, (void *)oPaintTraverseEx);
1144 } 1141 }
@@ -1147,21 +1144,18 @@ static bool hkdCreateMove(void *self, int seq, float frametime, bool active) {
1147} 1144}
1148 1145
1149static void install_hook() { 1146static void install_hook() {
1150 void **vt = VTABLE(g_iface.client); 1147 VtablePatch patches[] = {
1151 constexpr size_t SLOTS = 256; 1148 {21, (void *)&hkdCreateMove, reinterpret_cast<void **>(&oCreateMove)},
1152 void **newvt = vtable_clone(g_iface.client, SLOTS); 1149 {35, (void *)&hkdFrameStageNotify,
1153 if (!newvt) { 1150 reinterpret_cast<void **>(&oFrameStageNotify)},
1154 logf("hook install: client vtable unavailable"); 1151 };
1152 if (!hooks_install_vtable(g_iface.client, 256, patches, 2)) {
1153 logf("hook install: client vtable patch failed");
1155 return; 1154 return;
1156 } 1155 }
1157 oCreateMove = (CreateMoveFn)vt[21]; 1156 logf("hook installed: CreateMove -> %p (orig %p), FrameStageNotify -> %p "
1158 newvt[21] = (void *)&hkdCreateMove; 1157 "(orig %p)",
1159 oFrameStageNotify = (FrameStageFn)vt[35]; 1158 (void *)&hkdCreateMove, (void *)oCreateMove,
1160 newvt[35] = (void *)&hkdFrameStageNotify;
1161 VTABLE(g_iface.client) = newvt;
1162 logf("hook installed: CreateMove %p -> %p (orig %p), FrameStageNotify %p "
1163 "-> %p (orig %p)",
1164 vt[21], (void *)&hkdCreateMove, (void *)oCreateMove, vt[35],
1165 (void *)&hkdFrameStageNotify, (void *)oFrameStageNotify); 1159 (void *)&hkdFrameStageNotify, (void *)oFrameStageNotify);
1166} 1160}
1167 1161
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 @@
11#include <pthread.h> 11#include <pthread.h>
12#include <unistd.h> 12#include <unistd.h>
13 13
14#include "hooks.h"
14#include "overlay.h" 15#include "overlay.h"
15 16
16// Shared float-precision constant. More digits than a float can hold are 17// 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 @@
1// hooks.cpp — vtable hook installation and teardown bookkeeping.
2#include "hl2h.h"
3
4#include <mutex>
5#include <vector>
6
7namespace {
8
9struct Installed {
10 void *iface;
11 void **old_vt;
12};
13
14std::mutex g_hook_mu;
15std::vector<Installed> g_installed;
16
17} // namespace
18
19bool hooks_install_vtable(void *iface, size_t slots, const VtablePatch *patches,
20 int count) {
21 if (!iface || !patches || count <= 0)
22 return false;
23 void **old = *(void ***)iface;
24 if (!old)
25 return false;
26 for (int i = 0; i < count; i++) {
27 if (patches[i].slot < 0 || (size_t)patches[i].slot >= slots) {
28 logf("hooks: slot %d out of range (table has %u)", patches[i].slot,
29 (unsigned)slots);
30 return false;
31 }
32 }
33
34 void **clone = vtable_clone(iface, slots);
35 if (!clone)
36 return false;
37 for (int i = 0; i < count; i++) {
38 if (patches[i].original)
39 *patches[i].original = old[patches[i].slot];
40 clone[patches[i].slot] = patches[i].replacement;
41 }
42
43 std::lock_guard<std::mutex> lock(g_hook_mu);
44 *(void ***)iface = clone;
45 g_installed.push_back({iface, old});
46 return true;
47}
48
49void hooks_restore_all() {
50 std::lock_guard<std::mutex> lock(g_hook_mu);
51 for (auto it = g_installed.rbegin(); it != g_installed.rend(); ++it)
52 *(void ***)it->iface = it->old_vt;
53 g_installed.clear();
54}
55
56int hooks_installed_count() {
57 std::lock_guard<std::mutex> lock(g_hook_mu);
58 return (int)g_installed.size();
59}
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 @@
1#ifndef HL2H_HOOKS_H
2#define HL2H_HOOKS_H
3
4#include <cstddef>
5
6// One patched vtable slot. `original` receives the untouched function pointer
7// (it may be null if the caller does not need it).
8struct VtablePatch {
9 int slot;
10 void *replacement;
11 void **original;
12};
13
14// Clone `iface`'s vtable (preserving the Itanium ABI header), apply all
15// patches, and install the clone on the object. All slots belong to the same
16// interface. Returns false and leaves the object untouched if cloning fails or
17// any slot is out of range.
18bool hooks_install_vtable(void *iface, size_t slots, const VtablePatch *patches,
19 int count);
20
21// Restore every patched interface's original vtable in reverse install order.
22// Intended for tests and shutdown; the cheat normally lives as long as the
23// process does.
24void hooks_restore_all();
25
26int hooks_installed_count();
27
28#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() {
597 (void *)g_force_override, (void *)g_orig_model_exec); 597 (void *)g_force_override, (void *)g_orig_model_exec);
598 return; 598 return;
599 } 599 }
600 void **nvt = vtable_clone(g_iface.modelrender, 32); 600 VtablePatch patches[] = {
601 if (!nvt) { 601 {1, (void *)&hkdForceOverride,
602 logf("chams: vtable clone failed"); 602 reinterpret_cast<void **>(&g_real_force_override)}, // track forced passes
603 {19, (void *)&hkdModelExecute,
604 reinterpret_cast<void **>(&g_orig_model_exec)},
605 };
606 if (!hooks_install_vtable(g_iface.modelrender, 32, patches, 2)) {
607 logf("chams: vtable patch failed");
603 return; 608 return;
604 } 609 }
605 nvt[1] = (void *)&hkdForceOverride; // track engine-forced passes
606 nvt[19] = (void *)&hkdModelExecute;
607 g_modelrender = g_iface.modelrender; 610 g_modelrender = g_iface.modelrender;
608 *(void ***)g_modelrender = nvt;
609 logf("chams: IVModelRender %p hooked (draw=%p override=%p)", g_modelrender, 611 logf("chams: IVModelRender %p hooked (draw=%p override=%p)", g_modelrender,
610 (void *)g_orig_model_exec, (void *)g_force_override); 612 (void *)g_orig_model_exec, (void *)g_force_override);
611} 613}
@@ -641,24 +643,22 @@ void studio_props_init() {
641 logf("studio: interface has no vtable"); 643 logf("studio: interface has no vtable");
642 return; 644 return;
643 } 645 }
644 void **vt = vtable_clone(sr, 49); 646 VtablePatch patches[] = {
645 if (!vt) { 647 {27, (void *)&hook_set_color,
646 logf("studio: vtable clone failed"); 648 reinterpret_cast<void **>(&g_orig_set_color)},
649 {28, (void *)&hook_set_alpha,
650 reinterpret_cast<void **>(&g_orig_set_alpha)},
651 {29, (void *)&hook_draw_model,
652 reinterpret_cast<void **>(&g_orig_draw_model)},
653 {30, (void *)&hook_draw_static_prop,
654 reinterpret_cast<void **>(&g_orig_draw_static)},
655 {46, (void *)&hook_draw_array,
656 reinterpret_cast<void **>(&g_orig_draw_array)},
657 };
658 if (!hooks_install_vtable(sr, 49, patches, 5)) {
659 logf("studio: vtable patch failed");
647 return; 660 return;
648 } 661 }
649
650 g_orig_set_color = (SetColorFn)vt[27];
651 g_orig_set_alpha = (SetAlphaFn)vt[28];
652 g_orig_draw_model = (DrawModelFn)vt[29];
653 g_orig_draw_static = (DrawStaticFn)vt[30];
654 g_orig_draw_array = (DrawArrayFn)vt[46];
655 vt[27] = (void *)&hook_set_color;
656 vt[28] = (void *)&hook_set_alpha;
657 vt[29] = (void *)&hook_draw_model;
658 vt[30] = (void *)&hook_draw_static_prop;
659 vt[46] = (void *)&hook_draw_array;
660
661 *(void ***)sr = vt;
662 g_installed = true; 662 g_installed = true;
663 logf("studio: IStudioRender %p hooked (color=%p draw=%p static=%p " 663 logf("studio: IStudioRender %p hooked (color=%p draw=%p static=%p "
664 "array=%p)", sr, (void *)g_orig_set_color, (void *)g_orig_draw_model, 664 "array=%p)", sr, (void *)g_orig_set_color, (void *)g_orig_draw_model,