From 33f5ed9ef58f26c6b3b52cf605dd4311d658a60d Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 6 Nov 2026 16:09:55 +0100 Subject: 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 --- src/vischeck.cpp | 62 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 9 deletions(-) (limited to 'src/vischeck.cpp') diff --git a/src/vischeck.cpp b/src/vischeck.cpp index 6b531b9..6126c88 100644 --- a/src/vischeck.cpp +++ b/src/vischeck.cpp @@ -88,12 +88,29 @@ struct EntityFilter : ITraceFilterCompat { int GetTraceType() const override { return 0; } // TRACE_EVERYTHING }; +// True when pEntity is a child of target: held weapons are separate entities +// and heavy NPCs use phys_bone_follower entities for their collision model, so +// a trace toward an NPC's torso otherwise stops on the weapon/follower and the +// target reads as occluded. CBaseHandle stores the entity index in the low +// bits, so the owner handle resolves through the client entity list. +static bool owned_by(void *pEntity, void *target) { + if (!pEntity || !target || g_net.owner < 0) + return false; + uint32_t h = *(uint32_t *)((uintptr_t)pEntity + g_net.owner); + int idx = (int)(h & 0x1FFFu); + if (idx <= 0) + return false; + return entitylist_get(idx) == target; +} + struct VisibilityFilter : ITraceFilterCompat { void *self; void *target; VisibilityFilter(void *s, void *t) : self(s), target(t) {} bool ShouldHitEntity(void *pEntity, int) override { - return pEntity != self && pEntity != target; + if (pEntity == self || pEntity == target) + return false; + return !owned_by(pEntity, target); } int GetTraceType() const override { return 0; } // TRACE_EVERYTHING }; @@ -176,14 +193,14 @@ bool is_visible(const Vector &eye, const Vector &point, void *self, // Trace everything that bullets care about: world, static props, crates and // NPCs. Non-solid glass and glass whose material name marks it breakable is // skipped; solid non-break glass (bulletproof) blocks. + using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, + GameTraceCompat *); Vector start = eye; for (int pass = 0; pass < 5; pass++) { RayCompat ray; make_ray(ray, start, point); GameTraceCompat tr{}; VisibilityFilter filter(self, target); - using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, - GameTraceCompat *); vfunc(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, MASK_SHOT, &filter, &tr); if (tr.startsolid || tr.allsolid) @@ -196,11 +213,34 @@ bool is_visible(const Vector &eye, const Vector &point, void *self, bool solid = (tr.contents & CONTENTS_SOLID) != 0; bool breakable_glass = glass && surface_is_breakable_glass(tr.surface); if (glass && (!solid || breakable_glass)) { - // Step just past the pane and look for the next obstruction. - start = tr.endpos; - start.x += dir.x * 0.5f; - start.y += dir.y * 0.5f; - start.z += dir.z * 0.5f; + // Glass brushes are commonly 1-4 units thick, so the old fixed 0.5-unit + // step landed inside the brush and the next pass reported startsolid, + // which read as "blocked". Advance until a probe from the new start is + // clear, doubling the step, and stop if we pass the target. + Vector next = tr.endpos; + float step = 1.0f; + bool clear = false; + for (int guard = 0; guard < 16 && !clear; guard++) { + next.x += dir.x * step; + next.y += dir.y * step; + next.z += dir.z * step; + float ox = next.x - point.x, oy = next.y - point.y, + oz = next.z - point.z; + if (ox * ox + oy * oy + oz * oz < 0.25f) + return true; // stepped past the target; nothing left to block it + RayCompat probe; + make_ray(probe, next, point); + GameTraceCompat ptr{}; + VisibilityFilter pfilter(self, target); + vfunc(g_iface.enginetrace, 4)( + g_iface.enginetrace, &probe, MASK_SHOT, &pfilter, &ptr); + clear = !ptr.startsolid && !ptr.allsolid; + if (!clear) + step *= 2.0f; + } + if (!clear) + return false; // still inside geometry; treat as blocked + start = next; logd("vischeck: skipped glass surface=%s contents=0x%x solid=%d break=%d", tr.surface.name ? tr.surface.name : "?", tr.contents, (int)solid, (int)breakable_glass); @@ -270,7 +310,11 @@ bool point_ground(const Vector &point, void *ignore) { vfunc(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, MASK_PLAYERSOLID, &filter, &tr); // A start inside geometry means a wall/step there, not a ledge. - return tr.startsolid || tr.fraction < 1.0f; + if (tr.startsolid) + return true; + // Only a standable surface counts: a steep cliff face hit within 96 units is + // not a floor, and treating it as one suppressed the edge jump. + return tr.fraction < 1.0f && tr.normal.z >= 0.7f; } // Vertical distance from the standing player hull's feet to the first -- cgit v1.2.3