// cinput.cpp — CInput discovery, usercmd CRC, get/fix helpers #include "hl2h.h" uintptr_t g_input = 0; // --------------------------------------------------------------------------- // CRC-32 (standard IEEE reflected, matches Valve's implementation) // --------------------------------------------------------------------------- static uint32_t g_crc_table[256]; static bool g_crc_inited = false; static void crc32_init_table() { if (g_crc_inited) return; for (uint32_t i = 0; i < 256; i++) { uint32_t c = i; for (int k = 0; k < 8; k++) c = (c & 1) ? 0xEDB88320u ^ (c >> 1) : c >> 1; g_crc_table[i] = c; } g_crc_inited = true; } // field set verified from client.so GetChecksum disassembly uint32_t usercmd_checksum(const CUserCmd *c) { crc32_init_table(); uint32_t crc = 0xFFFFFFFFu; auto upd = [&crc](const void *p, size_t n) { const uint8_t *b = (const uint8_t *)p; while (n--) crc = g_crc_table[(crc ^ *b++) & 0xFF] ^ (crc >> 8); }; upd(&c->command_number, 4); upd(&c->tick_count, 4); upd(&c->viewangles, 12); upd(&c->forwardmove, 4); upd(&c->sidemove, 4); upd(&c->upmove, 4); upd(&c->buttons, 4); upd(&c->impulse, 1); upd(&c->weaponselect, 4); upd(&c->weaponsubtype, 4); upd(&c->random_seed, 4); upd(&c->mousedx, 2); upd(&c->mousedy, 2); return ~crc; } // CInput: +0xE4 = m_pCommands, +0xE8 = m_pVerifiedCommands static constexpr size_t CMDOFF_M_PCOMMANDS = 0xE4; static constexpr size_t CMDOFF_M_PVERIFIED = 0xE8; static constexpr size_t VERIFIED_STRIDE = 0x58; static constexpr size_t VERIFIED_CRC_OFF = 0x54; CUserCmd *get_usercmd(int seq) { // Negative sequence numbers would make `seq % 90` negative and the size_t // cast a huge index; reject before forming the pointer. if (!g_input || seq < 0) return nullptr; uintptr_t cmds = *(uintptr_t *)(g_input + CMDOFF_M_PCOMMANDS); if (!cmds) return nullptr; auto *cmd = (CUserCmd *)(cmds + (size_t)((unsigned)seq % 90u) * 0x54); if (cmd->command_number != seq) return nullptr; return cmd; } void fix_verified_crc(int seq, uint32_t crc) { if (!g_input || seq < 0) return; uintptr_t verified = *(uintptr_t *)(g_input + CMDOFF_M_PVERIFIED); if (verified) *(uint32_t *)(verified + (size_t)((unsigned)seq % 90u) * VERIFIED_STRIDE + 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; using InputExtraMouseFn = void (*)(void *, float, bool); static InputExtraMouseFn oInputExtraMouseSample = nullptr; // ExtraMouseSample runs AdjustAngles at render-frame rate, between movement // commands. While the GUI is open it must not touch the view; movement still // comes through CreateMove. static void hkdInputExtraMouseSample(void *self, float frametime, bool active) { if (!init_live()) { if (oInputExtraMouseSample) oInputExtraMouseSample(self, frametime, active); return; } if (gui_is_open()) return; if (oInputExtraMouseSample) oInputExtraMouseSample(self, frametime, active); } void input_clear_mouse_states() { if (!g_input) return; // CInput::ClearStates (IInput slot 20): zeros both the software mouse // accumulator and the engine raw-input accumulator. using Fn = void (*)(void *); vfunc((void *)g_input, 20)((void *)g_input); } static void hkdInputCreateMove(void *self, int seq, float frametime, bool active) { if (!init_live()) { if (oInputCreateMove) oInputCreateMove(self, seq, frametime, active); return; } // Mouse deltas are accumulated between ticks. With the custom GUI open we // must discard them before CInput::CreateMove applies mouse look, otherwise // the view moves for one frame and snaps back when do_move freezes it. if (gui_is_open()) input_clear_mouse_states(); if (oInputCreateMove) oInputCreateMove(self, seq, frametime, active); // No combat features may modify the command while the GUI is open. if (gui_is_open()) return; 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 = vtable_clone((void *)g_input, N); if (!nvt) { logf("CInput vtable clone failed"); return false; } oInputCreateMove = (InputCreateMoveFn)vt[3]; nvt[3] = (void *)&hkdInputCreateMove; if (N > 4) { oInputExtraMouseSample = (InputExtraMouseFn)vt[4]; nvt[4] = (void *)&hkdInputExtraMouseSample; } VTABLE((void *)g_input) = nvt; logf("CInput CreateMove hooked: slot 3 orig=%p; ExtraMouseSample slot 4 " "orig=%p", (void *)oInputCreateMove, (void *)oInputExtraMouseSample); return true; } // --------------------------------------------------------------------------- // CInput discovery: byte-scan of CHLClient::CreateMove prologue // --------------------------------------------------------------------------- bool find_input_global() { void **vt = VTABLE(g_iface.client); uintptr_t cm = (uintptr_t)vt[21]; // CreateMove slot if (!mod_client().contains(cm)) { logf("CreateMove %p outside client module", (void *)cm); return false; } const uint8_t *b = (const uint8_t *)cm; // 1) PIC anchor: call __x86.get_pc_thunk + add $imm,%esi uintptr_t anchor = 0; for (int i = 0; i <= 0x100 - 11; i++) { if (b[i] == 0xE8 && b[i + 5] == 0x81 && b[i + 6] == 0xC6) { uint32_t imm; memcpy(&imm, b + i + 7, 4); anchor = cm + (uintptr_t)(i + 5) + imm; break; } } if (!anchor || !mod_client().contains(anchor)) { logf("PIC anchor not found in CreateMove"); return false; } // 2) the input->CreateMove call: "ff 51 0c" preceded by "52", scan back // for "8d 86 imm32" with "8b 10" between for (int c = 3; c <= 0x100 - 3; c++) { if (!(b[c] == 0xFF && b[c + 1] == 0x51 && b[c + 2] == 0x0C && b[c - 1] == 0x52)) continue; for (int i = c - 8; i > c - 44 && i > 2; i--) { if (b[i] != 0x8D || b[i + 1] != 0x86) continue; bool has_mov = false; for (int j = i + 6; j < c - 1; j++) if (b[j] == 0x8B && b[j + 1] == 0x10) { has_mov = true; break; } if (!has_mov) continue; uint32_t disp; memcpy(&disp, b + i + 2, 4); uintptr_t var = anchor + disp; if (!mod_client().contains(var)) continue; uintptr_t input = *(uintptr_t *)var; if (!input || !mod_client().contains(input)) continue; uintptr_t vtbl = *(uintptr_t *)input; if (!vtbl || !mod_client().contains(vtbl)) continue; uintptr_t f3 = *(uintptr_t *)(vtbl + 3 * sizeof(void *)); if (!mod_client().contains(f3)) continue; logd("CInput* = %#lx (ptr var %#lx, vfunc[3] %#lx)", (unsigned long)input, (unsigned long)var, (unsigned long)f3); g_input = input; if (!install_input_hook()) toastf(TOAST_ERROR, "Input vtable hook failed; features disabled"); return true; } } logf("CInput pointer variable not found in CreateMove scan"); return false; }