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/vischeck.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 'src/vischeck.cpp')
| -rw-r--r-- | src/vischeck.cpp | 62 |
1 files changed, 53 insertions, 9 deletions
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 { | |||
| 88 | int GetTraceType() const override { return 0; } // TRACE_EVERYTHING | 88 | int GetTraceType() const override { return 0; } // TRACE_EVERYTHING |
| 89 | }; | 89 | }; |
| 90 | 90 | ||
| 91 | // True when pEntity is a child of target: held weapons are separate entities | ||
| 92 | // and heavy NPCs use phys_bone_follower entities for their collision model, so | ||
| 93 | // a trace toward an NPC's torso otherwise stops on the weapon/follower and the | ||
| 94 | // target reads as occluded. CBaseHandle stores the entity index in the low | ||
| 95 | // bits, so the owner handle resolves through the client entity list. | ||
| 96 | static bool owned_by(void *pEntity, void *target) { | ||
| 97 | if (!pEntity || !target || g_net.owner < 0) | ||
| 98 | return false; | ||
| 99 | uint32_t h = *(uint32_t *)((uintptr_t)pEntity + g_net.owner); | ||
| 100 | int idx = (int)(h & 0x1FFFu); | ||
| 101 | if (idx <= 0) | ||
| 102 | return false; | ||
| 103 | return entitylist_get(idx) == target; | ||
| 104 | } | ||
| 105 | |||
| 91 | struct VisibilityFilter : ITraceFilterCompat { | 106 | struct VisibilityFilter : ITraceFilterCompat { |
| 92 | void *self; | 107 | void *self; |
| 93 | void *target; | 108 | void *target; |
| 94 | VisibilityFilter(void *s, void *t) : self(s), target(t) {} | 109 | VisibilityFilter(void *s, void *t) : self(s), target(t) {} |
| 95 | bool ShouldHitEntity(void *pEntity, int) override { | 110 | bool ShouldHitEntity(void *pEntity, int) override { |
| 96 | return pEntity != self && pEntity != target; | 111 | if (pEntity == self || pEntity == target) |
| 112 | return false; | ||
| 113 | return !owned_by(pEntity, target); | ||
| 97 | } | 114 | } |
| 98 | int GetTraceType() const override { return 0; } // TRACE_EVERYTHING | 115 | int GetTraceType() const override { return 0; } // TRACE_EVERYTHING |
| 99 | }; | 116 | }; |
| @@ -176,14 +193,14 @@ bool is_visible(const Vector &eye, const Vector &point, void *self, | |||
| 176 | // Trace everything that bullets care about: world, static props, crates and | 193 | // Trace everything that bullets care about: world, static props, crates and |
| 177 | // NPCs. Non-solid glass and glass whose material name marks it breakable is | 194 | // NPCs. Non-solid glass and glass whose material name marks it breakable is |
| 178 | // skipped; solid non-break glass (bulletproof) blocks. | 195 | // skipped; solid non-break glass (bulletproof) blocks. |
| 196 | using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, | ||
| 197 | GameTraceCompat *); | ||
| 179 | Vector start = eye; | 198 | Vector start = eye; |
| 180 | for (int pass = 0; pass < 5; pass++) { | 199 | for (int pass = 0; pass < 5; pass++) { |
| 181 | RayCompat ray; | 200 | RayCompat ray; |
| 182 | make_ray(ray, start, point); | 201 | make_ray(ray, start, point); |
| 183 | GameTraceCompat tr{}; | 202 | GameTraceCompat tr{}; |
| 184 | VisibilityFilter filter(self, target); | 203 | VisibilityFilter filter(self, target); |
| 185 | using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, | ||
| 186 | GameTraceCompat *); | ||
| 187 | vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, | 204 | vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, |
| 188 | MASK_SHOT, &filter, &tr); | 205 | MASK_SHOT, &filter, &tr); |
| 189 | if (tr.startsolid || tr.allsolid) | 206 | if (tr.startsolid || tr.allsolid) |
| @@ -196,11 +213,34 @@ bool is_visible(const Vector &eye, const Vector &point, void *self, | |||
| 196 | bool solid = (tr.contents & CONTENTS_SOLID) != 0; | 213 | bool solid = (tr.contents & CONTENTS_SOLID) != 0; |
| 197 | bool breakable_glass = glass && surface_is_breakable_glass(tr.surface); | 214 | bool breakable_glass = glass && surface_is_breakable_glass(tr.surface); |
| 198 | if (glass && (!solid || breakable_glass)) { | 215 | if (glass && (!solid || breakable_glass)) { |
| 199 | // Step just past the pane and look for the next obstruction. | 216 | // Glass brushes are commonly 1-4 units thick, so the old fixed 0.5-unit |
| 200 | start = tr.endpos; | 217 | // step landed inside the brush and the next pass reported startsolid, |
| 201 | start.x += dir.x * 0.5f; | 218 | // which read as "blocked". Advance until a probe from the new start is |
| 202 | start.y += dir.y * 0.5f; | 219 | // clear, doubling the step, and stop if we pass the target. |
| 203 | start.z += dir.z * 0.5f; | 220 | Vector next = tr.endpos; |
| 221 | float step = 1.0f; | ||
| 222 | bool clear = false; | ||
| 223 | for (int guard = 0; guard < 16 && !clear; guard++) { | ||
| 224 | next.x += dir.x * step; | ||
| 225 | next.y += dir.y * step; | ||
| 226 | next.z += dir.z * step; | ||
| 227 | float ox = next.x - point.x, oy = next.y - point.y, | ||
| 228 | oz = next.z - point.z; | ||
| 229 | if (ox * ox + oy * oy + oz * oz < 0.25f) | ||
| 230 | return true; // stepped past the target; nothing left to block it | ||
| 231 | RayCompat probe; | ||
| 232 | make_ray(probe, next, point); | ||
| 233 | GameTraceCompat ptr{}; | ||
| 234 | VisibilityFilter pfilter(self, target); | ||
| 235 | vfunc<TraceRayFn>(g_iface.enginetrace, 4)( | ||
| 236 | g_iface.enginetrace, &probe, MASK_SHOT, &pfilter, &ptr); | ||
| 237 | clear = !ptr.startsolid && !ptr.allsolid; | ||
| 238 | if (!clear) | ||
| 239 | step *= 2.0f; | ||
| 240 | } | ||
| 241 | if (!clear) | ||
| 242 | return false; // still inside geometry; treat as blocked | ||
| 243 | start = next; | ||
| 204 | logd("vischeck: skipped glass surface=%s contents=0x%x solid=%d break=%d", | 244 | logd("vischeck: skipped glass surface=%s contents=0x%x solid=%d break=%d", |
| 205 | tr.surface.name ? tr.surface.name : "?", tr.contents, (int)solid, | 245 | tr.surface.name ? tr.surface.name : "?", tr.contents, (int)solid, |
| 206 | (int)breakable_glass); | 246 | (int)breakable_glass); |
| @@ -270,7 +310,11 @@ bool point_ground(const Vector &point, void *ignore) { | |||
| 270 | vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, | 310 | vfunc<TraceRayFn>(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, |
| 271 | MASK_PLAYERSOLID, &filter, &tr); | 311 | MASK_PLAYERSOLID, &filter, &tr); |
| 272 | // A start inside geometry means a wall/step there, not a ledge. | 312 | // A start inside geometry means a wall/step there, not a ledge. |
| 273 | return tr.startsolid || tr.fraction < 1.0f; | 313 | if (tr.startsolid) |
| 314 | return true; | ||
| 315 | // Only a standable surface counts: a steep cliff face hit within 96 units is | ||
| 316 | // not a floor, and treating it as one suppressed the edge jump. | ||
| 317 | return tr.fraction < 1.0f && tr.normal.z >= 0.7f; | ||
| 274 | } | 318 | } |
| 275 | 319 | ||
| 276 | // Vertical distance from the standing player hull's feet to the first | 320 | // Vertical distance from the standing player hull's feet to the first |
