diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-06 16:09:55 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-06 16:09:55 +0100 |
| commit | 33f5ed9ef58f26c6b3b52cf605dd4311d658a60d (patch) | |
| tree | a97254872e465c9107b7ef19695ab56bb076d7e2 /src/cinput.cpp | |
| parent | Items: log the usable ammo table once (diff) | |
| download | hl2h-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.cpp | 39 |
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; | |||
| 53 | static constexpr size_t VERIFIED_CRC_OFF = 0x54; | 53 | static constexpr size_t VERIFIED_CRC_OFF = 0x54; |
| 54 | 54 | ||
| 55 | CUserCmd *get_usercmd(int seq) { | 55 | CUserCmd *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 | ||
| 67 | void fix_verified_crc(int seq, uint32_t crc) { | 69 | void 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. |
| 94 | static void hkdInputExtraMouseSample(void *self, float frametime, bool active) { | 96 | static 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 | ||
| 110 | static void hkdInputCreateMove(void *self, int seq, float frametime, | 117 | static 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() { | |||
| 173 | bool find_input_global() { | 188 | bool 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); |
