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/modules.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/modules.cpp | 47 |
1 files changed, 38 insertions, 9 deletions
diff --git a/src/modules.cpp b/src/modules.cpp index 7a82028..a671d82 100644 --- a/src/modules.cpp +++ b/src/modules.cpp | |||
| @@ -1,10 +1,27 @@ | |||
| 1 | // modules.cpp — /proc/self/maps parsing and module discovery | 1 | // modules.cpp — /proc/self/maps parsing and module discovery |
| 2 | #include "hl2h.h" | 2 | #include "hl2h.h" |
| 3 | #include <atomic> | ||
| 3 | #include <elf.h> | 4 | #include <elf.h> |
| 4 | #include <mutex> | ||
| 5 | #include <vector> | 5 | #include <vector> |
| 6 | 6 | ||
| 7 | static std::mutex g_modules_mutex; | 7 | // Immutable snapshots. Readers load a pointer with acquire semantics and get a |
| 8 | // module that never changes underneath them. find_modules() publishes a fresh | ||
| 9 | // copy instead of mutating the current one; retired snapshots are deliberately | ||
| 10 | // leaked so a reference already handed to another thread stays valid. | ||
| 11 | static const Module g_no_module; | ||
| 12 | static std::atomic<const Module *> g_client_snap{&g_no_module}; | ||
| 13 | static std::atomic<const Module *> g_engine_snap{&g_no_module}; | ||
| 14 | static std::atomic<const Module *> g_server_snap{&g_no_module}; | ||
| 15 | |||
| 16 | const Module &mod_client() { | ||
| 17 | return *g_client_snap.load(std::memory_order_acquire); | ||
| 18 | } | ||
| 19 | const Module &mod_engine() { | ||
| 20 | return *g_engine_snap.load(std::memory_order_acquire); | ||
| 21 | } | ||
| 22 | const Module &mod_server() { | ||
| 23 | return *g_server_snap.load(std::memory_order_acquire); | ||
| 24 | } | ||
| 8 | 25 | ||
| 9 | static bool basename_is(const char *path, const char *base) { | 26 | static bool basename_is(const char *path, const char *base) { |
| 10 | size_t pl = strlen(path), bl = strlen(base); | 27 | size_t pl = strlen(path), bl = strlen(base); |
| @@ -55,6 +72,9 @@ void find_modules() { | |||
| 55 | else if (engine.valid() && engine.ranges.back().file && | 72 | else if (engine.valid() && engine.ranges.back().file && |
| 56 | lo == engine.ranges.back().hi && hi - lo <= 0x1000000) | 73 | lo == engine.ranges.back().hi && hi - lo <= 0x1000000) |
| 57 | m = &engine; | 74 | m = &engine; |
| 75 | else if (server.valid() && server.ranges.back().file && | ||
| 76 | lo == server.ranges.back().hi && hi - lo <= 0x1000000) | ||
| 77 | m = &server; | ||
| 58 | } | 78 | } |
| 59 | if (!m) | 79 | if (!m) |
| 60 | continue; | 80 | continue; |
| @@ -72,16 +92,25 @@ void find_modules() { | |||
| 72 | } | 92 | } |
| 73 | } | 93 | } |
| 74 | fclose(f); | 94 | fclose(f); |
| 75 | // setup_thread and resolve_server_netvars can both call this; the old code | 95 | // setup_thread and resolve_server_netvars can both call this. Publish whole |
| 76 | // reset the shared Module globals and reallocated their vectors live, which | 96 | // snapshots atomically so a reader never iterates a vector being reallocated. |
| 77 | // crashed under the startup thread. Publish complete snapshots instead. | ||
| 78 | std::lock_guard<std::mutex> lock(g_modules_mutex); | ||
| 79 | if (client.valid()) | 97 | if (client.valid()) |
| 80 | g_client = std::move(client); | 98 | g_client_snap.store(new Module(std::move(client)), std::memory_order_release); |
| 81 | if (engine.valid()) | 99 | if (engine.valid()) |
| 82 | g_engine = std::move(engine); | 100 | g_engine_snap.store(new Module(std::move(engine)), std::memory_order_release); |
| 83 | if (server.valid()) | 101 | if (server.valid()) |
| 84 | g_server = std::move(server); | 102 | g_server_snap.store(new Module(std::move(server)), std::memory_order_release); |
| 103 | } | ||
| 104 | |||
| 105 | bool module_range(uintptr_t p, size_t n) { | ||
| 106 | if (n == 0) | ||
| 107 | return true; | ||
| 108 | uintptr_t last = p + (n - 1); | ||
| 109 | if (last < p) | ||
| 110 | return false; // address-space wrap | ||
| 111 | return (mod_client().contains(p) && mod_client().contains(last)) || | ||
| 112 | (mod_engine().contains(p) && mod_engine().contains(last)) || | ||
| 113 | (mod_server().contains(p) && mod_server().contains(last)); | ||
| 85 | } | 114 | } |
| 86 | 115 | ||
| 87 | // --------------------------------------------------------------------------- | 116 | // --------------------------------------------------------------------------- |
