summaryrefslogtreecommitdiffstats
path: root/src/netvars.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/netvars.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/netvars.cpp50
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
8static bool module_string(uintptr_t p, char *out, size_t outn) { 8static 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;
30static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { 34static 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);