diff options
Diffstat (limited to 'src/hl2h.cpp')
| -rw-r--r-- | src/hl2h.cpp | 214 |
1 files changed, 159 insertions, 55 deletions
diff --git a/src/hl2h.cpp b/src/hl2h.cpp index ddfc02a..6697778 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp | |||
| @@ -1,5 +1,6 @@ | |||
| 1 | // hl2h.cpp — core: logging, vector math, globals, feature logic, setup | 1 | // hl2h.cpp — core: logging, vector math, globals, feature logic, setup |
| 2 | #include "hl2h.h" | 2 | #include "hl2h.h" |
| 3 | #include <atomic> | ||
| 3 | #include <cmath> | 4 | #include <cmath> |
| 4 | #include <cstdarg> | 5 | #include <cstdarg> |
| 5 | #include <cstdlib> | 6 | #include <cstdlib> |
| @@ -7,6 +8,41 @@ | |||
| 7 | #include <time.h> | 8 | #include <time.h> |
| 8 | 9 | ||
| 9 | // --------------------------------------------------------------------------- | 10 | // --------------------------------------------------------------------------- |
| 11 | // init readiness | ||
| 12 | // --------------------------------------------------------------------------- | ||
| 13 | |||
| 14 | static std::atomic<int> g_init_stage{INIT_NONE}; | ||
| 15 | |||
| 16 | void init_set_stage(int stage) { | ||
| 17 | g_init_stage.store(stage, std::memory_order_release); | ||
| 18 | } | ||
| 19 | |||
| 20 | int init_stage() { return g_init_stage.load(std::memory_order_acquire); } | ||
| 21 | |||
| 22 | // --------------------------------------------------------------------------- | ||
| 23 | // vtable cloning | ||
| 24 | // --------------------------------------------------------------------------- | ||
| 25 | // | ||
| 26 | // Under the Itanium C++ ABI the two words before slot 0 are offset-to-top and | ||
| 27 | // the type_info pointer. Copying only the slots and repointing the object's | ||
| 28 | // vptr leaves those words pointing at allocator metadata, which breaks any | ||
| 29 | // RTTI the host performs on the interface. Keep the header words with the | ||
| 30 | // clone and hand back slot 0. | ||
| 31 | |||
| 32 | void **vtable_clone(void *iface, size_t slots) { | ||
| 33 | if (!iface || slots == 0) | ||
| 34 | return nullptr; | ||
| 35 | void **vt = *(void ***)iface; | ||
| 36 | if (!vt) | ||
| 37 | return nullptr; | ||
| 38 | void **out = new void *[slots + 2]; | ||
| 39 | out[0] = vt[-2]; | ||
| 40 | out[1] = vt[-1]; | ||
| 41 | memcpy(out + 2, vt, slots * sizeof(void *)); | ||
| 42 | return out + 2; | ||
| 43 | } | ||
| 44 | |||
| 45 | // --------------------------------------------------------------------------- | ||
| 10 | // logging | 46 | // logging |
| 11 | // --------------------------------------------------------------------------- | 47 | // --------------------------------------------------------------------------- |
| 12 | 48 | ||
| @@ -67,7 +103,6 @@ void logd(const char *fmt, ...) { | |||
| 67 | // globals | 103 | // globals |
| 68 | // --------------------------------------------------------------------------- | 104 | // --------------------------------------------------------------------------- |
| 69 | 105 | ||
| 70 | Module g_client, g_engine, g_server; | ||
| 71 | GameInterfaces g_iface; | 106 | GameInterfaces g_iface; |
| 72 | NetVars g_net; | 107 | NetVars g_net; |
| 73 | Vector g_cam_eye{0, 0, 0}; | 108 | Vector g_cam_eye{0, 0, 0}; |
| @@ -87,11 +122,11 @@ QAngle vector_angles(const Vector &forward) { | |||
| 87 | a.y = atan2f(forward.y, forward.x) * 180.f / PI_F; | 122 | a.y = atan2f(forward.y, forward.x) * 180.f / PI_F; |
| 88 | if (a.y < 0) | 123 | if (a.y < 0) |
| 89 | a.y += 360.f; | 124 | a.y += 360.f; |
| 125 | // Pitch stays in [-90, 90] like Source's QAngle; wrapping negatives to | ||
| 126 | // [270, 360) made every upward aim compare as a steeply negative angle. | ||
| 90 | a.x = atan2f(-forward.z, | 127 | a.x = atan2f(-forward.z, |
| 91 | sqrtf(forward.x * forward.x + forward.y * forward.y)) * | 128 | sqrtf(forward.x * forward.x + forward.y * forward.y)) * |
| 92 | 180.f / PI_F; | 129 | 180.f / PI_F; |
| 93 | if (a.x < 0) | ||
| 94 | a.x += 360.f; | ||
| 95 | } | 130 | } |
| 96 | return a; | 131 | return a; |
| 97 | } | 132 | } |
| @@ -485,7 +520,31 @@ static bool g_seed_hook_installed = false; | |||
| 485 | // Pellet-spread window armed by the player's FireBullets RandomSeed call. | 520 | // Pellet-spread window armed by the player's FireBullets RandomSeed call. |
| 486 | static volatile int g_spread_rand_left = 0; | 521 | static volatile int g_spread_rand_left = 0; |
| 487 | 522 | ||
| 523 | // Identify the caller of a random-stream function by reading the caller's | ||
| 524 | // stack frame. The callers live in Valve's binaries, which are not built with | ||
| 525 | // the guarantee our own -fno-omit-frame-pointer gives us, so treat the walk as | ||
| 526 | // untrusted: it may only stay inside the current stack, and the object it | ||
| 527 | // yields is used for nothing but comparison against g_local_server_player. | ||
| 528 | static void *caller_self_arg() { | ||
| 529 | void *sp = nullptr, *bp = nullptr; | ||
| 530 | __asm__ volatile("movl %%esp, %0" : "=r"(sp)); | ||
| 531 | __asm__ volatile("movl %%ebp, %0" : "=r"(bp)); | ||
| 532 | uintptr_t spv = (uintptr_t)sp, bpv = (uintptr_t)bp; | ||
| 533 | constexpr uintptr_t MAX_DEPTH = 0x100000; // 1 MiB of stack frames | ||
| 534 | if (!bpv || (bpv & 3) || bpv <= spv || bpv - spv > MAX_DEPTH) | ||
| 535 | return nullptr; | ||
| 536 | uintptr_t caller = *(uintptr_t *)bpv; | ||
| 537 | if (!caller || (caller & 3) || caller < bpv || caller - spv > MAX_DEPTH) | ||
| 538 | return nullptr; | ||
| 539 | return *(void **)(caller + 8); // first argument slot of the caller | ||
| 540 | } | ||
| 541 | |||
| 488 | static void hkdSrvRandomSeed(int seed) { | 542 | static void hkdSrvRandomSeed(int seed) { |
| 543 | if (!init_live()) { | ||
| 544 | if (g_real_random_seed) | ||
| 545 | g_real_random_seed(seed); | ||
| 546 | return; | ||
| 547 | } | ||
| 489 | uintptr_t ret = (uintptr_t)__builtin_return_address(0); | 548 | uintptr_t ret = (uintptr_t)__builtin_return_address(0); |
| 490 | long off = (long)(ret - g_srv_base); | 549 | long off = (long)(ret - g_srv_base); |
| 491 | static bool trace = getenv("HL2H_SEED_TRACE") != nullptr; | 550 | static bool trace = getenv("HL2H_SEED_TRACE") != nullptr; |
| @@ -494,16 +553,16 @@ static void hkdSrvRandomSeed(int seed) { | |||
| 494 | // The caller's first argument is `this`. Only FireBullets is called on the | 553 | // The caller's first argument is `this`. Only FireBullets is called on the |
| 495 | // player entity; the CUniformRandomStream wrappers and the constructor-time | 554 | // player entity; the CUniformRandomStream wrappers and the constructor-time |
| 496 | // RandomSeed(0) reset have a different object there. | 555 | // RandomSeed(0) reset have a different object there. |
| 497 | void *frame = nullptr; | 556 | void *self = caller_self_arg(); |
| 498 | __asm__ volatile("movl %%ebp, %0" : "=r"(frame)); | ||
| 499 | if (frame) | ||
| 500 | frame = *(void **)frame; // caller's ebp (CBaseEntity::FireBullets) | ||
| 501 | void *self = frame ? *(void **)((char *)frame + 8) : nullptr; | ||
| 502 | bool local = self && g_local_server_player && | 557 | bool local = self && g_local_server_player && |
| 503 | (uintptr_t)self == g_local_server_player; | 558 | (uintptr_t)self == g_local_server_player; |
| 504 | 559 | ||
| 505 | if (local && g_server.contains(ret) && feature_active(FEAT_SPREAD) && | 560 | // Seed selection and engine spread zeroing are independent toggles: "engine |
| 506 | spread_no_spread_enabled() && spread_engine_enabled()) { | 561 | // override" only decides whether RandomFloat is forced to 0, it must not |
| 562 | // disable the seed hook (without the seed hook the server ignores | ||
| 563 | // cmd->random_seed entirely). | ||
| 564 | if (local && mod_server().contains(ret) && feature_active(FEAT_SPREAD) && | ||
| 565 | spread_no_spread_enabled()) { | ||
| 507 | g_spread_rand_left = 8; | 566 | g_spread_rand_left = 8; |
| 508 | int pellets = g_spread_pellets; | 567 | int pellets = g_spread_pellets; |
| 509 | if (pellets < 1) | 568 | if (pellets < 1) |
| @@ -533,12 +592,12 @@ static void hkdSrvRandomSeed(int seed) { | |||
| 533 | 592 | ||
| 534 | static void install_seed_hook() { | 593 | static void install_seed_hook() { |
| 535 | static bool failed = false; | 594 | static bool failed = false; |
| 536 | if (g_seed_hook_installed || failed || !g_server.valid()) | 595 | if (g_seed_hook_installed || failed || !mod_server().valid()) |
| 537 | return; | 596 | return; |
| 538 | g_srv_base = g_server.ranges[0].lo; | 597 | g_srv_base = mod_server().ranges[0].lo; |
| 539 | uintptr_t reloc = 0, bias = 0; | 598 | uintptr_t reloc = 0, bias = 0; |
| 540 | uintptr_t slot = 0; | 599 | uintptr_t slot = 0; |
| 541 | if (module_jump_slot(g_server.path.c_str(), "RandomSeed", &reloc, &bias)) { | 600 | if (module_jump_slot(mod_server().path.c_str(), "RandomSeed", &reloc, &bias)) { |
| 542 | slot = g_srv_base + reloc - bias; | 601 | slot = g_srv_base + reloc - bias; |
| 543 | logf("seed hook: RandomSeed GOT reloc=%#lx bias=%#lx -> %#lx", | 602 | logf("seed hook: RandomSeed GOT reloc=%#lx bias=%#lx -> %#lx", |
| 544 | (unsigned long)reloc, (unsigned long)bias, (unsigned long)slot); | 603 | (unsigned long)reloc, (unsigned long)bias, (unsigned long)slot); |
| @@ -547,13 +606,13 @@ static void install_seed_hook() { | |||
| 547 | static const uintptr_t known[] = {0xce5348, 0xce1348}; | 606 | static const uintptr_t known[] = {0xce5348, 0xce1348}; |
| 548 | for (size_t i = 0; i < sizeof(known) / sizeof(known[0]) && !slot; i++) { | 607 | for (size_t i = 0; i < sizeof(known) / sizeof(known[0]) && !slot; i++) { |
| 549 | uintptr_t cand = g_srv_base + known[i]; | 608 | uintptr_t cand = g_srv_base + known[i]; |
| 550 | if (g_server.contains(cand)) | 609 | if (mod_server().contains(cand)) |
| 551 | slot = cand; | 610 | slot = cand; |
| 552 | } | 611 | } |
| 553 | logf("seed hook: ELF reloc scan failed, fallback slot=%#lx", | 612 | logf("seed hook: ELF reloc scan failed, fallback slot=%#lx", |
| 554 | (unsigned long)slot); | 613 | (unsigned long)slot); |
| 555 | } | 614 | } |
| 556 | if (!slot || !g_server.contains(slot)) { | 615 | if (!slot || !mod_server().contains(slot)) { |
| 557 | failed = true; | 616 | failed = true; |
| 558 | logf("seed hook: no writable RandomSeed slot"); | 617 | logf("seed hook: no writable RandomSeed slot"); |
| 559 | return; | 618 | return; |
| @@ -568,8 +627,15 @@ static void install_seed_hook() { | |||
| 568 | toastf(TOAST_WARN, "Spread seed hook unavailable"); | 627 | toastf(TOAST_WARN, "Spread seed hook unavailable"); |
| 569 | return; | 628 | return; |
| 570 | } | 629 | } |
| 571 | mprotect((void *)(slot & ~(uintptr_t)0xFFF), 0x1000, | 630 | // The slot is 4-byte aligned, so a single page always covers the 4-byte |
| 572 | PROT_READ | PROT_WRITE | PROT_EXEC); | 631 | // write. No PROT_EXEC: this is a data slot. Permissions are intentionally |
| 632 | // not restored because .got.plt stays writable for lazy binding. | ||
| 633 | uintptr_t page = slot & ~(uintptr_t)0xFFF; | ||
| 634 | if (mprotect((void *)page, 0x1000, PROT_READ | PROT_WRITE) != 0) { | ||
| 635 | failed = true; | ||
| 636 | logf("seed hook: mprotect failed for page %#lx", (unsigned long)page); | ||
| 637 | return; | ||
| 638 | } | ||
| 573 | *(uint32_t *)slot = (uint32_t)(uintptr_t)&hkdSrvRandomSeed; | 639 | *(uint32_t *)slot = (uint32_t)(uintptr_t)&hkdSrvRandomSeed; |
| 574 | g_seed_hook_installed = true; | 640 | g_seed_hook_installed = true; |
| 575 | logf("seed hook: server GOT %#lx -> %p (real %p)", | 641 | logf("seed hook: server GOT %#lx -> %p (real %p)", |
| @@ -598,19 +664,16 @@ static bool g_uniform_hook_installed = false; | |||
| 598 | // (the compiler may outline it), so this window is the reliable signal. | 664 | // (the compiler may outline it), so this window is the reliable signal. |
| 599 | 665 | ||
| 600 | static float hkdUniformRandomFloat(void *self, float mn, float mx) { | 666 | static float hkdUniformRandomFloat(void *self, float mn, float mx) { |
| 667 | if (!init_live()) | ||
| 668 | return g_orig_uniform_float ? g_orig_uniform_float(self, mn, mx) : mn; | ||
| 601 | bool enabled = g_local_server_player && feature_active(FEAT_SPREAD) && | 669 | bool enabled = g_local_server_player && feature_active(FEAT_SPREAD) && |
| 602 | spread_no_spread_enabled() && spread_engine_enabled(); | 670 | spread_no_spread_enabled() && spread_engine_enabled(); |
| 603 | bool hit = false; | 671 | bool hit = false; |
| 604 | void *who = nullptr; | 672 | void *who = nullptr; |
| 605 | if (enabled) { | 673 | if (enabled) { |
| 606 | void *frame = nullptr; | 674 | who = caller_self_arg(); |
| 607 | __asm__ volatile("movl %%ebp, %0" : "=r"(frame)); | 675 | if (who && (uintptr_t)who == g_local_server_player) |
| 608 | if (frame) { | 676 | hit = true; |
| 609 | frame = *(void **)frame; // caller's ebp | ||
| 610 | who = frame ? *(void **)((char *)frame + 8) : nullptr; | ||
| 611 | if (who && (uintptr_t)who == g_local_server_player) | ||
| 612 | hit = true; | ||
| 613 | } | ||
| 614 | if (!hit && g_spread_rand_left > 0) { | 677 | if (!hit && g_spread_rand_left > 0) { |
| 615 | // The next few draws after the player's FireBullets RandomSeed are the | 678 | // The next few draws after the player's FireBullets RandomSeed are the |
| 616 | // pellet spread, regardless of which function emits them. | 679 | // pellet spread, regardless of which function emits them. |
| @@ -748,9 +811,26 @@ static bool edge_jump_apply(CUserCmd *cmd, void *ent, int flags, | |||
| 748 | return true; | 811 | return true; |
| 749 | } | 812 | } |
| 750 | 813 | ||
| 814 | // Everything tied to one map load / local player lifetime. Called when the | ||
| 815 | // local entity pointer changes (map load, respawn, disconnect). | ||
| 816 | static void session_reset() { | ||
| 817 | bones_clear(); | ||
| 818 | aimbot_reset_lock(); | ||
| 819 | silentaim_reset_lock(); | ||
| 820 | esp_reset_session(); | ||
| 821 | fullbright_invalidate(); | ||
| 822 | world_tint_invalidate(); | ||
| 823 | fog_invalidate(); | ||
| 824 | skybox_invalidate(); | ||
| 825 | logd("session: reset (local entity changed)"); | ||
| 826 | } | ||
| 827 | |||
| 751 | static void do_move(int seq, float frametime) { | 828 | static void do_move(int seq, float frametime) { |
| 752 | install_seed_hook(); | 829 | install_seed_hook(); |
| 753 | install_uniform_random_hook(); | 830 | install_uniform_random_hook(); |
| 831 | // The pellet window is armed inside FireBullets during this tick; it must | ||
| 832 | // never survive into the next one. | ||
| 833 | g_spread_rand_left = 0; | ||
| 754 | static bool g_echo_sent = false; | 834 | static bool g_echo_sent = false; |
| 755 | if (!g_input || !g_net.ok()) | 835 | if (!g_input || !g_net.ok()) |
| 756 | return; | 836 | return; |
| @@ -779,17 +859,13 @@ static void do_move(int seq, float frametime) { | |||
| 779 | 859 | ||
| 780 | view_settings_apply(ent); | 860 | view_settings_apply(ent); |
| 781 | 861 | ||
| 782 | // New map / respawn: drop stale bone caches and target locks. | 862 | // New map / respawn: drop stale caches and locks. Any per-map state must be |
| 863 | // cleared here; caches that are not listed will leak across level changes | ||
| 864 | // (that is how the server-health latch and the fuse table broke before). | ||
| 783 | static void *last_local_ent = nullptr; | 865 | static void *last_local_ent = nullptr; |
| 784 | if (ent != last_local_ent) { | 866 | if (ent != last_local_ent) { |
| 785 | last_local_ent = ent; | 867 | last_local_ent = ent; |
| 786 | bones_clear(); | 868 | session_reset(); |
| 787 | aimbot_reset_lock(); | ||
| 788 | silentaim_reset_lock(); | ||
| 789 | fullbright_invalidate(); | ||
| 790 | world_tint_invalidate(); | ||
| 791 | fog_invalidate(); | ||
| 792 | skybox_invalidate(); | ||
| 793 | } | 869 | } |
| 794 | 870 | ||
| 795 | if (feature_active(FEAT_SPREAD)) { | 871 | if (feature_active(FEAT_SPREAD)) { |
| @@ -854,8 +930,9 @@ static void do_move(int seq, float frametime) { | |||
| 854 | 930 | ||
| 855 | if ((bhop_on || abh_on) && (cmd->buttons & IN_JUMP)) { | 931 | if ((bhop_on || abh_on) && (cmd->buttons & IN_JUMP)) { |
| 856 | if (inwater && (cmd->buttons & IN_DUCK)) { | 932 | if (inwater && (cmd->buttons & IN_DUCK)) { |
| 933 | // Not a bhop tick, but combat and jump assist still have to run. | ||
| 857 | lastground = onground; | 934 | lastground = onground; |
| 858 | return; | 935 | goto bhop_done; |
| 859 | } | 936 | } |
| 860 | float speed = sqrtf(vel[0] * vel[0] + vel[1] * vel[1] + vel[2] * vel[2]); | 937 | float speed = sqrtf(vel[0] * vel[0] + vel[1] * vel[1] + vel[2] * vel[2]); |
| 861 | bool moving = speed > 0.1f; | 938 | bool moving = speed > 0.1f; |
| @@ -899,6 +976,7 @@ static void do_move(int seq, float frametime) { | |||
| 899 | } | 976 | } |
| 900 | } | 977 | } |
| 901 | 978 | ||
| 979 | bhop_done: | ||
| 902 | bool jump_assist = crouch_jump_apply(cmd, flags, vel); | 980 | bool jump_assist = crouch_jump_apply(cmd, flags, vel); |
| 903 | if (edge_jump_apply(cmd, ent, flags, vel)) | 981 | if (edge_jump_apply(cmd, ent, flags, vel)) |
| 904 | jump_assist = true; | 982 | jump_assist = true; |
| @@ -975,7 +1053,12 @@ void set_silent_restore(const QAngle &angles) { | |||
| 975 | static void hkdFrameStageNotify(void *self, int stage) { | 1053 | static void hkdFrameStageNotify(void *self, int stage) { |
| 976 | if (oFrameStageNotify) | 1054 | if (oFrameStageNotify) |
| 977 | oFrameStageNotify(self, stage); | 1055 | oFrameStageNotify(self, stage); |
| 1056 | if (!init_live()) | ||
| 1057 | return; | ||
| 978 | if (stage == 5) { | 1058 | if (stage == 5) { |
| 1059 | // FRAME_RENDER_START is the real frame boundary: it lets the paint hooks | ||
| 1060 | // below draw exactly once per frame instead of once per VGUI panel. | ||
| 1061 | overlay_frame_mark(); | ||
| 979 | // Skybox texture uploads happen here, not in the VGUI paint hook: the | 1062 | // Skybox texture uploads happen here, not in the VGUI paint hook: the |
| 980 | // material system only downloads procedural textures on a render-safe | 1063 | // material system only downloads procedural textures on a render-safe |
| 981 | // frame stage. | 1064 | // frame stage. |
| @@ -1006,14 +1089,15 @@ static bool g_in_draw = false; | |||
| 1006 | // our overlay draws are submitted through outer_draw() (menu.cpp), which | 1089 | // our overlay draws are submitted through outer_draw() (menu.cpp), which |
| 1007 | // wraps them in PushMakeCurrent/PopMakeCurrent with the embedded panel. | 1090 | // wraps them in PushMakeCurrent/PopMakeCurrent with the embedded panel. |
| 1008 | static void hkdDrawPrintText(void *self, const wchar_t *text, int len, int drawType) { | 1091 | static void hkdDrawPrintText(void *self, const wchar_t *text, int len, int drawType) { |
| 1009 | if (g_in_draw) { | 1092 | if (g_in_draw || !init_live()) { |
| 1010 | oDrawPrintText(self, text, len, drawType); | 1093 | oDrawPrintText(self, text, len, drawType); |
| 1011 | return; | 1094 | return; |
| 1012 | } | 1095 | } |
| 1013 | g_in_draw = true; | 1096 | g_in_draw = true; |
| 1014 | oDrawPrintText(self, text, len, drawType); | 1097 | oDrawPrintText(self, text, len, drawType); |
| 1015 | // fallback trigger: fires when the game draws text (menus). Primary draw | 1098 | // Fallback trigger for frames where nothing paints (menus). The per-frame |
| 1016 | // path is the PaintTraverseEx hook (slot 114), which fires every frame. | 1099 | // budget inside outer_draw() makes this a no-op if PaintTraverseEx already |
| 1100 | // drew this frame. | ||
| 1017 | outer_draw(); | 1101 | outer_draw(); |
| 1018 | g_in_draw = false; | 1102 | g_in_draw = false; |
| 1019 | } | 1103 | } |
| @@ -1024,7 +1108,7 @@ static void hkdDrawPrintText(void *self, const wchar_t *text, int len, int drawT | |||
| 1024 | static void hkdPaintTraverseEx(void *self, long panel, bool paintPopups) { | 1108 | static void hkdPaintTraverseEx(void *self, long panel, bool paintPopups) { |
| 1025 | using Fn = void (*)(void *, long, bool); | 1109 | using Fn = void (*)(void *, long, bool); |
| 1026 | ((Fn)oPaintTraverseEx)(self, panel, paintPopups); | 1110 | ((Fn)oPaintTraverseEx)(self, panel, paintPopups); |
| 1027 | if (!g_in_draw) { | 1111 | if (!g_in_draw && init_live()) { |
| 1028 | g_in_draw = true; | 1112 | g_in_draw = true; |
| 1029 | outer_draw(); | 1113 | outer_draw(); |
| 1030 | g_in_draw = false; | 1114 | g_in_draw = false; |
| @@ -1032,6 +1116,8 @@ static void hkdPaintTraverseEx(void *self, long panel, bool paintPopups) { | |||
| 1032 | } | 1116 | } |
| 1033 | 1117 | ||
| 1034 | static bool hkdCreateMove(void *self, int seq, float frametime, bool active) { | 1118 | static bool hkdCreateMove(void *self, int seq, float frametime, bool active) { |
| 1119 | if (!init_live()) | ||
| 1120 | return oCreateMove(self, seq, frametime, active); | ||
| 1035 | bool ret = oCreateMove(self, seq, frametime, active); | 1121 | bool ret = oCreateMove(self, seq, frametime, active); |
| 1036 | do_move(seq, frametime); | 1122 | do_move(seq, frametime); |
| 1037 | 1123 | ||
| @@ -1041,8 +1127,12 @@ static bool hkdCreateMove(void *self, int seq, float frametime, bool active) { | |||
| 1041 | if (!isurface_hooked && g_surface) { | 1127 | if (!isurface_hooked && g_surface) { |
| 1042 | void **svt = VTABLE(g_surface); | 1128 | void **svt = VTABLE(g_surface); |
| 1043 | constexpr size_t VT_SIZE = 256; | 1129 | constexpr size_t VT_SIZE = 256; |
| 1044 | void **newsvt = new void *[VT_SIZE]; | 1130 | void **newsvt = vtable_clone(g_surface, VT_SIZE); |
| 1045 | memcpy(newsvt, svt, VT_SIZE * sizeof(void *)); | 1131 | if (!newsvt) { |
| 1132 | isurface_hooked = 1; // don't retry every tick | ||
| 1133 | logf("ISurface hook: vtable unavailable"); | ||
| 1134 | return ret; | ||
| 1135 | } | ||
| 1046 | oDrawPrintText = (DrawPrintTextFn)svt[22]; | 1136 | oDrawPrintText = (DrawPrintTextFn)svt[22]; |
| 1047 | newsvt[22] = (void *)&hkdDrawPrintText; | 1137 | newsvt[22] = (void *)&hkdDrawPrintText; |
| 1048 | oPaintTraverseEx = (PaintTraverseExFn)svt[114]; | 1138 | oPaintTraverseEx = (PaintTraverseExFn)svt[114]; |
| @@ -1059,8 +1149,11 @@ static bool hkdCreateMove(void *self, int seq, float frametime, bool active) { | |||
| 1059 | static void install_hook() { | 1149 | static void install_hook() { |
| 1060 | void **vt = VTABLE(g_iface.client); | 1150 | void **vt = VTABLE(g_iface.client); |
| 1061 | constexpr size_t SLOTS = 256; | 1151 | constexpr size_t SLOTS = 256; |
| 1062 | void **newvt = new void *[SLOTS]; | 1152 | void **newvt = vtable_clone(g_iface.client, SLOTS); |
| 1063 | memcpy(newvt, vt, SLOTS * sizeof(void *)); | 1153 | if (!newvt) { |
| 1154 | logf("hook install: client vtable unavailable"); | ||
| 1155 | return; | ||
| 1156 | } | ||
| 1064 | oCreateMove = (CreateMoveFn)vt[21]; | 1157 | oCreateMove = (CreateMoveFn)vt[21]; |
| 1065 | newvt[21] = (void *)&hkdCreateMove; | 1158 | newvt[21] = (void *)&hkdCreateMove; |
| 1066 | oFrameStageNotify = (FrameStageFn)vt[35]; | 1159 | oFrameStageNotify = (FrameStageFn)vt[35]; |
| @@ -1097,26 +1190,28 @@ static bool is_hl2_process() { | |||
| 1097 | static void *setup_thread(void *) { | 1190 | static void *setup_thread(void *) { |
| 1098 | for (int i = 0; i < 6000; i++) { | 1191 | for (int i = 0; i < 6000; i++) { |
| 1099 | find_modules(); | 1192 | find_modules(); |
| 1100 | if (g_client.valid() && g_engine.valid()) | 1193 | if (mod_client().valid() && mod_engine().valid()) |
| 1101 | break; | 1194 | break; |
| 1102 | usleep(200 * 1000); | 1195 | usleep(200 * 1000); |
| 1103 | } | 1196 | } |
| 1104 | if (!g_client.valid() || !g_engine.valid()) { | 1197 | if (!mod_client().valid() || !mod_engine().valid()) { |
| 1198 | init_set_stage(INIT_FAILED); | ||
| 1105 | logf("client.so/engine.so never appeared"); | 1199 | logf("client.so/engine.so never appeared"); |
| 1106 | toastf(TOAST_ERROR, "client.so/engine.so not found; hl2hake inactive"); | 1200 | toastf(TOAST_ERROR, "client.so/engine.so not found; hl2hake inactive"); |
| 1107 | return nullptr; | 1201 | return nullptr; |
| 1108 | } | 1202 | } |
| 1109 | logf("client.so: %d mapped range(s), %s", (int)g_client.ranges.size(), | 1203 | logf("client.so: %d mapped range(s), %s", (int)mod_client().ranges.size(), |
| 1110 | g_client.path.c_str()); | 1204 | mod_client().path.c_str()); |
| 1111 | logf("engine.so: %d mapped range(s), %s", (int)g_engine.ranges.size(), | 1205 | logf("engine.so: %d mapped range(s), %s", (int)mod_engine().ranges.size(), |
| 1112 | g_engine.path.c_str()); | 1206 | mod_engine().path.c_str()); |
| 1113 | 1207 | ||
| 1114 | // resolve factories via dlopen handles | 1208 | // resolve factories via dlopen handles |
| 1115 | void *ch = dlopen(g_client.path.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); | 1209 | void *ch = dlopen(mod_client().path.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); |
| 1116 | void *eh = dlopen(g_engine.path.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); | 1210 | void *eh = dlopen(mod_engine().path.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); |
| 1117 | auto cf = (CreateInterfaceFn)dlsym(ch, "CreateInterface"); | 1211 | auto cf = (CreateInterfaceFn)dlsym(ch, "CreateInterface"); |
| 1118 | auto ef = (CreateInterfaceFn)dlsym(eh, "CreateInterface"); | 1212 | auto ef = (CreateInterfaceFn)dlsym(eh, "CreateInterface"); |
| 1119 | if (!cf || !ef) { | 1213 | if (!cf || !ef) { |
| 1214 | init_set_stage(INIT_FAILED); | ||
| 1120 | logf("CreateInterface not found"); | 1215 | logf("CreateInterface not found"); |
| 1121 | toastf(TOAST_ERROR, "CreateInterface not found; hl2hake inactive"); | 1216 | toastf(TOAST_ERROR, "CreateInterface not found; hl2hake inactive"); |
| 1122 | return nullptr; | 1217 | return nullptr; |
| @@ -1134,6 +1229,7 @@ static void *setup_thread(void *) { | |||
| 1134 | usleep(100 * 1000); | 1229 | usleep(100 * 1000); |
| 1135 | } | 1230 | } |
| 1136 | if (!g_iface.client || !g_iface.entitylist || !g_iface.engine) { | 1231 | if (!g_iface.client || !g_iface.entitylist || !g_iface.engine) { |
| 1232 | init_set_stage(INIT_FAILED); | ||
| 1137 | logf("interfaces unresolved: VClient017=%p VClientEntityList003=%p " | 1233 | logf("interfaces unresolved: VClient017=%p VClientEntityList003=%p " |
| 1138 | "VEngineClient014=%p", | 1234 | "VEngineClient014=%p", |
| 1139 | g_iface.client, g_iface.entitylist, g_iface.engine); | 1235 | g_iface.client, g_iface.entitylist, g_iface.engine); |
| @@ -1159,10 +1255,10 @@ static void *setup_thread(void *) { | |||
| 1159 | g_iface.cvar = cf(cvar_names[i], nullptr); | 1255 | g_iface.cvar = cf(cvar_names[i], nullptr); |
| 1160 | } | 1256 | } |
| 1161 | if (!g_iface.cvar) { | 1257 | if (!g_iface.cvar) { |
| 1162 | size_t slash = g_engine.path.find_last_of('/'); | 1258 | size_t slash = mod_engine().path.find_last_of('/'); |
| 1163 | if (slash != std::string::npos) { | 1259 | if (slash != std::string::npos) { |
| 1164 | std::string path = | 1260 | std::string path = |
| 1165 | g_engine.path.substr(0, slash + 1) + "libvstdlib.so"; | 1261 | mod_engine().path.substr(0, slash + 1) + "libvstdlib.so"; |
| 1166 | void *h = dlopen(path.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); | 1262 | void *h = dlopen(path.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); |
| 1167 | if (!h) | 1263 | if (!h) |
| 1168 | h = dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL); | 1264 | h = dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL); |
| @@ -1195,15 +1291,23 @@ static void *setup_thread(void *) { | |||
| 1195 | toastf(TOAST_WARN, "Debug overlay missing; ESP projection uses fallback"); | 1291 | toastf(TOAST_WARN, "Debug overlay missing; ESP projection uses fallback"); |
| 1196 | if (!g_iface.enginetrace) | 1292 | if (!g_iface.enginetrace) |
| 1197 | toastf(TOAST_WARN, "Engine trace missing; vischeck fails open"); | 1293 | toastf(TOAST_WARN, "Engine trace missing; vischeck fails open"); |
| 1294 | init_set_stage(INIT_INTERFACES); | ||
| 1295 | |||
| 1296 | init_conmsg(); | ||
| 1297 | // Config load and ISurface/font discovery must finish before any hook is | ||
| 1298 | // allowed past the init_live() gate: the game thread keeps calling | ||
| 1299 | // CreateMove/PaintTraverse while this thread runs. | ||
| 1300 | install_menu(); | ||
| 1301 | init_set_stage(INIT_READY); | ||
| 1198 | 1302 | ||
| 1199 | if (!find_input_global()) { | 1303 | if (!find_input_global()) { |
| 1304 | init_set_stage(INIT_FAILED); | ||
| 1200 | toastf(TOAST_ERROR, "Input hook failed; movement features disabled"); | 1305 | toastf(TOAST_ERROR, "Input hook failed; movement features disabled"); |
| 1201 | return nullptr; | 1306 | return nullptr; |
| 1202 | } | 1307 | } |
| 1203 | |||
| 1204 | init_conmsg(); | ||
| 1205 | install_hook(); | 1308 | install_hook(); |
| 1206 | install_menu(); | 1309 | init_set_stage(INIT_LIVE); |
| 1310 | logf("init: live (surface %p)", (void *)g_surface); | ||
| 1207 | if (g_surface) | 1311 | if (g_surface) |
| 1208 | toastf(TOAST_INFO, "hl2hake loaded - Insert opens the menu"); | 1312 | toastf(TOAST_INFO, "hl2hake loaded - Insert opens the menu"); |
| 1209 | return nullptr; | 1313 | return nullptr; |
