From 1dd3a4d504e98b8d18f236cb1df1fea366ddc36e Mon Sep 17 00:00:00 2001 From: salmonik Date: Tue, 27 Oct 2026 15:49:54 +0100 Subject: Spread control: override the server FireBullets seed for the local player The installed 2024 server ignores the client usercmd random_seed and uses its own shot seed. Force the server's CBaseEntity::FireBullets RandomSeed call for the local player only: - hook the server.so RandomSeed GOT slot (R_386_JUMP_SLOT at 0xce1348); - read the FireBullets `this` from the caller frame and compare it with the local server player base; - replace the seed with the precomputed best single/shotgun window and step it per pellet; NPC shots and everything else pass through. - client prediction still uses the same chosen usercmd seed, so both sides agree on the spread pattern. --- src/esp.cpp | 19 ++++++++++++++++--- src/hl2h.cpp | 44 +++++++++++++++++++++++++++++++++++++++----- src/hl2h.h | 2 ++ 3 files changed, 57 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/esp.cpp b/src/esp.cpp index 0117d27..ae08258 100644 --- a/src/esp.cpp +++ b/src/esp.cpp @@ -1057,6 +1057,7 @@ static bool g_spread_ready = false; static int g_spread_seed1 = 0; static int g_spread_seed8 = 0; // first pellet exact, 7 spread static int g_spread_seed12 = 0; // all 12 spread +int g_spread_pellets = 1; static void spread_compute() { using SeedFn = void (*)(int); @@ -1163,9 +1164,11 @@ bool spread_control_apply(CUserCmd *cmd, void *local_ent) { return false; bool shotgun = active_weapon_is_shotgun(local_ent); - int seed = g_spread_seed1; + int pellets = 1; if (shotgun) - seed = (cmd->buttons & IN_ATTACK2) ? g_spread_seed12 : g_spread_seed8; + pellets = (cmd->buttons & IN_ATTACK2) ? 12 : 8; + g_spread_pellets = pellets; + int seed = spread_best_seed(pellets); if (cmd->random_seed == seed) return false; cmd->random_seed = seed; @@ -1173,11 +1176,21 @@ bool spread_control_apply(CUserCmd *cmd, void *local_ent) { static int logs = 0; if (logs < 10) { logs++; - logf("spread: seed=%d (%s)", seed, shotgun ? "shotgun" : "single"); + logf("spread: seed=%d pellets=%d", seed, pellets); } return true; } +int spread_best_seed(int pellets) { + if (!g_spread_ready) + return 33; + if (pellets >= 12) + return g_spread_seed12; + if (pellets >= 8) + return g_spread_seed8; + return g_spread_seed1; +} + bool triggerbot_apply(CUserCmd *cmd) { if (!g_iface.enginetrace || !g_iface.entitylist) return false; diff --git a/src/hl2h.cpp b/src/hl2h.cpp index 90e0990..c4f8aad 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp @@ -4,6 +4,7 @@ #include #include #include +#include // --------------------------------------------------------------------------- // logging @@ -417,24 +418,50 @@ static bool jumpbug_apply(CUserCmd *cmd, int local, void *ent, int flags, static void (*g_real_random_seed)(int) = nullptr; static uintptr_t g_srv_base = 0; +static uintptr_t g_local_server_player = 0; static bool g_seed_hook_installed = false; static void hkdSrvRandomSeed(int seed) { uintptr_t ret = (uintptr_t)__builtin_return_address(0); long off = (long)(ret - g_srv_base); static bool trace = getenv("HL2H_SEED_TRACE") != nullptr; - static bool force = getenv("HL2H_SEED_FORCE") != nullptr; static int logs = 0; bool bullet_site = (off >= 0x216900 && off <= 0x216930); + + void *frame = nullptr; + __asm__ volatile("movl %%ebp, %0" : "=r"(frame)); + if (frame) + frame = *(void **)frame; // caller's ebp (CBaseEntity::FireBullets) + void *self = frame ? *(void **)((char *)frame + 8) : nullptr; + bool local = self && g_local_server_player && + (uintptr_t)self == g_local_server_player; + + if (bullet_site && local && feature_active(FEAT_SPREAD) && + spread_no_spread_enabled()) { + int pellets = g_spread_pellets; + if (pellets < 1) + pellets = 1; + if (pellets > 12) + pellets = 12; + static int idx = 0; + static double last = 0.0; + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + double now = (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9; + if (now - last > 0.05 || idx >= pellets) + idx = 0; + last = now; + seed = spread_best_seed(pellets) + idx; + idx++; + } + if (trace && (bullet_site || logs < 12)) { if (logs < 200) { logs++; - logf("srvseed: seed=%d off=0x%lx bullet=%d", seed, off, - (int)bullet_site); + logf("srvseed: seed=%d off=0x%lx bullet=%d self=%p local=%d", seed, off, + (int)bullet_site, self, (int)local); } } - if (force && bullet_site) - seed = 33; if (g_real_random_seed) g_real_random_seed(seed); } @@ -491,6 +518,13 @@ static void do_move(int seq, float frametime) { if (!ent) return; + if (feature_active(FEAT_SPREAD)) { + resolve_server_netvars(); + void *srv = nullptr; + if (server_entity_health(localIdx, nullptr, nullptr, &srv) >= 0 && srv) + g_local_server_player = (uintptr_t)srv; + } + CUserCmd *cmd = get_usercmd(seq); if (!cmd) return; diff --git a/src/hl2h.h b/src/hl2h.h index ef8e8d6..285b591 100644 --- a/src/hl2h.h +++ b/src/hl2h.h @@ -151,6 +151,8 @@ bool silent_aim_apply(CUserCmd *cmd); bool melee_weapon_active(void *local_ent); bool melee_aimbot_apply(CUserCmd *cmd); bool spread_control_apply(CUserCmd *cmd, void *local_ent); +int spread_best_seed(int pellets); +extern int g_spread_pellets; enum FeatureId { FEAT_BHOP = 0, FEAT_ABH = 1, -- cgit v1.2.3