From 902507c1173a03a04fc49cfe421bc3c3fb49d8fb Mon Sep 17 00:00:00 2001 From: salmonik Date: Tue, 27 Oct 2026 15:32:26 +0100 Subject: Spread control: set random_seed after CInput::CreateMove CInput::CreateMove overwrites cmd->random_seed with MD5_PseudoRandom after CHLClient::CreateMove returns, so the seed set from the client-mode hook was being discarded. Hook the IInput vtable slot 3 (CInput::CreateMove), call the original, then apply the spread seed and update the verified command copy and CRC. This also keeps all aim/movement edits since the original copies the command into the verified table. --- src/cinput.cpp | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) (limited to 'src/cinput.cpp') diff --git a/src/cinput.cpp b/src/cinput.cpp index f4ba322..a75cc4f 100644 --- a/src/cinput.cpp +++ b/src/cinput.cpp @@ -73,6 +73,63 @@ void fix_verified_crc(int seq, uint32_t crc) { VERIFIED_CRC_OFF) = crc; } +// --------------------------------------------------------------------------- +// CInput::CreateMove hook +// --------------------------------------------------------------------------- +// +// The CHLClient::CreateMove hook runs *inside* CInput::CreateMove. After it +// returns, CInput::CreateMove overwrites cmd->random_seed with +// MD5_PseudoRandom(sequence_number), then copies the command into the verified +// command table. To choose the bullet-spread seed we must hook CInput itself +// and run after that assignment. + +using InputCreateMoveFn = void (*)(void *, int, float, bool); +static InputCreateMoveFn oInputCreateMove = nullptr; + +static void hkdInputCreateMove(void *self, int seq, float frametime, + bool active) { + if (oInputCreateMove) + oInputCreateMove(self, seq, frametime, active); + + CUserCmd *cmd = get_usercmd(seq); + if (!cmd) + return; + int local = engine_get_local_player(); + if (local <= 0) + return; + void *ent = entitylist_get(local); + if (!ent) + return; + if (!spread_control_apply(cmd, ent)) + return; + + // Keep the verified command copy and its CRC in sync with the modified + // random_seed. CInput::CreateMove already copied all other (aim/move) fields. + uintptr_t verified = *(uintptr_t *)(g_input + CMDOFF_M_PVERIFIED); + if (verified) { + CUserCmd *vc = + (CUserCmd *)(verified + (size_t)(seq % 90) * VERIFIED_STRIDE); + vc->random_seed = cmd->random_seed; + } + fix_verified_crc(seq, usercmd_checksum(cmd)); +} + +static bool install_input_hook() { + if (!g_input) + return false; + void **vt = VTABLE((void *)g_input); + if (!vt) + return false; + constexpr size_t N = 64; + void **nvt = new void *[N]; + memcpy(nvt, vt, N * sizeof(void *)); + oInputCreateMove = (InputCreateMoveFn)vt[3]; + nvt[3] = (void *)&hkdInputCreateMove; + VTABLE((void *)g_input) = nvt; + logf("CInput CreateMove hooked: slot 3 orig=%p", (void *)oInputCreateMove); + return true; +} + // --------------------------------------------------------------------------- // CInput discovery: byte-scan of CHLClient::CreateMove prologue // --------------------------------------------------------------------------- @@ -135,6 +192,7 @@ bool find_input_global() { logf("CInput* = %#lx (ptr var %#lx, vfunc[3] %#lx)", (unsigned long)input, (unsigned long)var, (unsigned long)f3); g_input = input; + install_input_hook(); return true; } } -- cgit v1.2.3