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/netvars.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/netvars.cpp | 50 |
1 files changed, 34 insertions, 16 deletions
diff --git a/src/netvars.cpp b/src/netvars.cpp index e22ff38..8e9579c 100644 --- a/src/netvars.cpp +++ b/src/netvars.cpp | |||
| @@ -6,11 +6,15 @@ | |||
| 6 | // ClientClass (2004-era): CreateFn@0, CreateEventFn@4, Name@8, RecvTable@0xC, Next@0x10 | 6 | // ClientClass (2004-era): CreateFn@0, CreateEventFn@4, Name@8, RecvTable@0xC, Next@0x10 |
| 7 | 7 | ||
| 8 | static bool module_string(uintptr_t p, char *out, size_t outn) { | 8 | static bool module_string(uintptr_t p, char *out, size_t outn) { |
| 9 | if (!g_client.contains(p) && !g_engine.contains(p) && !g_server.contains(p)) | 9 | if (!p || outn == 0 || !module_range(p, 1)) |
| 10 | return false; | 10 | return false; |
| 11 | const char *s = (const char *)p; | 11 | const char *s = (const char *)p; |
| 12 | size_t i = 0; | 12 | size_t i = 0; |
| 13 | while (i < outn - 1) { | 13 | while (i < outn - 1) { |
| 14 | // Validate every byte, not just the first: a string near the end of a | ||
| 15 | // mapping must not walk into an unmapped page looking for its NUL. | ||
| 16 | if (!module_range(p + i, 1)) | ||
| 17 | return false; | ||
| 14 | unsigned char ch = (unsigned char)s[i]; | 18 | unsigned char ch = (unsigned char)s[i]; |
| 15 | if (ch == 0) | 19 | if (ch == 0) |
| 16 | break; | 20 | break; |
| @@ -30,9 +34,13 @@ static int g_dumped_tables = 0; | |||
| 30 | static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | 34 | static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { |
| 31 | if (depth > 8) | 35 | if (depth > 8) |
| 32 | return; | 36 | return; |
| 37 | // RecvTable is 16 bytes; validate before the first read (the table pointer | ||
| 38 | // and the nested pDataTable both come from engine memory). | ||
| 39 | if (!module_range(table, 0x10)) | ||
| 40 | return; | ||
| 33 | uintptr_t props = *(uintptr_t *)(table + 0x00); | 41 | uintptr_t props = *(uintptr_t *)(table + 0x00); |
| 34 | int32_t nprops = *(int32_t *)(table + 0x04); | 42 | int32_t nprops = *(int32_t *)(table + 0x04); |
| 35 | if (!props || !g_client.contains(props) || nprops <= 0 || nprops > 512) | 43 | if (!props || !mod_client().contains(props) || nprops <= 0 || nprops > 512) |
| 36 | return; | 44 | return; |
| 37 | 45 | ||
| 38 | char tname[64]; | 46 | char tname[64]; |
| @@ -52,7 +60,7 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 52 | 60 | ||
| 53 | for (int32_t i = 0; i < nprops; i++) { | 61 | for (int32_t i = 0; i < nprops; i++) { |
| 54 | uintptr_t prop = props + (size_t)i * 0x3C; | 62 | uintptr_t prop = props + (size_t)i * 0x3C; |
| 55 | if (!g_client.contains(prop)) | 63 | if (!mod_client().contains(prop)) |
| 56 | return; | 64 | return; |
| 57 | uintptr_t pname = *(uintptr_t *)(prop + 0x00); | 65 | uintptr_t pname = *(uintptr_t *)(prop + 0x00); |
| 58 | int32_t ptype = *(int32_t *)(prop + 0x04); | 66 | int32_t ptype = *(int32_t *)(prop + 0x04); |
| @@ -74,7 +82,7 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 74 | strncmp(pname2, "m_hMyWeapons", 12) == 0) { | 82 | strncmp(pname2, "m_hMyWeapons", 12) == 0) { |
| 75 | int off = (int)(base_off + (uint32_t)poff); | 83 | int off = (int)(base_off + (uint32_t)poff); |
| 76 | uintptr_t elem = parray ? parray : ptable; | 84 | uintptr_t elem = parray ? parray : ptable; |
| 77 | if (elem) { | 85 | if (elem && module_range(elem, 0x30)) { |
| 78 | int32_t eoff = *(int32_t *)(elem + 0x2C); | 86 | int32_t eoff = *(int32_t *)(elem + 0x2C); |
| 79 | if (eoff) | 87 | if (eoff) |
| 80 | off = (int)(base_off + (uint32_t)eoff); | 88 | off = (int)(base_off + (uint32_t)eoff); |
| @@ -89,7 +97,7 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 89 | if (g_net.ammo < 0 && strncmp(pname2, "m_iAmmo", 7) == 0) { | 97 | if (g_net.ammo < 0 && strncmp(pname2, "m_iAmmo", 7) == 0) { |
| 90 | int off = (int)(base_off + (uint32_t)poff); | 98 | int off = (int)(base_off + (uint32_t)poff); |
| 91 | uintptr_t elem = parray ? parray : ptable; | 99 | uintptr_t elem = parray ? parray : ptable; |
| 92 | if (elem) { | 100 | if (elem && module_range(elem, 0x30)) { |
| 93 | int32_t eoff = *(int32_t *)(elem + 0x2C); | 101 | int32_t eoff = *(int32_t *)(elem + 0x2C); |
| 94 | if (eoff) | 102 | if (eoff) |
| 95 | off = (int)(base_off + (uint32_t)eoff); | 103 | off = (int)(base_off + (uint32_t)eoff); |
| @@ -113,7 +121,7 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 113 | } else if (strcmp(pname2, "m_fFlags") == 0 && g_net.flags < 0) { | 121 | } else if (strcmp(pname2, "m_fFlags") == 0 && g_net.flags < 0) { |
| 114 | g_net.flags = (int)(base_off + (uint32_t)poff); | 122 | g_net.flags = (int)(base_off + (uint32_t)poff); |
| 115 | } else if (strcmp(pname2, "m_vecVelocity") == 0 && g_net.velocity < 0) { | 123 | } else if (strcmp(pname2, "m_vecVelocity") == 0 && g_net.velocity < 0) { |
| 116 | if (parray && g_client.contains(parray)) { | 124 | if (parray && module_range(parray, 0x30)) { |
| 117 | int32_t eoff = *(int32_t *)(parray + 0x2C); | 125 | int32_t eoff = *(int32_t *)(parray + 0x2C); |
| 118 | g_net.velocity = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); | 126 | g_net.velocity = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); |
| 119 | } else { | 127 | } else { |
| @@ -126,6 +134,16 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 126 | } else if (strcmp(pname2, "m_hActiveWeapon") == 0 && | 134 | } else if (strcmp(pname2, "m_hActiveWeapon") == 0 && |
| 127 | g_net.activeweapon < 0) { | 135 | g_net.activeweapon < 0) { |
| 128 | g_net.activeweapon = (int)(base_off + (uint32_t)poff); | 136 | g_net.activeweapon = (int)(base_off + (uint32_t)poff); |
| 137 | } else if (strcmp(pname2, "m_hOwnerEntity") == 0 && g_net.owner < 0) { | ||
| 138 | // Used by vischeck to ignore a target's held weapon / bone followers, | ||
| 139 | // and by item ESP for weapon ownership. | ||
| 140 | int off = (int)(base_off + (uint32_t)poff); | ||
| 141 | if (off >= 0x20 && off <= 0x2000) | ||
| 142 | g_net.owner = off; | ||
| 143 | } else if (strcmp(pname2, "m_hOwner") == 0 && g_net.weapon_owner < 0) { | ||
| 144 | int off = (int)(base_off + (uint32_t)poff); | ||
| 145 | if (off >= 0x20 && off <= 0x2000) | ||
| 146 | g_net.weapon_owner = off; | ||
| 129 | } else if (strcmp(pname2, "m_clrRender") == 0 && g_net.clr_render < 0) { | 147 | } else if (strcmp(pname2, "m_clrRender") == 0 && g_net.clr_render < 0) { |
| 130 | int off = (int)(base_off + (uint32_t)poff); | 148 | int off = (int)(base_off + (uint32_t)poff); |
| 131 | // Reject the 0 offset some embedded tables report; the real render | 149 | // Reject the 0 offset some embedded tables report; the real render |
| @@ -149,7 +167,7 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 149 | } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) { | 167 | } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) { |
| 150 | g_net.teamnum = (int)(base_off + (uint32_t)poff); | 168 | g_net.teamnum = (int)(base_off + (uint32_t)poff); |
| 151 | } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) { | 169 | } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) { |
| 152 | if (parray && g_client.contains(parray)) { | 170 | if (parray && module_range(parray, 0x30)) { |
| 153 | int32_t eoff = *(int32_t *)(parray + 0x2C); | 171 | int32_t eoff = *(int32_t *)(parray + 0x2C); |
| 154 | g_net.origin = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); | 172 | g_net.origin = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); |
| 155 | } else { | 173 | } else { |
| @@ -157,21 +175,21 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 157 | } | 175 | } |
| 158 | } else if (strncmp(pname2, "m_vecViewOffset", 15) == 0 && | 176 | } else if (strncmp(pname2, "m_vecViewOffset", 15) == 0 && |
| 159 | g_net.viewoffset < 0) { | 177 | g_net.viewoffset < 0) { |
| 160 | if (parray && g_client.contains(parray)) { | 178 | if (parray && module_range(parray, 0x30)) { |
| 161 | int32_t eoff = *(int32_t *)(parray + 0x2C); | 179 | int32_t eoff = *(int32_t *)(parray + 0x2C); |
| 162 | g_net.viewoffset = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); | 180 | g_net.viewoffset = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); |
| 163 | } else { | 181 | } else { |
| 164 | g_net.viewoffset = (int)(base_off + (uint32_t)poff); | 182 | g_net.viewoffset = (int)(base_off + (uint32_t)poff); |
| 165 | } | 183 | } |
| 166 | } else if (strcmp(pname2, "m_vecMins") == 0 && g_net.mins < 0) { | 184 | } else if (strcmp(pname2, "m_vecMins") == 0 && g_net.mins < 0) { |
| 167 | if (parray && g_client.contains(parray)) { | 185 | if (parray && module_range(parray, 0x30)) { |
| 168 | int32_t eoff = *(int32_t *)(parray + 0x2C); | 186 | int32_t eoff = *(int32_t *)(parray + 0x2C); |
| 169 | g_net.mins = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); | 187 | g_net.mins = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); |
| 170 | } else { | 188 | } else { |
| 171 | g_net.mins = (int)(base_off + (uint32_t)poff); | 189 | g_net.mins = (int)(base_off + (uint32_t)poff); |
| 172 | } | 190 | } |
| 173 | } else if (strcmp(pname2, "m_vecMaxs") == 0 && g_net.maxs < 0) { | 191 | } else if (strcmp(pname2, "m_vecMaxs") == 0 && g_net.maxs < 0) { |
| 174 | if (parray && g_client.contains(parray)) { | 192 | if (parray && module_range(parray, 0x30)) { |
| 175 | int32_t eoff = *(int32_t *)(parray + 0x2C); | 193 | int32_t eoff = *(int32_t *)(parray + 0x2C); |
| 176 | g_net.maxs = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); | 194 | g_net.maxs = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); |
| 177 | } else { | 195 | } else { |
| @@ -187,7 +205,7 @@ static bool resolve_movetype() { | |||
| 187 | const size_t nlen = sizeof(needle); | 205 | const size_t nlen = sizeof(needle); |
| 188 | 206 | ||
| 189 | uintptr_t str_va = 0; | 207 | uintptr_t str_va = 0; |
| 190 | for (auto &r : g_client.ranges) { | 208 | for (auto &r : mod_client().ranges) { |
| 191 | const uint8_t *base = (const uint8_t *)r.lo; | 209 | const uint8_t *base = (const uint8_t *)r.lo; |
| 192 | size_t span = r.hi - r.lo; | 210 | size_t span = r.hi - r.lo; |
| 193 | for (size_t i = 0; i + nlen <= span; i++) { | 211 | for (size_t i = 0; i + nlen <= span; i++) { |
| @@ -208,7 +226,7 @@ static bool resolve_movetype() { | |||
| 208 | int counts[16] = {}; | 226 | int counts[16] = {}; |
| 209 | int ncand = 0; | 227 | int ncand = 0; |
| 210 | const uint32_t target = (uint32_t)str_va; | 228 | const uint32_t target = (uint32_t)str_va; |
| 211 | for (auto &r : g_client.ranges) { | 229 | for (auto &r : mod_client().ranges) { |
| 212 | const uint8_t *base = (const uint8_t *)r.lo; | 230 | const uint8_t *base = (const uint8_t *)r.lo; |
| 213 | size_t span = r.hi - r.lo; | 231 | size_t span = r.hi - r.lo; |
| 214 | for (size_t off = 4; off + 8 <= span; off += 4) { | 232 | for (size_t off = 4; off + 8 <= span; off += 4) { |
| @@ -247,7 +265,7 @@ static bool resolve_absvelocity() { | |||
| 247 | const size_t nlen = sizeof(needle); | 265 | const size_t nlen = sizeof(needle); |
| 248 | 266 | ||
| 249 | uintptr_t str_va = 0; | 267 | uintptr_t str_va = 0; |
| 250 | for (auto &r : g_client.ranges) { | 268 | for (auto &r : mod_client().ranges) { |
| 251 | const uint8_t *base = (const uint8_t *)r.lo; | 269 | const uint8_t *base = (const uint8_t *)r.lo; |
| 252 | size_t span = r.hi - r.lo; | 270 | size_t span = r.hi - r.lo; |
| 253 | for (size_t i = 0; i + nlen <= span; i++) { | 271 | for (size_t i = 0; i + nlen <= span; i++) { |
| @@ -268,7 +286,7 @@ static bool resolve_absvelocity() { | |||
| 268 | int counts[16] = {}; | 286 | int counts[16] = {}; |
| 269 | int ncand = 0; | 287 | int ncand = 0; |
| 270 | const uint32_t target = (uint32_t)str_va; | 288 | const uint32_t target = (uint32_t)str_va; |
| 271 | for (auto &r : g_client.ranges) { | 289 | for (auto &r : mod_client().ranges) { |
| 272 | const uint8_t *base = (const uint8_t *)r.lo; | 290 | const uint8_t *base = (const uint8_t *)r.lo; |
| 273 | size_t span = r.hi - r.lo; | 291 | size_t span = r.hi - r.lo; |
| 274 | for (size_t off = 4; off + 8 <= span; off += 4) { | 292 | for (size_t off = 4; off + 8 <= span; off += 4) { |
| @@ -306,12 +324,12 @@ void resolve_netvars() { | |||
| 306 | using GetAllClassesFn = void *(*)(void *); | 324 | using GetAllClassesFn = void *(*)(void *); |
| 307 | uintptr_t head = | 325 | uintptr_t head = |
| 308 | (uintptr_t)vfunc<GetAllClassesFn>(g_iface.client, 8)(g_iface.client); | 326 | (uintptr_t)vfunc<GetAllClassesFn>(g_iface.client, 8)(g_iface.client); |
| 309 | if (!g_client.contains(head)) { | 327 | if (!mod_client().contains(head)) { |
| 310 | logf("GetAllClasses returned %p, outside client module", (void *)head); | 328 | logf("GetAllClasses returned %p, outside client module", (void *)head); |
| 311 | return; | 329 | return; |
| 312 | } | 330 | } |
| 313 | int classes = 0; | 331 | int classes = 0; |
| 314 | for (uintptr_t cc = head; cc && g_client.contains(cc) && classes < 1024; | 332 | for (uintptr_t cc = head; cc && mod_client().contains(cc) && classes < 1024; |
| 315 | cc = *(uintptr_t *)(cc + 0x10)) { | 333 | cc = *(uintptr_t *)(cc + 0x10)) { |
| 316 | classes++; | 334 | classes++; |
| 317 | collect_netvars(*(uintptr_t *)(cc + 0x0C), 0, 0); | 335 | collect_netvars(*(uintptr_t *)(cc + 0x0C), 0, 0); |
