diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/hl2h.cpp | 64 |
1 files changed, 64 insertions, 0 deletions
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 | |||
| 591 | static float (*g_orig_uniform_float)(void *, float, float) = nullptr; | ||
| 592 | static bool g_uniform_hook_installed = false; | ||
| 593 | |||
| 594 | static 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 | |||
| 609 | static 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 | |||
| 577 | static void do_move(int seq, float frametime) { | 640 | static 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; |
