summaryrefslogtreecommitdiffstats
path: root/src/bones.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/bones.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/bones.cpp36
1 files changed, 25 insertions, 11 deletions
diff --git a/src/bones.cpp b/src/bones.cpp
index c777067..c4168a9 100644
--- a/src/bones.cpp
+++ b/src/bones.cpp
@@ -100,7 +100,10 @@ struct BoneEntry {
100 Matrix3x4 mats[MAX_BONES]; 100 Matrix3x4 mats[MAX_BONES];
101}; 101};
102 102
103static BoneEntry g_entries[64]; 103// Sized for the 128-target scan with headroom: failed updates also allocate an
104// entry, and 64 slots evicted live NPCs mid-frame.
105constexpr int BONE_CACHE = 192;
106static BoneEntry g_entries[BONE_CACHE];
104static int g_next = 0; 107static int g_next = 0;
105static int g_logged = 0; 108static int g_logged = 0;
106static bool g_renderable_warned = false; 109static bool g_renderable_warned = false;
@@ -112,14 +115,14 @@ static double bone_now() {
112} 115}
113 116
114static BoneEntry *find_entry(int idx) { 117static BoneEntry *find_entry(int idx) {
115 for (int i = 0; i < 64; i++) 118 for (int i = 0; i < BONE_CACHE; i++)
116 if (g_entries[i].ent && g_entries[i].idx == idx) 119 if (g_entries[i].ent && g_entries[i].idx == idx)
117 return &g_entries[i]; 120 return &g_entries[i];
118 return nullptr; 121 return nullptr;
119} 122}
120 123
121static BoneEntry *alloc_entry(int idx, void *ent) { 124static BoneEntry *alloc_entry(int idx, void *ent) {
122 BoneEntry *e = &g_entries[g_next++ % 64]; 125 BoneEntry *e = &g_entries[g_next++ % BONE_CACHE];
123 memset(e, 0, sizeof(*e)); 126 memset(e, 0, sizeof(*e));
124 e->idx = idx; 127 e->idx = idx;
125 e->ent = ent; 128 e->ent = ent;
@@ -321,13 +324,17 @@ bool bones_update(int idx, void *ent) {
321 324
322 const void *expected = 325 const void *expected =
323 vfunc<GetModelIdxFn>(g_iface.modelinfo, 2)(g_iface.modelinfo, modelindex); 326 vfunc<GetModelIdxFn>(g_iface.modelinfo, 2)(g_iface.modelinfo, modelindex);
324 if (!expected) 327 if (!expected) {
328 e->valid = false;
325 return false; 329 return false;
330 }
326 331
327 // IClientUnknown::GetClientRenderable = primary vtable slot 6. 332 // IClientUnknown::GetClientRenderable = primary vtable slot 6.
328 void *r = vfunc<GetRenderableFn>(ent, 6)(ent); 333 void *r = vfunc<GetRenderableFn>(ent, 6)(ent);
329 if (!r) 334 if (!r) {
335 e->valid = false;
330 return false; 336 return false;
337 }
331 // Validate the interface before using SetupBones on it. 338 // Validate the interface before using SetupBones on it.
332 const void *rmodel = vfunc<GetModelFn>(r, 9)(r); 339 const void *rmodel = vfunc<GetModelFn>(r, 9)(r);
333 if (rmodel != expected) { 340 if (rmodel != expected) {
@@ -418,9 +425,14 @@ int bone_nearest_core_parent(int idx, int bone) {
418 return -1; 425 return -1;
419 int p = bone_at(e->hdr, bone)->parent; 426 int p = bone_at(e->hdr, bone)->parent;
420 if (!e->humanoid) { 427 if (!e->humanoid) {
421 while (p >= 0 && p < e->count && !e->bone_valid[p]) 428 // Same cycle guard as the core path: a malformed model can self-parent a
429 // bone whose matrix is invalid, which would spin here forever.
430 for (int guard = 0; p >= 0 && p < e->count && guard < 64; guard++) {
431 if (e->bone_valid[p])
432 return p;
422 p = bone_at(e->hdr, p)->parent; 433 p = bone_at(e->hdr, p)->parent;
423 return (p >= 0 && p < e->count) ? p : -1; 434 }
435 return -1;
424 } 436 }
425 for (int guard = 0; p >= 0 && p < e->count && guard < 64; guard++) { 437 for (int guard = 0; p >= 0 && p < e->count && guard < 64; guard++) {
426 if (e->core[p] && e->bone_valid[p]) 438 if (e->core[p] && e->bone_valid[p])
@@ -487,14 +499,16 @@ bool head_position(int idx, Vector &out) {
487 if (boxes[i].bone < 0 || boxes[i].bone >= e->count) 499 if (boxes[i].bone < 0 || boxes[i].bone >= e->count)
488 continue; 500 continue;
489 const char *bn = bone_name_raw(e->hdr, boxes[i].bone); 501 const char *bn = bone_name_raw(e->hdr, boxes[i].bone);
490 if (bn && head_like_name(bn)) 502 // Keep looking: the first name match can be a bone whose matrix failed
491 return hitbox_world(e, boxes[i], out); 503 // validation, and returning its false result aborted the whole search.
504 if (bn && head_like_name(bn) && hitbox_world(e, boxes[i], out))
505 return true;
492 } 506 }
493 } 507 }
494 for (int i = 0; i < e->count; i++) { 508 for (int i = 0; i < e->count; i++) {
495 const char *bn = bone_name_raw(e->hdr, i); 509 const char *bn = bone_name_raw(e->hdr, i);
496 if (bn && head_like_name(bn)) 510 if (bn && head_like_name(bn) && bone_pos(idx, i, out))
497 return bone_pos(idx, i, out); 511 return true;
498 } 512 }
499 // Last resort for non-humanoid models with unnamed heads/hitboxes: the 513 // Last resort for non-humanoid models with unnamed heads/hitboxes: the
500 // first hitbox is the body (still a valid aim point). 514 // first hitbox is the body (still a valid aim point).