From b37e1f6892895e438aa7974e1144a6269d94e4aa Mon Sep 17 00:00:00 2001 From: salmonik Date: Mon, 26 Oct 2026 17:16:35 +0100 Subject: Aimbot v3: world-only vischeck - resolve EngineTraceClient003; TraceRay (slot 4) from the eye to the exact selected hitbox/bone point with MASK_VISIBLE - TRACE_WORLD_ONLY filter as requested (static props are not tested, per SDK) - Aimbot -> Vischeck toggle, persisted as vischeck=1; when on, blocked targets are skipped and the next-nearest candidate is tried - target candidates are ranked by angle; on each target the nearest visible selected hitbox point wins --- build.sh | 2 +- src/esp.cpp | 96 ++++++++++++++++++++++++++++++++++-------------------- src/hl2h.cpp | 6 ++-- src/hl2h.h | 3 ++ src/menu.cpp | 16 ++++++--- src/vischeck.cpp | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 179 insertions(+), 42 deletions(-) create mode 100644 src/vischeck.cpp diff --git a/build.sh b/build.sh index 8be25d4..437f8c5 100755 --- a/build.sh +++ b/build.sh @@ -3,6 +3,6 @@ set -e cd "$(dirname "$0")" g++ -m32 -O2 -std=c++17 -Wall -Wextra -Werror -fPIC -shared -fvisibility=hidden \ -o libhl2hake.so \ - src/hl2h.cpp src/modules.cpp src/cinput.cpp src/netvars.cpp src/menu.cpp src/esp.cpp src/server.cpp src/bones.cpp \ + src/hl2h.cpp src/modules.cpp src/cinput.cpp src/netvars.cpp src/menu.cpp src/esp.cpp src/server.cpp src/bones.cpp src/vischeck.cpp \ -ldl file libhl2hake.so diff --git a/src/esp.cpp b/src/esp.cpp index 57f67ae..2c033ea 100644 --- a/src/esp.cpp +++ b/src/esp.cpp @@ -358,9 +358,13 @@ bool aimbot_apply(CUserCmd *cmd) { hi = 4096; constexpr float MAX_FOV = 60.0f; // only things near the crosshair - float best_fov = MAX_FOV; - int best_idx = -1; - Vector best_head{}; + struct Candidate { + int idx; + float fov; + Vector approx; + }; + Candidate cands[16]; + int ncands = 0; for (int idx = 1; idx <= hi; idx++) { if (idx == local) @@ -394,14 +398,22 @@ bool aimbot_apply(CUserCmd *cmd) { Vector approx{origin.x, origin.y, origin.z + maxz * ((bone_mask & (1u << 1)) ? 0.9f : 0.55f)}; float fov = getfov(view, angle_to(g_cam_eye, approx)); - if (fov >= best_fov) + if (fov >= MAX_FOV) continue; - best_fov = fov; - best_idx = idx; - best_head = approx; + + // keep candidates sorted by approximate angle, nearest first + int pos = ncands; + while (pos > 0 && cands[pos - 1].fov > fov) + pos--; + if (ncands < 16) + ncands++; + for (int j = ncands - 1; j > pos; j--) + cands[j] = cands[j - 1]; + if (pos < 16) + cands[pos] = {idx, fov, approx}; } - if (best_idx < 0) { + if (ncands == 0) { static double last_none = 0.0; double now = esp_now(); if (now - last_none >= 2.0) { @@ -411,43 +423,57 @@ bool aimbot_apply(CUserCmd *cmd) { return false; } - static int sel_logs = 0; - if (sel_logs < 5) { - sel_logs++; - logf("aimbot: target idx=%d fov=%.2f eye=(%.1f %.1f %.1f) " - "approx_head=(%.1f %.1f %.1f)", - best_idx, best_fov, g_cam_eye.x, g_cam_eye.y, g_cam_eye.z, - best_head.x, best_head.y, best_head.z); - } - - // Refine to the nearest selected hitbox/bone point on the chosen target. - Vector pts[32]; - int npoints = 0; - void *best_ent = entitylist_get(best_idx); - if (best_ent && bones_update(best_idx, best_ent)) - npoints = aim_points(best_idx, bone_mask, pts, 32); - if (npoints <= 0) - return false; - float aim_fov = 1e9f; - Vector aim_pt{}; - for (int i = 0; i < npoints; i++) { - float f = getfov(view, angle_to(g_cam_eye, pts[i])); - if (f < aim_fov) { + // Walk targets nearest-first. On each, pick the nearest selected hitbox + // point that passes vischeck; skip to the next target if all are blocked. + int best_idx = -1; + Vector best_head{}; + for (int ci = 0; ci < ncands && best_idx < 0; ci++) { + int idx = cands[ci].idx; + void *ent = entitylist_get(idx); + if (!ent || !bones_update(idx, ent)) + continue; + Vector pts[32]; + int npoints = aim_points(idx, bone_mask, pts, 32); + float aim_fov = 1e9f; + Vector aim_pt{}; + for (int i = 0; i < npoints; i++) { + float f = getfov(view, angle_to(g_cam_eye, pts[i])); + if (f >= aim_fov) + continue; + if (aimbot_vischeck_enabled() && !is_visible(g_cam_eye, pts[i])) + continue; aim_fov = f; aim_pt = pts[i]; } + if (aim_fov <= MAX_FOV && aim_fov < 1e8f) { + best_idx = idx; + best_head = aim_pt; + static int sel_logs = 0; + if (sel_logs < 5) { + sel_logs++; + logf("aimbot: target idx=%d points=%d fov=%.2f vischeck=%d", idx, + npoints, aim_fov, (int)aimbot_vischeck_enabled()); + } + } } - if (aim_fov > MAX_FOV) + + if (best_idx < 0) { + static double last_vis = 0.0; + double now = esp_now(); + if (now - last_vis >= 2.0) { + last_vis = now; + logf("aimbot: no visible aim point (vischeck=%d)", + (int)aimbot_vischeck_enabled()); + } return false; - best_head = aim_pt; + } QAngle desired = angle_to(g_cam_eye, best_head); static int want_logs = 0; if (want_logs < 10) { want_logs++; - logf("aimbot: aim=(%.1f %.1f %.1f) points=%d fov=%.2f want=%.2f/%.2f", - best_head.x, best_head.y, best_head.z, npoints, aim_fov, desired.x, - desired.y); + logf("aimbot: aim=(%.1f %.1f %.1f) want=%.2f/%.2f", best_head.x, + best_head.y, best_head.z, desired.x, desired.y); } float dp = desired.x - cmd->viewangles[0]; float dy = desired.y - cmd->viewangles[1]; diff --git a/src/hl2h.cpp b/src/hl2h.cpp index c3b94fe..85012a0 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp @@ -408,9 +408,11 @@ static void *setup_thread(void *) { g_iface.modelinfo = ef("VModelInfoClient006", nullptr); g_iface.engineserver = ef("VEngineServer021", nullptr); g_iface.debugoverlay = ef("VDebugOverlay003", nullptr); + g_iface.enginetrace = ef("EngineTraceClient003", nullptr); logf("modelinfo: %p (VModelInfoClient006)", g_iface.modelinfo); - logf("engineserver: %p (VEngineServer021), debugoverlay: %p", - g_iface.engineserver, g_iface.debugoverlay); + logf("engineserver: %p (VEngineServer021), debugoverlay: %p, " + "enginetrace: %p (EngineTraceClient003)", + g_iface.engineserver, g_iface.debugoverlay, g_iface.enginetrace); resolve_netvars(); if (!find_input_global()) diff --git a/src/hl2h.h b/src/hl2h.h index 13af3d2..98d93f4 100644 --- a/src/hl2h.h +++ b/src/hl2h.h @@ -51,6 +51,7 @@ struct GameInterfaces { void *modelinfo = nullptr; // IVModelInfoClient006 (model index -> model name) void *engineserver = nullptr; // IVEngineServer021 (edict access, incl. server entities) void *debugoverlay = nullptr; // IVDebugOverlay003 (engine-accurate world->screen) + void *enginetrace = nullptr; // EngineTraceClient003 (world vischeck) }; extern GameInterfaces g_iface; @@ -168,11 +169,13 @@ bool esp_option(int opt); bool bhop_autostrafe_enabled(); unsigned aimbot_bone_mask(); int aimbot_smooth_step(); +bool aimbot_vischeck_enabled(); void *entitylist_get(int idx); int engine_get_local_player(); void draw_text_col(int x, int y, const char *s, int r, int g, int b); void esp_draw(); bool world_to_screen(const Vector &w, float &sx, float &sy); +bool is_visible(const Vector &eye, const Vector &point); // bones.cpp — client-side animated skeleton / hitbox access (see there) bool bones_update(int idx, void *ent); diff --git a/src/menu.cpp b/src/menu.cpp index db52fd2..d26b62e 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -75,6 +75,7 @@ enum { static bool g_aim_bones[AIM_BONE_COUNT] = {true, false, false, false, false, false}; static int g_aim_smooth = 5; // degrees per tick +static bool g_aim_vischeck = true; // ESP sub-options (menu "ESP" children). Defaults match the pre-option // behaviour: everything on. @@ -101,6 +102,7 @@ static MenuNode g_aimbot_feat{NODE_FEATURE, "Aimbot", false, 1, {}, nullptr, nul static MenuNode g_aimbot_active{NODE_TOGGLE, "Active", false, 0, {}, &g_aimbot.active, nullptr, nullptr}; static MenuNode g_aimbot_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_aimbot.bind, nullptr}; static MenuNode g_aimbot_bones_feat{NODE_FEATURE, "Bones", false, 0, {}, nullptr, nullptr, nullptr}; +static MenuNode g_aimbot_vischeck_node{NODE_TOGGLE, "Vischeck", false, 0, {}, &g_aim_vischeck, nullptr, nullptr}; static MenuNode g_aimbot_bone_nodes[AIM_BONE_COUNT] = { {NODE_TOGGLE, "Head", false, 0, {}, &g_aim_bones[AIM_BONE_HEAD], nullptr, nullptr}, {NODE_TOGGLE, "Torso", false, 0, {}, &g_aim_bones[AIM_BONE_TORSO], nullptr, nullptr}, @@ -126,9 +128,10 @@ static void build_tree() { g_esp_feat.children[0] = &g_esp_active; g_aimbot_feat.children[0] = &g_aimbot_active; g_aimbot_feat.children[1] = &g_aimbot_bind; - g_aimbot_feat.children[2] = &g_aimbot_bones_feat; - g_aimbot_feat.children[3] = &g_aimbot_smooth_node; - g_aimbot_feat.child_count = 4; + g_aimbot_feat.children[2] = &g_aimbot_vischeck_node; + g_aimbot_feat.children[3] = &g_aimbot_bones_feat; + g_aimbot_feat.children[4] = &g_aimbot_smooth_node; + g_aimbot_feat.child_count = 5; for (int i = 0; i < AIM_BONE_COUNT; i++) g_aimbot_bones_feat.children[i] = &g_aimbot_bone_nodes[i]; g_aimbot_bones_feat.child_count = AIM_BONE_COUNT; @@ -203,10 +206,11 @@ static void config_save() { fprintf(f, "[bhop]\nactive=%d\nbind=%d\nautostrafe=%d\n", g_bhop.active, g_bhop.bind, g_bhop_autostrafe); fprintf(f, "[abh]\nactive=%d\nbind=%d\n", g_abh.active, g_abh.bind); - fprintf(f, "[aimbot]\nactive=%d\nbind=%d\nsmooth=%d\n" + fprintf(f, "[aimbot]\nactive=%d\nbind=%d\nsmooth=%d\nvischeck=%d\n" "bone_head=%d\nbone_torso=%d\nbone_larm=%d\nbone_rarm=%d\n" "bone_lleg=%d\nbone_rleg=%d\n", g_aimbot.active, g_aimbot.bind, g_aim_smooth, + (int)g_aim_vischeck, g_aim_bones[AIM_BONE_HEAD], g_aim_bones[AIM_BONE_TORSO], g_aim_bones[AIM_BONE_LARM], g_aim_bones[AIM_BONE_RARM], g_aim_bones[AIM_BONE_LLEG], g_aim_bones[AIM_BONE_RLEG]); @@ -269,6 +273,8 @@ static void config_load() { g_aim_bones[AIM_BONE_LLEG] = atoi(val) != 0; } else if (strcmp(key, "bone_rleg") == 0) { g_aim_bones[AIM_BONE_RLEG] = atoi(val) != 0; + } else if (strcmp(key, "vischeck") == 0) { + g_aim_vischeck = atoi(val) != 0; } } if (in_esp) { @@ -429,6 +435,8 @@ unsigned aimbot_bone_mask() { int aimbot_smooth_step() { return g_aim_smooth; } +bool aimbot_vischeck_enabled() { return g_aim_vischeck; } + static void menu_handle_input() { poll_keyboard(); diff --git a/src/vischeck.cpp b/src/vischeck.cpp new file mode 100644 index 0000000..92d194b --- /dev/null +++ b/src/vischeck.cpp @@ -0,0 +1,98 @@ +// vischeck.cpp — world-only line-of-sight test via EngineTraceClient003. +// +// IEngineTrace::TraceRay (vtable slot 4) is given a ray from the eye to the +// aim point and a TRACE_WORLD_ONLY filter. MASK_VISIBLE ignores glass/windows +// (you can see through them) while still hitting opaque world geometry. +// Per the SDK, TRACE_WORLD_ONLY does not test static props; that is the +// deliberate trade-off for this build. + +#include "hl2h.h" +#include +#include + +namespace { + +struct alignas(16) VectorAlignedCompat { + float x, y, z, w; +}; + +// Ray_t: four VectorAligned (16 bytes each) followed by two bools. +struct alignas(16) RayCompat { + VectorAlignedCompat start; + VectorAlignedCompat delta; + VectorAlignedCompat startOffset; + VectorAlignedCompat extents; + bool isRay; + bool isSwept; +}; + +static_assert(sizeof(VectorAlignedCompat) == 16, "VectorAligned layout"); +static_assert(offsetof(RayCompat, delta) == 16, "Ray_t delta offset"); +static_assert(offsetof(RayCompat, isRay) == 64, "Ray_t isRay offset"); + +// trace_t / CBaseTrace prefix. cplane_t is Vector + float + type + signbits +// + 2 pad bytes, so fraction lands at 0x2C and startsolid at 0x37. +struct TraceCompat { + Vector startpos; + Vector endpos; + Vector normal; + float dist; + uint8_t type; + uint8_t signbits; + uint8_t pad[2]; + float fraction; + int contents; + uint16_t dispFlags; + bool allsolid; + bool startsolid; + uint8_t rest[128]; // CGameTrace tail (surface, ent, hitbox, ...) +}; + +static_assert(offsetof(TraceCompat, fraction) == 44, "trace_t fraction"); +static_assert(offsetof(TraceCompat, startsolid) == 55, "trace_t startsolid"); + +// ITraceFilter has no virtual destructor: vtable[0] = ShouldHitEntity, +// vtable[1] = GetTraceType. +struct ITraceFilterCompat { + virtual bool ShouldHitEntity(void *pEntity, int contentsMask) = 0; + virtual int GetTraceType() const = 0; +}; + +struct WorldOnlyFilter : ITraceFilterCompat { + bool ShouldHitEntity(void *, int) override { return false; } + int GetTraceType() const override { return 1; } // TRACE_WORLD_ONLY +}; + +// MASK_VISIBLE = CONTENTS_SOLID | CONTENTS_MOVEABLE | CONTENTS_OPAQUE | +// CONTENTS_IGNORE_NODRAW_OPAQUE +constexpr unsigned MASK_VISIBLE = 0x1u | 0x4000u | 0x80u | 0x2000u; + +} // namespace + +bool is_visible(const Vector &eye, const Vector &point) { + static bool warned = false; + if (!g_iface.enginetrace) { + if (!warned) { + warned = true; + logf("vischeck: EngineTraceClient003 unavailable; failing open"); + } + return true; + } + + float dx = point.x - eye.x, dy = point.y - eye.y, dz = point.z - eye.z; + RayCompat ray{}; + ray.start = {eye.x, eye.y, eye.z, 0.0f}; + ray.delta = {dx, dy, dz, 0.0f}; + ray.startOffset = {0.0f, 0.0f, 0.0f, 0.0f}; + ray.extents = {0.0f, 0.0f, 0.0f, 0.0f}; + ray.isRay = true; + ray.isSwept = (dx * dx + dy * dy + dz * dz) > 0.0f; + + TraceCompat tr{}; + WorldOnlyFilter filter; + using TraceRayFn = void (*)(void *, const RayCompat *, unsigned, void *, + TraceCompat *); + vfunc(g_iface.enginetrace, 4)(g_iface.enginetrace, &ray, + MASK_VISIBLE, &filter, &tr); + return tr.fraction > 0.97f && !tr.startsolid; +} -- cgit v1.2.3