summaryrefslogtreecommitdiffstats
path: root/src/cinput.cpp
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-06 16:09:55 +0100
committersalmonik <salmonik@tuta.io>2026-11-06 16:09:55 +0100
commit33f5ed9ef58f26c6b3b52cf605dd4311d658a60d (patch)
treea97254872e465c9107b7ef19695ab56bb076d7e2 /src/cinput.cpp
parentItems: log the usable ammo table once (diff)
downloadhl2h-33f5ed9ef58f26c6b3b52cf605dd4311d658a60d.tar.gz
hl2h-33f5ed9ef58f26c6b3b52cf605dd4311d658a60d.zip
Fix audit findings: crash safety, correctness, rendering, performance
Crash/UB: - gate every hook behind an init-stage flag until config/surface/input are ready - publish module maps as immutable atomic snapshots instead of reallocating shared vectors under unlocked readers - validate the %ebp walk in the RandomSeed/RandomFloat hooks against the stack - bounds-check every RecvTable/RecvProp/string dereference with module_range() - preserve the Itanium vtable header when cloning (client, CInput, model render, studio render) - reject negative usercmd sequence numbers; guard config_load against a null section target; cap the bone parent walk; check mprotect; drop PROT_EXEC on the GOT patch; absorb server.so .bss tail Correctness: - filter dormant entities across ESP, aimbot, triggerbot, melee, items and projectiles (slot self-tested against the local player) - reset the server-health latch per map and only latch on valid model indices - bone cache sized for the 128-target scan; set valid=false on every failure; head_position keeps searching past an invalid bone - vischeck steps past glass by probing until clear, and ignores entities owned by the target (held weapons, bone followers) - aimbot: maxs no longer disables it, AABB-aware FOV prefilter, lock hysteresis, lead point re-checked for FOV and visibility - spread: seed hook arms on the no-spread toggle alone; engine toggle only controls the RandomFloat zeroing - resolve m_hOwnerEntity/m_hOwner; NPC lead velocity from the server entity - vector_angles pitch range, point_ground normal test, underwater jump+duck no longer skips combat, semi-auto press/release edge for synthetic fire - item/projectile markers and blast rings refresh origin per frame while discovery stays at 10 Hz; trajectory uses the real m_DmgRadius and honours the blast-radius toggle Rendering/GUI: - one overlay draw per engine frame (FRAME_RENDER_START boundary), fixing alpha stacking and the speed-graph flicker - clipped lines/outlines and ellipsised text instead of drawing past the content pane or shifting over neighbours - dropdown dismissal consumes the click; hover ignores hidden controls - chams: reset AlphaModulate, reference shared materials once, restore prop tint colour; fonts created once per handle - projection uses Source's 4:3 aspect scaling and roll; screen size refreshed each frame; FOV circle gated on the aim toggles, not the bind Performance: - TTL caches for NPC relation, friendly/neutral and damage class - model names memoised per model index - entity pointer -> index cache for the triggerbot (was a 4096-slot walk/tick) Build: - real Makefile with -MMD dependency tracking, debug/release objects and an always-relink rule; build.sh kept as a thin wrapper
Diffstat (limited to '')
-rw-r--r--src/cinput.cpp39
1 files changed, 27 insertions, 12 deletions
diff --git a/src/cinput.cpp b/src/cinput.cpp
index 08686a1..45e2b04 100644
--- a/src/cinput.cpp
+++ b/src/cinput.cpp
@@ -53,23 +53,25 @@ static constexpr size_t VERIFIED_STRIDE = 0x58;
53static constexpr size_t VERIFIED_CRC_OFF = 0x54; 53static constexpr size_t VERIFIED_CRC_OFF = 0x54;
54 54
55CUserCmd *get_usercmd(int seq) { 55CUserCmd *get_usercmd(int seq) {
56 if (!g_input) 56 // Negative sequence numbers would make `seq % 90` negative and the size_t
57 // cast a huge index; reject before forming the pointer.
58 if (!g_input || seq < 0)
57 return nullptr; 59 return nullptr;
58 uintptr_t cmds = *(uintptr_t *)(g_input + CMDOFF_M_PCOMMANDS); 60 uintptr_t cmds = *(uintptr_t *)(g_input + CMDOFF_M_PCOMMANDS);
59 if (!cmds) 61 if (!cmds)
60 return nullptr; 62 return nullptr;
61 auto *cmd = (CUserCmd *)(cmds + (size_t)(seq % 90) * 0x54); 63 auto *cmd = (CUserCmd *)(cmds + (size_t)((unsigned)seq % 90u) * 0x54);
62 if (cmd->command_number != seq) 64 if (cmd->command_number != seq)
63 return nullptr; 65 return nullptr;
64 return cmd; 66 return cmd;
65} 67}
66 68
67void fix_verified_crc(int seq, uint32_t crc) { 69void fix_verified_crc(int seq, uint32_t crc) {
68 if (!g_input) 70 if (!g_input || seq < 0)
69 return; 71 return;
70 uintptr_t verified = *(uintptr_t *)(g_input + CMDOFF_M_PVERIFIED); 72 uintptr_t verified = *(uintptr_t *)(g_input + CMDOFF_M_PVERIFIED);
71 if (verified) 73 if (verified)
72 *(uint32_t *)(verified + (size_t)(seq % 90) * VERIFIED_STRIDE + 74 *(uint32_t *)(verified + (size_t)((unsigned)seq % 90u) * VERIFIED_STRIDE +
73 VERIFIED_CRC_OFF) = crc; 75 VERIFIED_CRC_OFF) = crc;
74} 76}
75 77
@@ -92,6 +94,11 @@ static InputExtraMouseFn oInputExtraMouseSample = nullptr;
92// commands. While the GUI is open it must not touch the view; movement still 94// commands. While the GUI is open it must not touch the view; movement still
93// comes through CreateMove. 95// comes through CreateMove.
94static void hkdInputExtraMouseSample(void *self, float frametime, bool active) { 96static void hkdInputExtraMouseSample(void *self, float frametime, bool active) {
97 if (!init_live()) {
98 if (oInputExtraMouseSample)
99 oInputExtraMouseSample(self, frametime, active);
100 return;
101 }
95 if (gui_is_open()) 102 if (gui_is_open())
96 return; 103 return;
97 if (oInputExtraMouseSample) 104 if (oInputExtraMouseSample)
@@ -109,6 +116,11 @@ void input_clear_mouse_states() {
109 116
110static void hkdInputCreateMove(void *self, int seq, float frametime, 117static void hkdInputCreateMove(void *self, int seq, float frametime,
111 bool active) { 118 bool active) {
119 if (!init_live()) {
120 if (oInputCreateMove)
121 oInputCreateMove(self, seq, frametime, active);
122 return;
123 }
112 // Mouse deltas are accumulated between ticks. With the custom GUI open we 124 // Mouse deltas are accumulated between ticks. With the custom GUI open we
113 // must discard them before CInput::CreateMove applies mouse look, otherwise 125 // must discard them before CInput::CreateMove applies mouse look, otherwise
114 // the view moves for one frame and snaps back when do_move freezes it. 126 // the view moves for one frame and snaps back when do_move freezes it.
@@ -151,8 +163,11 @@ static bool install_input_hook() {
151 if (!vt) 163 if (!vt)
152 return false; 164 return false;
153 constexpr size_t N = 64; 165 constexpr size_t N = 64;
154 void **nvt = new void *[N]; 166 void **nvt = vtable_clone((void *)g_input, N);
155 memcpy(nvt, vt, N * sizeof(void *)); 167 if (!nvt) {
168 logf("CInput vtable clone failed");
169 return false;
170 }
156 oInputCreateMove = (InputCreateMoveFn)vt[3]; 171 oInputCreateMove = (InputCreateMoveFn)vt[3];
157 nvt[3] = (void *)&hkdInputCreateMove; 172 nvt[3] = (void *)&hkdInputCreateMove;
158 if (N > 4) { 173 if (N > 4) {
@@ -173,7 +188,7 @@ static bool install_input_hook() {
173bool find_input_global() { 188bool find_input_global() {
174 void **vt = VTABLE(g_iface.client); 189 void **vt = VTABLE(g_iface.client);
175 uintptr_t cm = (uintptr_t)vt[21]; // CreateMove slot 190 uintptr_t cm = (uintptr_t)vt[21]; // CreateMove slot
176 if (!g_client.contains(cm)) { 191 if (!mod_client().contains(cm)) {
177 logf("CreateMove %p outside client module", (void *)cm); 192 logf("CreateMove %p outside client module", (void *)cm);
178 return false; 193 return false;
179 } 194 }
@@ -189,7 +204,7 @@ bool find_input_global() {
189 break; 204 break;
190 } 205 }
191 } 206 }
192 if (!anchor || !g_client.contains(anchor)) { 207 if (!anchor || !mod_client().contains(anchor)) {
193 logf("PIC anchor not found in CreateMove"); 208 logf("PIC anchor not found in CreateMove");
194 return false; 209 return false;
195 } 210 }
@@ -214,16 +229,16 @@ bool find_input_global() {
214 uint32_t disp; 229 uint32_t disp;
215 memcpy(&disp, b + i + 2, 4); 230 memcpy(&disp, b + i + 2, 4);
216 uintptr_t var = anchor + disp; 231 uintptr_t var = anchor + disp;
217 if (!g_client.contains(var)) 232 if (!mod_client().contains(var))
218 continue; 233 continue;
219 uintptr_t input = *(uintptr_t *)var; 234 uintptr_t input = *(uintptr_t *)var;
220 if (!input || !g_client.contains(input)) 235 if (!input || !mod_client().contains(input))
221 continue; 236 continue;
222 uintptr_t vtbl = *(uintptr_t *)input; 237 uintptr_t vtbl = *(uintptr_t *)input;
223 if (!vtbl || !g_client.contains(vtbl)) 238 if (!vtbl || !mod_client().contains(vtbl))
224 continue; 239 continue;
225 uintptr_t f3 = *(uintptr_t *)(vtbl + 3 * sizeof(void *)); 240 uintptr_t f3 = *(uintptr_t *)(vtbl + 3 * sizeof(void *));
226 if (!g_client.contains(f3)) 241 if (!mod_client().contains(f3))
227 continue; 242 continue;
228 logd("CInput* = %#lx (ptr var %#lx, vfunc[3] %#lx)", (unsigned long)input, 243 logd("CInput* = %#lx (ptr var %#lx, vfunc[3] %#lx)", (unsigned long)input,
229 (unsigned long)var, (unsigned long)f3); 244 (unsigned long)var, (unsigned long)f3);