diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 13:19:50 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 13:19:50 +0100 |
| commit | 6a7e0b3bae04a5a4667267b6a45baddeb66fc570 (patch) | |
| tree | 557c31924d468345a15886ac6e75209d00c49524 /src/hl2h.cpp | |
| parent | Studio: drop the prop draw diagnostic (diff) | |
| download | hl2h-6a7e0b3bae04a5a4667267b6a45baddeb66fc570.tar.gz hl2h-6a7e0b3bae04a5a4667267b6a45baddeb66fc570.zip | |
Fix No spread on the hl2_complete server build
The server RandomSeed GOT slot was hardcoded to 0xce1348, measured on the
older bin/server.so. The anniversary branch loads hl2_complete/bin/server.so,
whose R_386_JMP_SLOT for RandomSeed is 0xce5348, so the patch hit unrelated
writable data and no spread silently did nothing.
- Read the relocation from the loaded module's ELF (module_jump_slot) instead
of hardcoding, with the two known offsets as fallback.
- Override whenever the RandomSeed caller's `this` is the local server player
(CBaseEntity::FireBullets) instead of matching a hardcoded return-address
range that also moved between builds.
- m_takedamage is a char (FIELD_CHARACTER) in the 2013 SDK; resolve it as such
and read a byte, so the IMMUNE damage tag actually works.
Diffstat (limited to 'src/hl2h.cpp')
| -rw-r--r-- | src/hl2h.cpp | 50 |
1 files changed, 35 insertions, 15 deletions
diff --git a/src/hl2h.cpp b/src/hl2h.cpp index 28b2995..7ce35d3 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp | |||
| @@ -471,11 +471,12 @@ static bool jumpbug_apply(CUserCmd *cmd, int local, void *ent, int flags, | |||
| 471 | // server RandomSeed diagnostic / override | 471 | // server RandomSeed diagnostic / override |
| 472 | // --------------------------------------------------------------------------- | 472 | // --------------------------------------------------------------------------- |
| 473 | // | 473 | // |
| 474 | // The installed 2024 server build is stripped and its usercmd/server-random-seed | 474 | // The server is a stripped build whose usercmd/random-seed handling differs |
| 475 | // handling differs from the 2013 SDK source. This hook is installed on the | 475 | // from the 2013 SDK source. Hook the server.so PLT slot for RandomSeed so we |
| 476 | // server.so GOT slot for RandomSeed (R_386_JUMP_SLOT at vaddr 0xce1348) so we | 476 | // can force the chosen spread seed. The GOT offset is read from the loaded |
| 477 | // can see which seed FireBullets actually uses and optionally force the chosen | 477 | // module's relocation table at runtime (module_jump_slot): hardcoding it broke |
| 478 | // spread seed. Disabled unless HL2H_SEED_TRACE / HL2H_SEED_FORCE are set. | 478 | // when Steam switched to the hl2_complete/server.so branch (0xce1348 -> |
| 479 | // 0xce5348). | ||
| 479 | 480 | ||
| 480 | static void (*g_real_random_seed)(int) = nullptr; | 481 | static void (*g_real_random_seed)(int) = nullptr; |
| 481 | static uintptr_t g_srv_base = 0; | 482 | static uintptr_t g_srv_base = 0; |
| @@ -487,8 +488,10 @@ static void hkdSrvRandomSeed(int seed) { | |||
| 487 | long off = (long)(ret - g_srv_base); | 488 | long off = (long)(ret - g_srv_base); |
| 488 | static bool trace = getenv("HL2H_SEED_TRACE") != nullptr; | 489 | static bool trace = getenv("HL2H_SEED_TRACE") != nullptr; |
| 489 | static int logs = 0; | 490 | static int logs = 0; |
| 490 | bool bullet_site = (off >= 0x216900 && off <= 0x216930); | ||
| 491 | 491 | ||
| 492 | // The caller's first argument is `this`. Only FireBullets is called on the | ||
| 493 | // player entity; the CUniformRandomStream wrappers and the constructor-time | ||
| 494 | // RandomSeed(0) reset have a different object there. | ||
| 492 | void *frame = nullptr; | 495 | void *frame = nullptr; |
| 493 | __asm__ volatile("movl %%ebp, %0" : "=r"(frame)); | 496 | __asm__ volatile("movl %%ebp, %0" : "=r"(frame)); |
| 494 | if (frame) | 497 | if (frame) |
| @@ -497,7 +500,7 @@ static void hkdSrvRandomSeed(int seed) { | |||
| 497 | bool local = self && g_local_server_player && | 500 | bool local = self && g_local_server_player && |
| 498 | (uintptr_t)self == g_local_server_player; | 501 | (uintptr_t)self == g_local_server_player; |
| 499 | 502 | ||
| 500 | if (bullet_site && local && feature_active(FEAT_SPREAD) && | 503 | if (local && g_server.contains(ret) && feature_active(FEAT_SPREAD) && |
| 501 | spread_no_spread_enabled()) { | 504 | spread_no_spread_enabled()) { |
| 502 | int pellets = g_spread_pellets; | 505 | int pellets = g_spread_pellets; |
| 503 | if (pellets < 1) | 506 | if (pellets < 1) |
| @@ -516,12 +519,10 @@ static void hkdSrvRandomSeed(int seed) { | |||
| 516 | idx++; | 519 | idx++; |
| 517 | } | 520 | } |
| 518 | 521 | ||
| 519 | if (trace && (bullet_site || logs < 12)) { | 522 | if (trace && logs < 200) { |
| 520 | if (logs < 200) { | 523 | logs++; |
| 521 | logs++; | 524 | logd("srvseed: seed=%d off=0x%lx self=%p local=%d", seed, off, self, |
| 522 | logd("srvseed: seed=%d off=0x%lx bullet=%d self=%p local=%d", seed, off, | 525 | (int)local); |
| 523 | (int)bullet_site, self, (int)local); | ||
| 524 | } | ||
| 525 | } | 526 | } |
| 526 | if (g_real_random_seed) | 527 | if (g_real_random_seed) |
| 527 | g_real_random_seed(seed); | 528 | g_real_random_seed(seed); |
| @@ -532,9 +533,28 @@ static void install_seed_hook() { | |||
| 532 | if (g_seed_hook_installed || failed || !g_server.valid()) | 533 | if (g_seed_hook_installed || failed || !g_server.valid()) |
| 533 | return; | 534 | return; |
| 534 | g_srv_base = g_server.ranges[0].lo; | 535 | g_srv_base = g_server.ranges[0].lo; |
| 535 | uintptr_t slot = g_srv_base + 0xce1348; // R_386_JUMP_SLOT RandomSeed | 536 | uintptr_t reloc = 0, bias = 0; |
| 536 | if (!g_server.contains(slot)) | 537 | uintptr_t slot = 0; |
| 538 | if (module_jump_slot(g_server.path.c_str(), "RandomSeed", &reloc, &bias)) { | ||
| 539 | slot = g_srv_base + reloc - bias; | ||
| 540 | logf("seed hook: RandomSeed GOT reloc=%#lx bias=%#lx -> %#lx", | ||
| 541 | (unsigned long)reloc, (unsigned long)bias, (unsigned long)slot); | ||
| 542 | } else { | ||
| 543 | // ELF parse failed (deleted/unreadable module); try the builds we know. | ||
| 544 | static const uintptr_t known[] = {0xce5348, 0xce1348}; | ||
| 545 | for (size_t i = 0; i < sizeof(known) / sizeof(known[0]) && !slot; i++) { | ||
| 546 | uintptr_t cand = g_srv_base + known[i]; | ||
| 547 | if (g_server.contains(cand)) | ||
| 548 | slot = cand; | ||
| 549 | } | ||
| 550 | logf("seed hook: ELF reloc scan failed, fallback slot=%#lx", | ||
| 551 | (unsigned long)slot); | ||
| 552 | } | ||
| 553 | if (!slot || !g_server.contains(slot)) { | ||
| 554 | failed = true; | ||
| 555 | logf("seed hook: no writable RandomSeed slot"); | ||
| 537 | return; | 556 | return; |
| 557 | } | ||
| 538 | g_real_random_seed = | 558 | g_real_random_seed = |
| 539 | (void (*)(int))dlsym(RTLD_NEXT, "RandomSeed"); | 559 | (void (*)(int))dlsym(RTLD_NEXT, "RandomSeed"); |
| 540 | if (!g_real_random_seed) | 560 | if (!g_real_random_seed) |
