diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/hl2h.cpp | 50 | ||||
| -rw-r--r-- | src/hl2h.h | 2 | ||||
| -rw-r--r-- | src/modules.cpp | 99 | ||||
| -rw-r--r-- | src/server.cpp | 11 |
4 files changed, 144 insertions, 18 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) |
| @@ -278,6 +278,8 @@ int server_npc_takedamage(int idx); | |||
| 278 | int server_npc_turret_flags(int idx); | 278 | int server_npc_turret_flags(int idx); |
| 279 | int server_model_index(void *base); | 279 | int server_model_index(void *base); |
| 280 | void find_modules(); | 280 | void find_modules(); |
| 281 | bool module_jump_slot(const char *path, const char *name, uintptr_t *vaddr, | ||
| 282 | uintptr_t *bias); | ||
| 281 | void init_conmsg(); | 283 | void init_conmsg(); |
| 282 | bool feature_active(int feature); | 284 | bool feature_active(int feature); |
| 283 | bool feature_has_bind(int feature); | 285 | bool feature_has_bind(int feature); |
diff --git a/src/modules.cpp b/src/modules.cpp index 70e2dff..b832694 100644 --- a/src/modules.cpp +++ b/src/modules.cpp | |||
| @@ -1,5 +1,7 @@ | |||
| 1 | // modules.cpp — /proc/self/maps parsing and module discovery | 1 | // modules.cpp — /proc/self/maps parsing and module discovery |
| 2 | #include "hl2h.h" | 2 | #include "hl2h.h" |
| 3 | #include <elf.h> | ||
| 4 | #include <vector> | ||
| 3 | 5 | ||
| 4 | static bool basename_is(const char *path, const char *base) { | 6 | static bool basename_is(const char *path, const char *base) { |
| 5 | size_t pl = strlen(path), bl = strlen(base); | 7 | size_t pl = strlen(path), bl = strlen(base); |
| @@ -70,3 +72,100 @@ void find_modules() { | |||
| 70 | } | 72 | } |
| 71 | fclose(f); | 73 | fclose(f); |
| 72 | } | 74 | } |
| 75 | |||
| 76 | // --------------------------------------------------------------------------- | ||
| 77 | // ELF relocation lookup | ||
| 78 | // --------------------------------------------------------------------------- | ||
| 79 | // | ||
| 80 | // The server RandomSeed hook used to hardcode the GOT offset measured on one | ||
| 81 | // server.so build. The anniversary branch ships a different server.so | ||
| 82 | // (hl2_complete/bin/server.so) whose slot moved, so the patch silently landed | ||
| 83 | // on unrelated data and No spread stopped working. Read the loaded module's | ||
| 84 | // ELF relocation table instead: find the R_386_JMP_SLOT for a symbol and | ||
| 85 | // return its virtual address plus the load bias implied by the first PT_LOAD. | ||
| 86 | bool module_jump_slot(const char *path, const char *name, uintptr_t *out_vaddr, | ||
| 87 | uintptr_t *out_bias) { | ||
| 88 | if (!path || !*path || !name) | ||
| 89 | return false; | ||
| 90 | FILE *f = fopen(path, "rb"); | ||
| 91 | if (!f) | ||
| 92 | return false; | ||
| 93 | if (fseek(f, 0, SEEK_END) != 0) { | ||
| 94 | fclose(f); | ||
| 95 | return false; | ||
| 96 | } | ||
| 97 | long sz = ftell(f); | ||
| 98 | if (sz < (long)sizeof(Elf32_Ehdr) || fseek(f, 0, SEEK_SET) != 0) { | ||
| 99 | fclose(f); | ||
| 100 | return false; | ||
| 101 | } | ||
| 102 | std::vector<unsigned char> buf((size_t)sz); | ||
| 103 | size_t got = fread(buf.data(), 1, buf.size(), f); | ||
| 104 | fclose(f); | ||
| 105 | if (got != buf.size()) | ||
| 106 | return false; | ||
| 107 | if (buf.size() < sizeof(Elf32_Ehdr)) | ||
| 108 | return false; | ||
| 109 | |||
| 110 | const Elf32_Ehdr *eh = (const Elf32_Ehdr *)buf.data(); | ||
| 111 | if (memcmp(eh->e_ident, ELFMAG, SELFMAG) != 0 || | ||
| 112 | eh->e_ident[EI_CLASS] != ELFCLASS32) | ||
| 113 | return false; | ||
| 114 | if (eh->e_shoff == 0 || eh->e_shentsize != sizeof(Elf32_Shdr)) | ||
| 115 | return false; | ||
| 116 | if ((uint64_t)eh->e_shoff + (uint64_t)eh->e_shnum * sizeof(Elf32_Shdr) > | ||
| 117 | buf.size()) | ||
| 118 | return false; | ||
| 119 | const Elf32_Shdr *sh = (const Elf32_Shdr *)(buf.data() + eh->e_shoff); | ||
| 120 | |||
| 121 | uintptr_t bias = 0; | ||
| 122 | if (eh->e_phoff && eh->e_phnum && | ||
| 123 | eh->e_phentsize == sizeof(Elf32_Phdr) && | ||
| 124 | (uint64_t)eh->e_phoff + (uint64_t)eh->e_phnum * sizeof(Elf32_Phdr) <= | ||
| 125 | buf.size()) { | ||
| 126 | const Elf32_Phdr *ph = (const Elf32_Phdr *)(buf.data() + eh->e_phoff); | ||
| 127 | for (int i = 0; i < eh->e_phnum; i++) { | ||
| 128 | if (ph[i].p_type == PT_LOAD) { | ||
| 129 | bias = ph[i].p_vaddr; | ||
| 130 | break; | ||
| 131 | } | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | for (int s = 0; s < eh->e_shnum; s++) { | ||
| 136 | if (sh[s].sh_type != SHT_REL || sh[s].sh_link >= eh->e_shnum) | ||
| 137 | continue; | ||
| 138 | if ((uint64_t)sh[s].sh_offset + (uint64_t)sh[s].sh_size > buf.size()) | ||
| 139 | continue; | ||
| 140 | const Elf32_Shdr &symsh = sh[sh[s].sh_link]; | ||
| 141 | if (symsh.sh_link >= eh->e_shnum) | ||
| 142 | continue; | ||
| 143 | const Elf32_Shdr &strsh = sh[symsh.sh_link]; | ||
| 144 | if ((uint64_t)symsh.sh_offset + (uint64_t)symsh.sh_size > buf.size() || | ||
| 145 | (uint64_t)strsh.sh_offset + (uint64_t)strsh.sh_size > buf.size()) | ||
| 146 | continue; | ||
| 147 | const Elf32_Rel *rels = (const Elf32_Rel *)(buf.data() + sh[s].sh_offset); | ||
| 148 | size_t nrels = sh[s].sh_size / sizeof(Elf32_Rel); | ||
| 149 | const Elf32_Sym *syms = (const Elf32_Sym *)(buf.data() + symsh.sh_offset); | ||
| 150 | size_t nsyms = symsh.sh_size / sizeof(Elf32_Sym); | ||
| 151 | const char *strs = (const char *)(buf.data() + strsh.sh_offset); | ||
| 152 | size_t strn = strsh.sh_size; | ||
| 153 | for (size_t r = 0; r < nrels; r++) { | ||
| 154 | unsigned type = ELF32_R_TYPE(rels[r].r_info); | ||
| 155 | unsigned sym = ELF32_R_SYM(rels[r].r_info); | ||
| 156 | if (type != R_386_JMP_SLOT || sym >= nsyms) | ||
| 157 | continue; | ||
| 158 | if (syms[sym].st_name >= strn) | ||
| 159 | continue; | ||
| 160 | const char *sn = strs + syms[sym].st_name; | ||
| 161 | if (strcmp(sn, name) != 0) | ||
| 162 | continue; | ||
| 163 | if (out_vaddr) | ||
| 164 | *out_vaddr = rels[r].r_offset; | ||
| 165 | if (out_bias) | ||
| 166 | *out_bias = bias; | ||
| 167 | return true; | ||
| 168 | } | ||
| 169 | } | ||
| 170 | return false; | ||
| 171 | } | ||
diff --git a/src/server.cpp b/src/server.cpp index e9fd921..f71b441 100644 --- a/src/server.cpp +++ b/src/server.cpp | |||
| @@ -142,7 +142,11 @@ bool resolve_server_netvars() { | |||
| 142 | g_srv_spawnflags = datamap_offset("m_spawnflags", FIELD_INTEGER, 0x4); | 142 | g_srv_spawnflags = datamap_offset("m_spawnflags", FIELD_INTEGER, 0x4); |
| 143 | g_srv_hacked = datamap_offset("m_bHackedByAlyx", FIELD_BOOLEAN); | 143 | g_srv_hacked = datamap_offset("m_bHackedByAlyx", FIELD_BOOLEAN); |
| 144 | g_srv_npcstate = datamap_offset("m_NPCState", FIELD_INTEGER); | 144 | g_srv_npcstate = datamap_offset("m_NPCState", FIELD_INTEGER); |
| 145 | g_srv_takedamage = datamap_offset("m_takedamage", FIELD_INTEGER); | 145 | // CBaseEntity::m_takedamage is a char in the 2013 SDK |
| 146 | // (CNetworkVarForDerived(char, m_takedamage)); older builds used int. | ||
| 147 | g_srv_takedamage = datamap_offset("m_takedamage", FIELD_CHARACTER); | ||
| 148 | if (g_srv_takedamage < 0) | ||
| 149 | g_srv_takedamage = datamap_offset("m_takedamage", FIELD_INTEGER); | ||
| 146 | g_srv_ready = g_srv_health > 0; | 150 | g_srv_ready = g_srv_health > 0; |
| 147 | if (g_srv_ready) { | 151 | if (g_srv_ready) { |
| 148 | logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d " | 152 | logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d " |
| @@ -272,7 +276,8 @@ int server_npc_state(int idx) { | |||
| 272 | } | 276 | } |
| 273 | 277 | ||
| 274 | // TAKEDAMAGE: 0 = DAMAGE_NO, 1 = DAMAGE_EVENTS_ONLY, 2 = DAMAGE_YES, | 278 | // TAKEDAMAGE: 0 = DAMAGE_NO, 1 = DAMAGE_EVENTS_ONLY, 2 = DAMAGE_YES, |
| 275 | // 3 = DAMAGE_AIM. 0/1 cannot be damaged at all. | 279 | // 3 = DAMAGE_AIM. 0/1 cannot be damaged at all. The field is a char, so read |
| 280 | // a byte even if the older int layout was resolved. | ||
| 276 | int server_npc_takedamage(int idx) { | 281 | int server_npc_takedamage(int idx) { |
| 277 | if (!g_srv_ready || g_srv_takedamage < 0) | 282 | if (!g_srv_ready || g_srv_takedamage < 0) |
| 278 | return -1; | 283 | return -1; |
| @@ -280,7 +285,7 @@ int server_npc_takedamage(int idx) { | |||
| 280 | server_entity_health(idx, nullptr, nullptr, &base); | 285 | server_entity_health(idx, nullptr, nullptr, &base); |
| 281 | if (!base) | 286 | if (!base) |
| 282 | return -1; | 287 | return -1; |
| 283 | return *(int *)((uintptr_t)base + g_srv_takedamage); | 288 | return (int)*(unsigned char *)((uintptr_t)base + g_srv_takedamage); |
| 284 | } | 289 | } |
| 285 | 290 | ||
| 286 | bool server_npc_has_follow_target(int idx) { | 291 | bool server_npc_has_follow_target(int idx) { |
