summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--CONTEXT.md8
-rw-r--r--src/hl2h.cpp64
2 files changed, 72 insertions, 0 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index bd3507a..75b2f0a 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -298,6 +298,14 @@ vocabulary / decisions.
298 `CBaseEntity::FireBullets`; the `CUniformRandomStream` wrappers and 298 `CBaseEntity::FireBullets`; the `CUniformRandomStream` wrappers and
299 constructor-time `RandomSeed(0)` resets pass other objects and are left 299 constructor-time `RandomSeed(0)` resets pass other objects and are left
300 alone. 300 alone.
301- Seed choice alone cannot remove all of a shotgun's pellet spread: the best
302 of 256 sequences still leaves its 8/12 draws apart. `CShotManipulator::
303 ApplySpread` is inlined into `CBaseEntity::FireBullets` and draws x/y via
304 the global `CUniformRandomStream` (`_ZTV20CUniformRandomStream` + 8, slot 1
305 = `RandomFloat(float,float)`; confirmed against `libvstdlib.so`). The hook
306 returns 0 for those draws only when the calling frame's `this` (at
307 `ebp+8`) is the local server player, so every pellet follows the exact shot
308 direction. NPC fire and the client's prediction stream keep normal values.
301- This attacks the random cone offset only. The cone size, the pistol's 309- This attacks the random cone offset only. The cone size, the pistol's
302 `m_flAccuracyPenalty` growth, the server view punch added to 310 `m_flAccuracyPenalty` growth, the server view punch added to
303 `GetAutoaimVector`, and any damage falloff are server-side state. In 311 `GetAutoaimVector`, and any damage falloff are server-side state. In
diff --git a/src/hl2h.cpp b/src/hl2h.cpp
index 7ce35d3..02bf584 100644
--- a/src/hl2h.cpp
+++ b/src/hl2h.cpp
@@ -574,8 +574,72 @@ static void install_seed_hook() {
574 (void *)g_real_random_seed); 574 (void *)g_real_random_seed);
575} 575}
576 576
577
578// ---------------------------------------------------------------------------
579// pellet spread: zero the random offsets inside FireBullets
580// ---------------------------------------------------------------------------
581//
582// CShotManipulator::ApplySpread is inlined into CBaseEntity::FireBullets and
583// draws its x/y offsets through the global CUniformRandomStream (vtable slot
584// 1, confirmed against libvstdlib: _ZTV20CUniformRandomStream + 8 + 4). The
585// seed search can only choose the least-bad sequence, which still leaves the
586// shotgun's pellets slightly apart. Returning 0 for these draws makes every
587// pellet follow the shot direction exactly. The hook is conditional on the
588// calling frame belonging to the local server player, so NPC fire and the
589// client's own prediction stream keep their randomness.
590
591static float (*g_orig_uniform_float)(void *, float, float) = nullptr;
592static bool g_uniform_hook_installed = false;
593
594static float hkdUniformRandomFloat(void *self, float mn, float mx) {
595 if (g_local_server_player && feature_active(FEAT_SPREAD) &&
596 spread_no_spread_enabled()) {
597 void *frame = nullptr;
598 __asm__ volatile("movl %%ebp, %0" : "=r"(frame));
599 if (frame) {
600 frame = *(void **)frame; // caller's ebp (CBaseEntity::FireBullets)
601 void *who = frame ? *(void **)((char *)frame + 8) : nullptr;
602 if (who && (uintptr_t)who == g_local_server_player)
603 return 0.0f;
604 }
605 }
606 return g_orig_uniform_float ? g_orig_uniform_float(self, mn, mx) : mn;
607}
608
609static void install_uniform_random_hook() {
610 if (g_uniform_hook_installed)
611 return;
612 void *vt = dlsym(RTLD_DEFAULT, "_ZTV20CUniformRandomStream");
613 if (!vt) {
614 logf("spread: CUniformRandomStream vtable not found");
615 return;
616 }
617 void **ap = (void **)((char *)vt + 8); // skip offset-to-top and typeinfo
618 void *rf = dlsym(RTLD_DEFAULT, "_ZN20CUniformRandomStream11RandomFloatEff");
619 int slot = 1; // RandomFloat(min,max) follows SetSeed in the vtable
620 if (rf) {
621 for (int i = 0; i < 8; i++) {
622 if (ap[i] == rf) {
623 slot = i;
624 break;
625 }
626 }
627 }
628 g_orig_uniform_float = (float (*)(void *, float, float))ap[slot];
629 uintptr_t page = (uintptr_t)&ap[slot] & ~(uintptr_t)0xFFF;
630 if (mprotect((void *)page, 0x1000, PROT_READ | PROT_WRITE) != 0) {
631 logf("spread: could not make the random vtable writable");
632 return;
633 }
634 ap[slot] = (void *)&hkdUniformRandomFloat;
635 g_uniform_hook_installed = true;
636 logf("spread: CUniformRandomStream::RandomFloat slot %d (%p) -> %p", slot,
637 (void *)g_orig_uniform_float, (void *)&hkdUniformRandomFloat);
638}
639
577static void do_move(int seq, float frametime) { 640static void do_move(int seq, float frametime) {
578 install_seed_hook(); 641 install_seed_hook();
642 install_uniform_random_hook();
579 static bool g_echo_sent = false; 643 static bool g_echo_sent = false;
580 if (!g_input || !g_net.ok()) 644 if (!g_input || !g_net.ok())
581 return; 645 return;