diff --git a/Cargo.lock b/Cargo.lock index 48bd0f6..bc69554 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,9 +8,67 @@ version = "0.1.0" dependencies = [ "ctor", "gl", + "glow", + "imgui", + "imgui-glow-renderer", + "imgui-sdl2-support", "libc", + "once_cell", + "sdl2-sys", ] +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chlorine" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00d31b1d19317b4777ec879192d3745bd97d05262b4b19cb1dda284b9d22f19" + [[package]] name = "ctor" version = "0.2.9" @@ -21,6 +79,36 @@ dependencies = [ "syn", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + [[package]] name = "gl" version = "0.14.0" @@ -41,24 +129,159 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "glow" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0fe580e4b60a8ab24a868bc08e2f03cbcb20d3d676601fa909386713333728" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "imgui" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122d677d0efcd64ca15f12907beaf46b26bbd2cdc855ee5b227f29cf50f75bb5" +dependencies = [ + "bitflags 1.3.2", + "cfg-if 1.0.4", + "imgui-sys", + "mint", + "parking_lot", +] + +[[package]] +name = "imgui-glow-renderer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15334602cc59b50210529885eb1db8571dc41bc08521be0de648cfb206bc1dff" +dependencies = [ + "glow", + "imgui", + "memoffset", +] + +[[package]] +name = "imgui-sdl2-support" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d038368d619b25dc3b1dae4ac894a5781b99247cedb2b9d5c93b9909f56cd135" +dependencies = [ + "imgui", + "sdl2", +] + +[[package]] +name = "imgui-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d785272a57cb8058a53a1e6f376f48e2ec4f40fbc6a9bb197dabf7b6b59c03bf" +dependencies = [ + "cc", + "cfg-if 1.0.4", + "chlorine", + "mint", +] + +[[package]] +name = "js-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +dependencies = [ + "cfg-if 1.0.4", + "futures-util", + "once_cell", + "wasm-bindgen", +] + [[package]] name = "khronos_api" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mint" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if 1.0.4", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "proc-macro2" version = "1.0.106" @@ -77,6 +300,77 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdl2" +version = "0.34.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deecbc3fa9460acff5a1e563e05cb5f31bba0aa0c214bb49a43db8159176d54b" +dependencies = [ + "bitflags 1.3.2", + "lazy_static", + "libc", + "sdl2-sys", +] + +[[package]] +name = "sdl2-sys" +version = "0.34.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a29aa21f175b5a41a6e26da572d5e5d1ee5660d35f9f9d0913e8a802098f74" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "version-compare", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "syn" version = "2.0.117" @@ -94,6 +388,79 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "version-compare" +version = "0.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasm-bindgen" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +dependencies = [ + "cfg-if 1.0.4", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "xml-rs" version = "0.8.28" diff --git a/Cargo.toml b/Cargo.toml index 4ef4750..160c5dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,9 @@ crate-type = ["cdylib"] libc = "0.2" ctor = "0.2" gl = "0.14" +glow = "0.12" +imgui = "0.11" +imgui-glow-renderer = "0.11" +imgui-sdl2-support = "0.11" +sdl2-sys = "0.34" +once_cell = "1.19" diff --git a/src/lib.rs b/src/lib.rs index bbacb88..73881ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,166 +1,1510 @@ mod sdk; use sdk::*; -use std::ffi::{CString, c_char, c_void}; +use std::ffi::{c_char, c_void}; +use std::ffi::CStr; use std::ptr; -use std::sync::atomic::{AtomicPtr, Ordering}; -use libc::{dlopen, dlsym, dlclose, RTLD_LAZY, RTLD_NOLOAD, mprotect, PROT_READ, PROT_WRITE}; +use std::sync::OnceLock; +use libc::{dlopen, dlsym, RTLD_LAZY, RTLD_NOLOAD, mprotect, PROT_READ, PROT_WRITE, PROT_EXEC}; use std::fs::OpenOptions; use std::io::Write; - -static ENGINE: AtomicPtr = AtomicPtr::new(ptr::null_mut()); -static CLIENT: AtomicPtr = AtomicPtr::new(ptr::null_mut()); -static PLAYER_MOVE: AtomicPtr = AtomicPtr::new(ptr::null_mut()); +use glow::HasContext; static mut ORIG_CLIENT: Option = None; -static mut FILL_RGBA_BOX: Option = None; +static mut HOOKS_APPLIED: bool = false; +static mut ORIG_CREATEMOVE: Option = None; +static mut ENGINE: *mut Engine = ptr::null_mut(); +static mut PMOVE: *mut PlayerMove = ptr::null_mut(); +static mut LOCALPLAYER: *mut Entity = ptr::null_mut(); +static mut WAS_ONGROUND: bool = false; + +static mut CLIENT_USERMSGS: *mut UserMsg = ptr::null_mut(); +static mut ORIG_TEAMINFO: Option i32> = None; +static mut ORIG_HEALTH: Option i32> = None; +static mut ORIG_SCOREATTRIB: Option i32> = None; +static mut ORIG_RADAR: Option i32> = None; +static mut ORIG_RESETHUD: Option i32> = None; + +static mut PLAYER_TEAM: [[u8; 16]; 33] = [[0; 16]; 33]; + +#[derive(Clone, Copy)] +struct PlayerData { + pos: Vector, + health: i32, + is_alive: bool, + last_update: f32, +} + +static mut PLAYER_DATA: [PlayerData; 33] = [PlayerData { + pos: Vector { x: 0.0, y: 0.0, z: 0.0 }, + health: 0, + is_alive: false, + last_update: 0.0, +}; 33]; + +// Parsemsg globals +static mut PARSEMSG_FLAGS: i32 = 0; +static mut PARSEMSG_SIZE: i32 = 0; +static mut PARSEMSG_MAXSIZE: i32 = 0; +static mut PARSEMSG_READSIZE: i32 = 0; +static mut PARSEMSG_READCOUNT: i32 = 0; +static mut PARSEMSG_DATA: *mut u8 = ptr::null_mut(); struct Settings { show_menu: bool, - bhop: bool, - chams: bool, + last_rshift_down: bool, + mouse_grabbed: bool, + have_angles: bool, + last_angles: Vector, + bhop_enabled: bool, + faststop_enabled: bool, + autostrafe_enabled: bool, + esp_enabled: bool, + wh_enabled: bool, } static mut SETTINGS: Settings = Settings { show_menu: false, - bhop: true, - chams: true, + last_rshift_down: false, + mouse_grabbed: false, + have_angles: false, + last_angles: Vector { x: 0.0, y: 0.0, z: 0.0 }, + bhop_enabled: false, + faststop_enabled: false, + autostrafe_enabled: false, + esp_enabled: true, + wh_enabled: true, }; +static mut VIEW_ORIGIN: Vector = Vector { x: 0.0, y: 0.0, z: 0.0 }; +static GET_PLAYER_INFO: OnceLock> = OnceLock::new(); + +type GetPlayerInfoFn = unsafe extern "C" fn(i32, *mut PlayerInfo); + +fn parsemsg_begin(buf: *mut c_void, size_arg: i32, flags_arg: i32) { + unsafe { + PARSEMSG_FLAGS = flags_arg; + PARSEMSG_SIZE = size_arg; + PARSEMSG_MAXSIZE = 0; + PARSEMSG_READSIZE = 0; + PARSEMSG_READCOUNT = 0; + PARSEMSG_DATA = buf as *mut u8; + } +} + +fn parsemsg_canread() -> bool { + unsafe { + if PARSEMSG_MAXSIZE == 0 { + return PARSEMSG_READCOUNT + PARSEMSG_READSIZE <= PARSEMSG_SIZE; + } + return PARSEMSG_READCOUNT + PARSEMSG_READSIZE <= PARSEMSG_SIZE && PARSEMSG_SIZE <= PARSEMSG_MAXSIZE; + } +} + +fn parsemsg_readbyte() -> u8 { + unsafe { + PARSEMSG_READSIZE = 1; + if !parsemsg_canread() { + return 255; // PARSEMSG_INVALID + } + let byte = *PARSEMSG_DATA.offset(PARSEMSG_READCOUNT as isize); + PARSEMSG_READCOUNT += PARSEMSG_READSIZE; + byte + } +} + +fn parsemsg_readshort() -> i16 { + unsafe { + PARSEMSG_READSIZE = 2; + if !parsemsg_canread() { + return -1; + } + let b1 = *PARSEMSG_DATA.offset(PARSEMSG_READCOUNT as isize) as i16; + let b2 = *PARSEMSG_DATA.offset(PARSEMSG_READCOUNT as isize + 1) as i16; + PARSEMSG_READCOUNT += PARSEMSG_READSIZE; + (b1 & 0xFF) | (b2 << 8) + } +} + +fn parsemsg_readcoord() -> f32 { + parsemsg_readshort() as f32 * (1.0 / 8.0) +} + +fn parsemsg_readstr() -> String { + unsafe { + PARSEMSG_READSIZE = 1; + if !parsemsg_canread() { + return String::new(); + } + let len = if (PARSEMSG_FLAGS & 1) != 0 { + 2048 + } else if (PARSEMSG_FLAGS & 2) != 0 { + 8192 + } else { + 2048 + }; + let mut buf = Vec::with_capacity(len as usize); + for _ in 0..(len - 1) { + let byte = parsemsg_readbyte(); + if byte == 0 || byte == 255 { + break; + } + buf.push(byte); + } + String::from_utf8_lossy(&buf).into_owned() + } +} + +fn set_player_team(idx: usize, team: &str) { + unsafe { + if idx >= PLAYER_TEAM.len() { + return; + } + log(&format!("SET TEAM: idx={}, team={}", idx, team)); + let bytes = team.as_bytes(); + let max = PLAYER_TEAM[idx].len() - 1; + let len = bytes.len().min(max); + let mut entry = [0u8; 16]; + entry[..len].copy_from_slice(&bytes[..len]); + PLAYER_TEAM[idx] = entry; + } +} + +fn player_team(idx: usize) -> &'static str { + unsafe { + if idx >= PLAYER_TEAM.len() { + return ""; + } + CStr::from_ptr(PLAYER_TEAM[idx].as_ptr() as *const c_char) + .to_str() + .unwrap_or("") + } +} + +fn normalize_team_key(team: &str) -> String { + team.chars() + .filter(|c| c.is_ascii_alphanumeric()) + .flat_map(|c| c.to_uppercase()) + .collect() +} + +fn is_ct_team(team: &str) -> bool { + let key = normalize_team_key(team); + matches!( + key.as_str(), + "CT" | "COUNTERTERRORIST" | "COUNTERTERRORISTS" | "COUNTER" | "BLUE" | "2" + ) +} + +fn is_t_team(team: &str) -> bool { + let key = normalize_team_key(team); + matches!( + key.as_str(), + "T" | "TT" | "TERRORIST" | "TERRORISTS" | "TERROR" | "RED" | "1" + ) +} + +fn team_color(team: &str) -> imgui::ImColor32 { + if is_ct_team(team) { + imgui::ImColor32::from_rgba(40, 40, 255, 220) + } else if is_t_team(team) { + imgui::ImColor32::from_rgba(255, 40, 40, 220) + } else { + if !team.is_empty() && team != "SPECTATOR" && team != "UNASSIGNED" { + log(&format!("UNKNOWN TEAM: {}", team)); + } + imgui::ImColor32::from_rgba(255, 255, 255, 220) + } +} + +fn get_entity_world_origin(ent: *mut Entity) -> Vector { + unsafe { + let idx = (*ent).i as usize; + let origin = (*ent).origin; + + // В GoldSrc если игрок вне PVS, его origin часто 0 или перестает обновляться. + // Мы проверяем msgnum (номер сообщения), чтобы понять, приходят ли свежие данные. + let in_pvs = (origin.x != 0.0 || origin.y != 0.0 || origin.z != 0.0) && (*ent).curstate.msgnum > 0; + + if idx < PLAYER_DATA.len() { + let pdata = &PLAYER_DATA[idx]; + // Если у нас есть данные из Radar/Sound, а игрок вне зоны видимости (PVS) + if pdata.pos.x != 0.0 && !in_pvs { + return pdata.pos; + } + } + + if !in_pvs { + (*ent).curstate.origin + } else { + origin + } + } +} + +fn get_entity_health(ent: *mut Entity) -> Option { + unsafe { + if ent.is_null() { + return None; + } + let idx = (*ent).i as usize; + if idx < PLAYER_DATA.len() && PLAYER_DATA[idx].health > 0 { + return Some(PLAYER_DATA[idx].health); + } + + // Fallback to brute-force if no message data (some servers don't send it) + let base = &(*ent).curstate as *const EntityState as *const u8; + let max_offset = std::mem::size_of::().saturating_sub(4); + for offset in (40..=max_offset).step_by(4) { + let value = std::ptr::read_unaligned(base.add(offset) as *const i32); + if (1..=100).contains(&value) { + return Some(value); + } + } + None + } +} + +fn findusermsg(name: *const c_char) -> *mut UserMsg { + unsafe { + let mut usermsg = CLIENT_USERMSGS; + if usermsg.is_null() { return ptr::null_mut(); } + + while !usermsg.is_null() { + let msg_name_ptr = (*usermsg).name.as_ptr() as *const c_char; + if !msg_name_ptr.is_null() && *msg_name_ptr != 0 { + if libc::strcmp(msg_name_ptr, name) == 0 { + return usermsg; + } + } + usermsg = (*usermsg).next; + } + ptr::null_mut() + } +} + +unsafe extern "C" fn teaminfo(dummy: usize, name: *const c_char, size: i32, buf: *mut c_void) -> i32 { + parsemsg_begin(buf, size, 1); // PARSEMSG_USERMSG + let i = parsemsg_readbyte() as usize; + let team = parsemsg_readstr(); + if i < PLAYER_TEAM.len() { + set_player_team(i, &team); + } + if let Some(orig) = ORIG_TEAMINFO { + orig(dummy, name, size, buf) + } else { 1 } +} + +unsafe extern "C" fn health_hook(dummy: usize, name: *const c_char, size: i32, buf: *mut c_void) -> i32 { + parsemsg_begin(buf, size, 1); + let hp = parsemsg_readbyte() as i32; + if let Some(lp) = ENGINE.as_ref().map(|e| (e.getlocalplayer)()) { + if !lp.is_null() { + let idx = (*lp).i as usize; + if idx < PLAYER_DATA.len() { + PLAYER_DATA[idx].health = hp; + PLAYER_DATA[idx].is_alive = hp > 0; + } + } + } + if let Some(orig) = ORIG_HEALTH { + orig(dummy, name, size, buf) + } else { 1 } +} + +unsafe extern "C" fn scoreattrib_hook(dummy: usize, name: *const c_char, size: i32, buf: *mut c_void) -> i32 { + parsemsg_begin(buf, size, 1); + let idx = parsemsg_readbyte() as usize; + let flags = parsemsg_readbyte(); + if idx < PLAYER_DATA.len() { + PLAYER_DATA[idx].is_alive = (flags & 1) == 0; + } + if let Some(orig) = ORIG_SCOREATTRIB { + orig(dummy, name, size, buf) + } else { 1 } +} + +unsafe extern "C" fn radar_hook(dummy: usize, name: *const c_char, size: i32, buf: *mut c_void) -> i32 { + parsemsg_begin(buf, size, 1); + let idx = parsemsg_readbyte() as usize; + let x = parsemsg_readcoord(); + let y = parsemsg_readcoord(); + let z = parsemsg_readcoord(); + if idx < PLAYER_DATA.len() { + PLAYER_DATA[idx].pos = Vector { x, y, z }; + } + if let Some(orig) = ORIG_RADAR { + orig(dummy, name, size, buf) + } else { 1 } +} + +unsafe extern "C" fn resethud_hook(dummy: usize, name: *const c_char, size: i32, buf: *mut c_void) -> i32 { + for i in 0..PLAYER_DATA.len() { + PLAYER_DATA[i].health = 0; + PLAYER_DATA[i].is_alive = false; + PLAYER_DATA[i].pos = Vector { x: 0.0, y: 0.0, z: 0.0 }; + } + if let Some(orig) = ORIG_RESETHUD { + orig(dummy, name, size, buf) + } else { 1 } +} fn log(msg: &str) { if let Ok(mut file) = OpenOptions::new().create(true).append(true).open("/tmp/basturma.log") { let _ = writeln!(file, "[DEBUG] {}", msg); } } -#[ctor::ctor] -fn init() { - log("Library injected. Starting search thread..."); - std::thread::spawn(|| { - unsafe { - let mut hw = ptr::null_mut(); - // Список возможных имен движка в Linux - let libs = [ - b"hw.so\0".as_ptr() as *const c_char, - b"engine_i486.so\0".as_ptr() as *const c_char, - b"engine.so\0".as_ptr() as *const c_char, - b"./hw.so\0".as_ptr() as *const c_char, - b"hw_i386.so\0".as_ptr() as *const c_char, - ]; +fn cstr_to_str<'a>(ptr: *const c_char) -> &'a str { + if ptr.is_null() { + return ""; + } + unsafe { CStr::from_ptr(ptr) }.to_str().unwrap_or("") +} - log("Waiting for engine library to be loaded..."); - for i in 0..500 { // Ждем до 50 секунд - for &lib in libs.iter() { - hw = dlopen(lib, RTLD_LAZY | RTLD_NOLOAD); - if !hw.is_null() { - let lib_name = std::ffi::CStr::from_ptr(lib).to_string_lossy(); - log(&format!("Success! Found engine: {}", lib_name)); - break; - } - } - if !hw.is_null() { break; } - std::thread::sleep(std::time::Duration::from_millis(100)); +// -------- Movement helpers (ported from deboogerxyz/ahc) -------- + +fn vector_len2d(v: Vector) -> f32 { + (v.x * v.x + v.y * v.y).sqrt() +} + +fn vector_toang2d(v: Vector) -> f32 { + v.y.atan2(v.x).to_degrees() +} + +fn vector_fromang2d(ang_deg: f32) -> Vector { + let ang = ang_deg.to_radians(); + Vector { x: ang.cos(), y: ang.sin(), z: 0.0 } +} + +fn isalive(ent: *mut Entity) -> bool { + if ent.is_null() { + return false; + } + unsafe { + let idx = (*ent).i as usize; + if idx < PLAYER_DATA.len() && PLAYER_DATA[idx].is_alive { + return true; + } + let mt = (*ent).curstate.movetype; + mt != MOVETYPE_TOSS && mt != MOVETYPE_NONE + } +} + +fn bhop_apply(cmd: &mut UserCmd) { + unsafe { + if !isalive(LOCALPLAYER) { + return; + } + if PMOVE.is_null() { + return; + } + let pm = &*PMOVE; + let on_ground = (pm.flags & FL_ONGROUND) != 0; + + // Same behavior as ahc: if we're in-air and were already in-air last tick, clear IN_JUMP. + if !on_ground && !WAS_ONGROUND { + cmd.buttons &= !IN_JUMP; + } + WAS_ONGROUND = on_ground; + } +} + +fn faststop_apply(cmd: &mut UserCmd) { + unsafe { + if (cmd.buttons & (IN_MOVELEFT | IN_MOVERIGHT | IN_FORWARD | IN_BACK)) != 0 { + return; + } + if (cmd.buttons & IN_JUMP) != 0 { + return; + } + if PMOVE.is_null() { + return; + } + let pm = &*PMOVE; + if (pm.flags & FL_ONGROUND) == 0 { + return; + } + if !isalive(LOCALPLAYER) { + return; + } + + let speed = vector_len2d(pm.velocity); + if speed < 15.0 { + return; + } + + let dir = cmd.viewangles.y - vector_toang2d(pm.velocity); + let negdir = vector_fromang2d(dir); + // TODO (upstream): use runtime cvars, for now hardcode 400 like ahc. + cmd.forwardmove = negdir.x * -400.0; + cmd.sidemove = negdir.y * -400.0; + } +} + +fn angledelta_deg(a: f32, b: f32) -> f32 { + // remainder in degrees, keep within [-180, 180] + let mut d = (a - b).rem_euclid(360.0); + if d > 180.0 { + d -= 360.0; + } + d +} + +fn autostrafe_apply(cmd: &mut UserCmd) { + unsafe { + if (cmd.buttons & IN_JUMP) == 0 { + return; + } + if !isalive(LOCALPLAYER) { + return; + } + if PMOVE.is_null() { + return; + } + let pm = &*PMOVE; + let speed = vector_len2d(pm.velocity); + if speed < 15.0 { + return; + } + + // TODO (upstream): runtime cvars: sv_airaccelerate=10, sv_maxspeed=320 + let term = 10.0 / 320.0 * 100.0 / speed; + if !(term > -1.0 && term < 1.0) { + return; + } + let bestdelta = term.acos(); // radians + + let yaw = cmd.viewangles.y.to_radians(); + let veldir = pm.velocity.y.atan2(pm.velocity.x) - yaw; + let wishang = (-cmd.sidemove).atan2(cmd.forwardmove); + let delta = angledelta_deg(veldir.to_degrees(), wishang.to_degrees()).to_radians(); + + let movedir = if delta < 0.0 { + veldir + bestdelta + } else { + veldir - bestdelta + }; + + cmd.forwardmove = movedir.cos() * 400.0; + cmd.sidemove = -movedir.sin() * 400.0; + } +} + +fn fixmovement(cmd: &mut UserCmd, old_yaw_deg: f32) { + // Port of ahc fixmovement: preserve intended movement when view yaw changes. + let oldyaw = old_yaw_deg + if old_yaw_deg < 0.0 { 360.0 } else { 0.0 }; + let newyaw = cmd.viewangles.y + if cmd.viewangles.y < 0.0 { 360.0 } else { 0.0 }; + + let mut delta = if newyaw < oldyaw { + (newyaw - oldyaw).abs() + } else { + 360.0 - (newyaw - oldyaw).abs() + }; + delta = 360.0 - delta; + + let forward = cmd.forwardmove; + let side = cmd.sidemove; + let dr = delta.to_radians(); + cmd.forwardmove = dr.cos() * forward + (dr + 90.0_f32.to_radians()).cos() * side; + cmd.sidemove = dr.sin() * forward + (dr + 90.0_f32.to_radians()).sin() * side; +} + +fn vec3_sub(a: Vector, b: Vector) -> Vector { + Vector { + x: a.x - b.x, + y: a.y - b.y, + z: a.z - b.z, + } +} + +fn vec3_dot(a: Vector, b: Vector) -> f32 { + a.x * b.x + a.y * b.y + a.z * b.z +} + +fn angle_vectors(ang: Vector) -> (Vector, Vector, Vector) { + let pitch = ang.x.to_radians(); + let yaw = ang.y.to_radians(); + let sp = pitch.sin(); + let cp = pitch.cos(); + let sy = yaw.sin(); + let cy = yaw.cos(); + let forward = Vector { + x: cp * cy, + y: cp * sy, + z: -sp, + }; + let right = Vector { + x: -sy, + y: cy, + z: 0.0, + }; + let up = Vector { + x: sp * cy, + y: sp * sy, + z: cp, + }; + (forward, right, up) +} + +fn world_to_screen(world: Vector, cam_origin: Vector, view_angles: Vector, width: f32, height: f32) -> Option<(f32, f32, f32)> { + let (forward, right, up) = angle_vectors(view_angles); + let delta = vec3_sub(world, cam_origin); + let z = vec3_dot(delta, forward); + if z <= 0.1 { + return None; + } + + let x = vec3_dot(delta, right); + let y = vec3_dot(delta, up); + let fov: f32 = 90.0; + let tan_half_fov_x = (fov * 0.5).to_radians().tan(); + let tan_half_fov_y = tan_half_fov_x * height / width; + let screen_x = width * 0.5 * (1.0 - x / (z * tan_half_fov_x)); + let screen_y = height * 0.5 * (1.0 - y / (z * tan_half_fov_y)); + Some((screen_x, screen_y, z)) +} + +fn get_player_name(idx: i32) -> Option { + let mut info = PlayerInfo::default(); + unsafe { + let mut called = false; + if let Some(Some(get_info)) = GET_PLAYER_INFO.get() { + (get_info)(idx, &mut info); + called = true; + } else if !ENGINE.is_null() { + ((*ENGINE).getplayerinfo)(idx, &mut info); + called = true; + } + + if called && !info.name.is_null() { + let name = CStr::from_ptr(info.name).to_string_lossy(); + let name = name.trim_matches(char::from(0)).trim(); + if !name.is_empty() { + return Some(name.to_owned()); + } + } + } + None +} + +fn render_esp(draw_list: &imgui::DrawListMut, width: f32, height: f32) { + unsafe { + if !SETTINGS.wh_enabled && !SETTINGS.esp_enabled { + return; + } + if ENGINE.is_null() || LOCALPLAYER.is_null() { + return; + } + + let cam_origin = VIEW_ORIGIN; + let view_angles = SETTINGS.last_angles; + let max_clients = ((*ENGINE).getmaxclients)(); + if max_clients <= 0 { + return; + } + + for idx in 1..=32 { + if idx > max_clients as usize { break; } + let ent = ((*ENGINE).getentitybyindex)(idx as i32); + if ent.is_null() || ent == LOCALPLAYER { + continue; + } + if !isalive(ent) { + continue; + } + if (*ent).player == 0 { + continue; } - if hw.is_null() { - log("FATAL: Engine library not found after timeout!"); + let origin = get_entity_world_origin(ent); + let head = Vector { + x: origin.x, + y: origin.y, + z: origin.z + 36.0, + }; + let body = origin; + + let head_screen = world_to_screen(head, cam_origin, view_angles, width, height); + let screen_pos = head_screen + .or_else(|| world_to_screen(body, cam_origin, view_angles, width, height)); + + if let Some((hx, hy, depth)) = screen_pos { + let fov_rad = 90.0f32.to_radians(); + let tan_half_fov = (fov_rad * 0.5).tan(); + let tan_half_fov_y = tan_half_fov * height as f32 / width as f32; + let screen_scale_y = height as f32 * 0.5 / tan_half_fov_y; + let screen_scale_x = width as f32 * 0.5 / tan_half_fov; + let height_px = 72.0 * screen_scale_y / depth; + let width_px = 32.0 * screen_scale_x / depth; + let center_x = hx; + let top = if head_screen.is_some() { + hy + } else { + hy - height_px * 0.5 + }; + let bottom = top + height_px; + let left = center_x - width_px * 0.5; + let right = center_x + width_px * 0.5; + + let base_name = if let Some(name) = get_player_name(idx as i32) { + name + } else { + format!("P{}", idx) + }; + let health = get_entity_health(ent); + let label = if let Some(hp) = health { + format!("{} [{}]", base_name, hp) + } else { + base_name + }; + + let mut team = player_team(idx); + if team.is_empty() { + // Fallback to model-based team detection + let mut info = PlayerInfo::default(); + unsafe { + let mut called = false; + if let Some(Some(get_info)) = GET_PLAYER_INFO.get() { + (get_info)(idx as i32, &mut info); + called = true; + } else if !ENGINE.is_null() { + ((*ENGINE).getplayerinfo)(idx as i32, &mut info); + called = true; + } + + if called && !info.model.is_null() { + let model = CStr::from_ptr(info.model).to_string_lossy().to_uppercase(); + if model.contains("TERROR") || model.contains("LEET") || model.contains("ARCTIC") || model.contains("GUERILLA") { + team = "T"; + } else if model.contains("URBAN") || model.contains("GSG9") || model.contains("SAS") || model.contains("GIGN") || model.contains("VIP") { + team = "CT"; + } + } + } + } + let color = team_color(team); + let bg_color = imgui::ImColor32::from_rgba(0, 0, 0, 120); + let health_color = if let Some(hp) = health { + if hp > 60 { + imgui::ImColor32::from_rgba(40, 220, 40, 200) + } else if hp > 30 { + imgui::ImColor32::from_rgba(220, 200, 40, 200) + } else { + imgui::ImColor32::from_rgba(220, 40, 40, 200) + } + } else { + imgui::ImColor32::from_rgba(40, 220, 40, 200) + }; + + if SETTINGS.wh_enabled { + draw_list + .add_rect([left - 1.5, top - 1.5], [right + 1.5, bottom + 1.5], imgui::ImColor32::from_rgba(0, 0, 0, 60)) + .thickness(0.0) + .filled(true) + .build(); + draw_list.add_rect([left, top], [right, bottom], color).thickness(2.5).build(); + draw_list.add_line([left, top], [right, top], color).thickness(1.2).build(); + draw_list.add_line([left, bottom], [right, bottom], color).thickness(1.2).build(); + } + + if SETTINGS.esp_enabled { + let text_height = 14.0; + let label_pos = [left, top - text_height - 4.0]; + let label_width = width_px.max(80.0); + draw_list + .add_rect(label_pos, [label_pos[0] + label_width, label_pos[1] + text_height + 4.0], bg_color) + .filled(true) + .thickness(0.0) + .build(); + draw_list.add_text([label_pos[0] + 2.0, label_pos[1] + 2.0], imgui::ImColor32::from_rgba(255, 255, 255, 240), &label); + let mid_x = left - 8.0; + let health_top = bottom - (bottom - top) * health.map_or(1.0, |hp| hp.clamp(0, 100) as f32 / 100.0); + let health_bottom = bottom; + draw_list + .add_rect([mid_x - 4.0, top], [mid_x, bottom], imgui::ImColor32::from_rgba(0, 0, 0, 100)) + .filled(true) + .thickness(0.0) + .build(); + draw_list + .add_rect([mid_x - 4.0, health_top], [mid_x, health_bottom], health_color) + .filled(true) + .thickness(0.0) + .build(); + } + } + } + } +} + +// -------- SDL2 input polling (preferred, avoids Xlib threading crashes) -------- + +#[derive(Clone, Copy)] +struct Sdl2Api { + sdl_get_keyboard_state: unsafe extern "C" fn(*mut i32) -> *const u8, + sdl_get_mouse_state: unsafe extern "C" fn(*mut i32, *mut i32) -> u32, + sdl_set_relative_mouse_mode: Option i32>, + sdl_show_cursor: Option i32>, + sdl_capture_mouse: Option i32>, + sdl_get_mouse_focus: Option *mut c_void>, + sdl_set_window_grab: Option, +} + +static SDL2_API: OnceLock> = OnceLock::new(); + +fn sdl2_init() -> Option { + unsafe { + // Most common soname for SDL2 on Linux. + let lib = dlopen(b"libSDL2-2.0.so.0\0".as_ptr() as *const c_char, RTLD_LAZY); + if lib.is_null() { + return None; + } + + let gks = dlsym(lib, b"SDL_GetKeyboardState\0".as_ptr() as *const c_char); + let gms = dlsym(lib, b"SDL_GetMouseState\0".as_ptr() as *const c_char); + if gks.is_null() || gms.is_null() { + return None; + } + + let sdl_set_relative_mouse_mode = + dlsym(lib, b"SDL_SetRelativeMouseMode\0".as_ptr() as *const c_char); + let sdl_show_cursor = dlsym(lib, b"SDL_ShowCursor\0".as_ptr() as *const c_char); + let sdl_capture_mouse = dlsym(lib, b"SDL_CaptureMouse\0".as_ptr() as *const c_char); + let sdl_get_mouse_focus = dlsym(lib, b"SDL_GetMouseFocus\0".as_ptr() as *const c_char); + let sdl_set_window_grab = dlsym(lib, b"SDL_SetWindowGrab\0".as_ptr() as *const c_char); + + Some(Sdl2Api { + sdl_get_keyboard_state: std::mem::transmute(gks), + sdl_get_mouse_state: std::mem::transmute(gms), + sdl_set_relative_mouse_mode: if sdl_set_relative_mouse_mode.is_null() { + None + } else { + Some(std::mem::transmute(sdl_set_relative_mouse_mode)) + }, + sdl_show_cursor: if sdl_show_cursor.is_null() { + None + } else { + Some(std::mem::transmute(sdl_show_cursor)) + }, + sdl_capture_mouse: if sdl_capture_mouse.is_null() { + None + } else { + Some(std::mem::transmute(sdl_capture_mouse)) + }, + sdl_get_mouse_focus: if sdl_get_mouse_focus.is_null() { + None + } else { + Some(std::mem::transmute(sdl_get_mouse_focus)) + }, + sdl_set_window_grab: if sdl_set_window_grab.is_null() { + None + } else { + Some(std::mem::transmute(sdl_set_window_grab)) + }, + }) + } +} + +fn sdl2_rshift_down() -> Option { + let api = SDL2_API.get_or_init(|| sdl2_init()); + let Some(api) = api.as_ref() else { return None }; + unsafe { + let mut len: i32 = 0; + let state = (api.sdl_get_keyboard_state)(&mut len as *mut i32); + if state.is_null() || len <= 0 { + return None; + } + // SDL2 scancode for Right Shift is SDL_SCANCODE_RSHIFT = 229. + let idx = 229i32; + if idx >= len { + return None; + } + Some(*state.add(idx as usize) != 0) + } +} + +fn sdl2_space_down() -> Option { + let api = SDL2_API.get_or_init(|| sdl2_init()); + let Some(api) = api.as_ref() else { return None }; + unsafe { + let mut len: i32 = 0; + let state = (api.sdl_get_keyboard_state)(&mut len as *mut i32); + if state.is_null() || len <= 0 { + return None; + } + // SDL2 scancode for Space is SDL_SCANCODE_SPACE = 44. + let idx = 44i32; + if idx >= len { + return None; + } + Some(*state.add(idx as usize) != 0) + } +} + +#[derive(Clone, Copy, Default)] +struct Sdl2Mouse { + x: f32, + y: f32, + left: bool, + right: bool, + middle: bool, +} + +fn sdl2_mouse_state() -> Option { + let api = SDL2_API.get_or_init(|| sdl2_init()); + let Some(api) = api.as_ref() else { return None }; + unsafe { + let mut x: i32 = 0; + let mut y: i32 = 0; + let buttons = (api.sdl_get_mouse_state)(&mut x as *mut i32, &mut y as *mut i32); + // SDL button masks: left=1, middle=2, right=4 + Some(Sdl2Mouse { + x: x as f32, + y: y as f32, + left: (buttons & 1) != 0, + middle: (buttons & 2) != 0, + right: (buttons & 4) != 0, + }) + } +} + +fn sdl2_on_menu_state(show_menu: bool) { + unsafe { + let api = SDL2_API.get_or_init(|| sdl2_init()); + let api_opt = api.as_ref(); + + // Most games "capture" the mouse via relative mode and/or window grab. + // Force-disable these when the menu is open so the game can't consume motion. + if let Some(api) = api_opt { + if let (Some(get_focus), Some(set_grab)) = (api.sdl_get_mouse_focus, api.sdl_set_window_grab) + { + let win = get_focus(); + if !win.is_null() { + set_grab(win, if show_menu { 0 } else { 1 }); + } + } + if let Some(set_rel) = api.sdl_set_relative_mouse_mode { + let _ = set_rel(if show_menu { 0 } else { 1 }); + } + if let Some(show_cursor) = api.sdl_show_cursor { + let _ = show_cursor(if show_menu { 1 } else { 0 }); + } + if let Some(capture_mouse) = api.sdl_capture_mouse { + let _ = capture_mouse(if show_menu { 0 } else { 1 }); + } + } else { + let _ = x11_set_pointer_grab(show_menu); + } + + SETTINGS.mouse_grabbed = show_menu; + } +} + +// -------- X11 Right Shift polling (fallback if hudkeyevent doesn't receive modifiers) -------- + +type Display = c_void; +type Window = u64; +type Cursor = u64; +type Time = u64; + +#[derive(Clone, Copy)] +struct X11Api { + x_init_threads: Option i32>, + x_lock_display: Option, + x_unlock_display: Option, + x_open_display: unsafe extern "C" fn(*const c_char) -> *mut Display, + x_query_keymap: unsafe extern "C" fn(*mut Display, *mut c_char) -> i32, + x_keysym_to_keycode: unsafe extern "C" fn(*mut Display, u64) -> u32, + x_default_root_window: unsafe extern "C" fn(*mut Display) -> Window, + x_query_pointer: unsafe extern "C" fn( + *mut Display, + Window, + *mut Window, + *mut Window, + *mut i32, + *mut i32, + *mut i32, + *mut i32, + *mut u32, + ) -> i32, + x_grab_pointer: unsafe extern "C" fn( + *mut Display, + Window, + i32, + u32, + i32, + i32, + Window, + Cursor, + Time, + ) -> i32, + x_ungrab_pointer: unsafe extern "C" fn(*mut Display, Time) -> i32, +} + +struct X11State { + api: X11Api, + // Store as usize so X11State is Sync for static storage. + display: usize, + root: Window, + rshift_keycode: u8, +} + +static X11_STATE: OnceLock> = OnceLock::new(); + +fn x11_init() -> Option { + unsafe { + // Try common sonames. + let lib = dlopen(b"libX11.so.6\0".as_ptr() as *const c_char, RTLD_LAZY); + if lib.is_null() { + return None; + } + + // Optional but important for stability if engine uses threads + Xlib. + let x_init_threads = dlsym(lib, b"XInitThreads\0".as_ptr() as *const c_char); + let x_lock_display = dlsym(lib, b"XLockDisplay\0".as_ptr() as *const c_char); + let x_unlock_display = dlsym(lib, b"XUnlockDisplay\0".as_ptr() as *const c_char); + + let x_open_display = dlsym(lib, b"XOpenDisplay\0".as_ptr() as *const c_char); + let x_query_keymap = dlsym(lib, b"XQueryKeymap\0".as_ptr() as *const c_char); + let x_keysym_to_keycode = dlsym(lib, b"XKeysymToKeycode\0".as_ptr() as *const c_char); + let x_default_root_window = dlsym(lib, b"XDefaultRootWindow\0".as_ptr() as *const c_char); + let x_query_pointer = dlsym(lib, b"XQueryPointer\0".as_ptr() as *const c_char); + let x_grab_pointer = dlsym(lib, b"XGrabPointer\0".as_ptr() as *const c_char); + let x_ungrab_pointer = dlsym(lib, b"XUngrabPointer\0".as_ptr() as *const c_char); + + if x_open_display.is_null() + || x_query_keymap.is_null() + || x_keysym_to_keycode.is_null() + || x_default_root_window.is_null() + || x_query_pointer.is_null() + || x_grab_pointer.is_null() + || x_ungrab_pointer.is_null() + { + return None; + } + + let api = X11Api { + x_init_threads: if x_init_threads.is_null() { None } else { Some(std::mem::transmute(x_init_threads)) }, + x_lock_display: if x_lock_display.is_null() { None } else { Some(std::mem::transmute(x_lock_display)) }, + x_unlock_display: if x_unlock_display.is_null() { None } else { Some(std::mem::transmute(x_unlock_display)) }, + x_open_display: std::mem::transmute(x_open_display), + x_query_keymap: std::mem::transmute(x_query_keymap), + x_keysym_to_keycode: std::mem::transmute(x_keysym_to_keycode), + x_default_root_window: std::mem::transmute(x_default_root_window), + x_query_pointer: std::mem::transmute(x_query_pointer), + x_grab_pointer: std::mem::transmute(x_grab_pointer), + x_ungrab_pointer: std::mem::transmute(x_ungrab_pointer), + }; + + if let Some(init_threads) = api.x_init_threads { + // If it fails, we still proceed but avoid crashing when possible. + let _ = init_threads(); + } + + let display = (api.x_open_display)(ptr::null()); + if display.is_null() { + return None; + } + + let root = (api.x_default_root_window)(display); + if root == 0 { + return None; + } + + // XK_Shift_R keysym = 0xFFE2 + let kc = (api.x_keysym_to_keycode)(display, 0xFFE2); + if kc == 0 || kc > 255 { + return None; + } + + Some(X11State { + api, + display: display as usize, + root, + rshift_keycode: kc as u8, + }) + } +} + +fn x11_rshift_down() -> Option { + let state = X11_STATE.get_or_init(|| x11_init()); + let Some(st) = state.as_ref() else { return None }; + + unsafe { + let mut keys: [c_char; 32] = [0; 32]; + let display = st.display as *mut Display; + if let Some(lock) = st.api.x_lock_display { lock(display); } + (st.api.x_query_keymap)(display, keys.as_mut_ptr()); + if let Some(unlock) = st.api.x_unlock_display { unlock(display); } + + let kc = st.rshift_keycode as usize; + let byte = kc / 8; + let bit = kc % 8; + let mask = 1u8 << bit; + let val = keys[byte] as u8; + Some((val & mask) != 0) + } +} + +#[derive(Clone, Copy, Default)] +struct X11Mouse { + x: f32, + y: f32, + left: bool, + right: bool, + middle: bool, +} + +fn x11_mouse_state() -> Option { + let state = X11_STATE.get_or_init(|| x11_init()); + let Some(st) = state.as_ref() else { return None }; + + unsafe { + let display = st.display as *mut Display; + let mut root_return: Window = 0; + let mut child_return: Window = 0; + let mut root_x: i32 = 0; + let mut root_y: i32 = 0; + let mut win_x: i32 = 0; + let mut win_y: i32 = 0; + let mut mask: u32 = 0; + + if let Some(lock) = st.api.x_lock_display { lock(display); } + let ok = (st.api.x_query_pointer)( + display, + st.root, + &mut root_return, + &mut child_return, + &mut root_x, + &mut root_y, + &mut win_x, + &mut win_y, + &mut mask, + ); + if let Some(unlock) = st.api.x_unlock_display { unlock(display); } + if ok == 0 { + return None; + } + + // Button masks from X11: Button1=1<<8, Button2=1<<9, Button3=1<<10. + let button1 = (mask & (1 << 8)) != 0; + let button2 = (mask & (1 << 9)) != 0; + let button3 = (mask & (1 << 10)) != 0; + + Some(X11Mouse { + x: root_x as f32, + y: root_y as f32, + left: button1, + middle: button2, + right: button3, + }) + } +} + +fn x11_set_pointer_grab(grab: bool) -> bool { + let state = X11_STATE.get_or_init(|| x11_init()); + let Some(st) = state.as_ref() else { return false }; + + unsafe { + let display = st.display as *mut Display; + if let Some(lock) = st.api.x_lock_display { lock(display); } + if grab { + // Event masks: ButtonPress(1<<2), ButtonRelease(1<<3), PointerMotion(1<<6). + let event_mask: u32 = (1 << 2) | (1 << 3) | (1 << 6); + let owner_events: i32 = 0; + let pointer_mode: i32 = 1; // GrabModeAsync + let keyboard_mode: i32 = 1; // GrabModeAsync + let status = (st.api.x_grab_pointer)( + display, + st.root, + owner_events, + event_mask, + pointer_mode, + keyboard_mode, + 0, + 0, + 0, + ); + if let Some(unlock) = st.api.x_unlock_display { unlock(display); } + status == 0 + } else { + (st.api.x_ungrab_pointer)(display, 0); + if let Some(unlock) = st.api.x_unlock_display { unlock(display); } + true + } + } +} + +// -------- Minimal ImGui overlay rendering in hudredraw -------- + +type GlXGetProcAddressARB = unsafe extern "C" fn(*const u8) -> *const c_void; + +struct Overlay { + imgui: imgui::Context, + renderer: imgui_glow_renderer::AutoRenderer, + last_frame: std::time::Instant, +} + +static mut OVERLAY: Option = None; + +fn get_gl_loader() -> Option { + unsafe { + let libgl = dlopen(b"libGL.so.1\0".as_ptr() as *const c_char, RTLD_LAZY); + if libgl.is_null() { + return None; + } + let sym = dlsym(libgl, b"glXGetProcAddressARB\0".as_ptr() as *const c_char); + if sym.is_null() { + return None; + } + Some(std::mem::transmute(sym)) + } +} + +fn overlay_init() -> Option { + let glx_get_proc = get_gl_loader()?; + + unsafe { + let gl = glow::Context::from_loader_function(|name| { + // Try RTLD_DEFAULT first (some drivers export core symbols). + if let Ok(c_name) = std::ffi::CString::new(name) { + let sym = libc::dlsym(libc::RTLD_DEFAULT, c_name.as_ptr() as *const c_char); + if !sym.is_null() { + return sym as *const c_void; + } + } + glx_get_proc(name.as_ptr()) + }); + + let mut imgui = imgui::Context::create(); + imgui.set_ini_filename(None); + imgui.set_log_filename(None); + + // Slightly nicer defaults. + imgui.io_mut().config_flags |= imgui::ConfigFlags::NO_MOUSE_CURSOR_CHANGE; + + let renderer = imgui_glow_renderer::AutoRenderer::initialize(gl, &mut imgui).ok()?; + + Some(Overlay { + imgui, + renderer, + last_frame: std::time::Instant::now(), + }) + } +} + +fn overlay_render(show_menu: bool) { + unsafe { + if OVERLAY.is_none() { + OVERLAY = overlay_init(); + } + let Some(ov) = OVERLAY.as_mut() else { return }; + + // Delta time. + let now = std::time::Instant::now(); + let dt = (now - ov.last_frame).as_secs_f32().max(1.0 / 1000.0); + ov.last_frame = now; + ov.imgui.io_mut().delta_time = dt; + + // Feed mouse state via SDL2 (no Xlib calls). + ov.imgui.io_mut().mouse_draw_cursor = show_menu; + if show_menu { + if let Some(ms) = sdl2_mouse_state() { + ov.imgui.io_mut().mouse_pos = [ms.x, ms.y]; + ov.imgui.io_mut().mouse_down = [ms.left, ms.right, ms.middle, false, false]; + } + } else { + ov.imgui.io_mut().mouse_pos = [-1.0, -1.0]; + ov.imgui.io_mut().mouse_down = [false; 5]; + } + + // Display size from GL viewport. + let mut vp = [0i32; 4]; + ov.renderer + .gl_context() + .get_parameter_i32_slice(glow::VIEWPORT, &mut vp); + ov.imgui.io_mut().display_size = [vp[2] as f32, vp[3] as f32]; + + let ui = ov.imgui.frame(); + if show_menu { + ui.window("basturma") + .always_auto_resize(true) + .build(|| { + ui.text("Menu is open"); + ui.text("Hotkey: Right Shift"); + ui.separator(); + ui.checkbox("Bhop (hold Space)", &mut SETTINGS.bhop_enabled); + ui.checkbox("Fast stop", &mut SETTINGS.faststop_enabled); + ui.checkbox("Autostrafe", &mut SETTINGS.autostrafe_enabled); + ui.checkbox("Wallhack (WH)", &mut SETTINGS.wh_enabled); + ui.checkbox("ESP labels", &mut SETTINGS.esp_enabled); + }); + } + + let hide_window_flags = imgui::WindowFlags::NO_TITLE_BAR + | imgui::WindowFlags::NO_COLLAPSE + | imgui::WindowFlags::NO_RESIZE + | imgui::WindowFlags::NO_MOVE + | imgui::WindowFlags::NO_SAVED_SETTINGS + | imgui::WindowFlags::NO_NAV + | imgui::WindowFlags::NO_INPUTS + | imgui::WindowFlags::NO_BACKGROUND; + + ui.window("##basturma_esp") + .flags(hide_window_flags) + .position([0.0, 0.0], imgui::Condition::Always) + .size([vp[2] as f32, vp[3] as f32], imgui::Condition::Always) + .build(|| { + let draw_list = ui.get_window_draw_list(); + render_esp(&draw_list, vp[2] as f32, vp[3] as f32); + }); + + let draw_data = ov.imgui.render(); + let _ = ov.renderer.render(draw_data); + } +} + +#[ctor::ctor] +fn init() { + log("------------------------------------------"); + log("Injected: LEGACY SIGNATURE TEST V17"); + + std::thread::spawn(|| { + unsafe { + let hw = dlopen(b"hw.so\0".as_ptr() as *const c_char, RTLD_LAZY | RTLD_NOLOAD); + if hw.is_null() || HOOKS_APPLIED { return; } + + let client_ptr = dlsym(hw, b"cl_funcs\0".as_ptr() as *const c_char) as *mut Client; + if client_ptr.is_null() { + log("FATAL: cl_funcs not found"); return; } - setup(hw); + let engine_ptr = dlsym(hw, b"cl_enginefuncs\0".as_ptr() as *const c_char) as *mut Engine; + if engine_ptr.is_null() { + log("WARN: cl_enginefuncs not found"); + } else { + ENGINE = engine_ptr; + } + + let mut get_player_info_sym = dlsym(hw, b"GetPlayerInfo\0".as_ptr() as *const c_char); + + let pmove_sym = dlsym(hw, b"pmove\0".as_ptr() as *const c_char); + if pmove_sym.is_null() { + log("WARN: pmove not found"); + } else { + // In ahc: playermove = *(PlayerMove **)dlsym(hw, "pmove"); + PMOVE = *(pmove_sym as *mut *mut PlayerMove); + } + + let shutdownusermsgs = dlsym(hw, b"CL_ShutDownUsrMessages\0".as_ptr() as *const c_char); + if !shutdownusermsgs.is_null() { + let clientusermsgs_sym = dlsym(hw, b"clientusermsgs\0".as_ptr() as *const c_char); + if !clientusermsgs_sym.is_null() { + CLIENT_USERMSGS = clientusermsgs_sym as *mut UserMsg; + log("INFO: clientusermsgs found via dlsym"); + } else { + log("WARN: clientusermsgs not found, message hooks disabled"); + } + } + + let clientdll = *(dlsym(hw, b"hClientDLL\0".as_ptr() as *const c_char) as *mut *mut c_void); + if get_player_info_sym.is_null() && !clientdll.is_null() { + get_player_info_sym = dlsym(clientdll, b"GetPlayerInfo\0".as_ptr() as *const c_char); + } + if !get_player_info_sym.is_null() { + let _ = GET_PLAYER_INFO.set(Some(std::mem::transmute(get_player_info_sym))); + log("INFO: GetPlayerInfo resolved"); + } else { + log("WARN: GetPlayerInfo not found"); + } + + ORIG_CLIENT = Some(*client_ptr); + ORIG_CREATEMOVE = Some((*client_ptr).createmove); + + let addr = client_ptr as usize; + let page_size = libc::sysconf(libc::_SC_PAGESIZE) as usize; + let page_start = addr & !(page_size - 1); + mprotect(page_start as *mut c_void, page_size * 2, PROT_READ | PROT_WRITE | PROT_EXEC); + + (*client_ptr).hudredraw = hooked_hudredraw; + (*client_ptr).hudkeyevent = hooked_hudkeyevent; + (*client_ptr).createmove = hooked_createmove; + + HOOKS_APPLIED = true; + log("SUCCESS: Hooks applied with hidden arg fix."); } }); } -unsafe fn setup(hw: *mut c_void) { - log("Starting setup and symbol lookups..."); - - FILL_RGBA_BOX = std::mem::transmute(dlsym(hw, b"Draw_FillRGBA\0".as_ptr() as *const c_char)); - if FILL_RGBA_BOX.is_some() { log("Found Draw_FillRGBA"); } - - let engine_ptr = dlsym(hw, b"cl_enginefuncs\0".as_ptr() as *const c_char) as *mut Engine; - if !engine_ptr.is_null() { - ENGINE.store(engine_ptr, Ordering::SeqCst); - log("Found cl_enginefuncs"); - } else { - log("WARNING: cl_enginefuncs not found via dlsym"); - } - - let client_ptr = dlsym(hw, b"cl_funcs\0".as_ptr() as *const c_char) as *mut Client; - if !client_ptr.is_null() { - CLIENT.store(client_ptr, Ordering::SeqCst); - ORIG_CLIENT = Some(*client_ptr); - - let addr = client_ptr as usize; - let page_size = libc::sysconf(libc::_SC_PAGESIZE) as usize; - let page_start = addr & !(page_size - 1); - mprotect(page_start as *mut c_void, page_size, PROT_READ | PROT_WRITE); - - (*client_ptr).createmove = hooked_createmove; - (*client_ptr).hudredraw = hooked_hudredraw; - (*client_ptr).hudkeyevent = hooked_hudkeyevent; - log("Hooks applied: CreateMove, HUD_Redraw, Key_Event"); - } - - let pmove_ptr_ptr = dlsym(hw, b"pmove\0".as_ptr() as *const c_char) as *mut *mut PlayerMove; - if !pmove_ptr_ptr.is_null() { - PLAYER_MOVE.store(*pmove_ptr_ptr, Ordering::SeqCst); - log("Found pmove"); - } - - dlclose(hw); - log("Setup complete!"); -} - -extern "C" fn hooked_hudkeyevent(eventcode: i32, keynum: i32, binding: *const c_char) -> i32 { +extern "C" fn hooked_hudredraw(dummy: usize, time: f32, intermission: i32) -> i32 { unsafe { - if eventcode == 1 && keynum == 146 { // INSERT - SETTINGS.show_menu = !SETTINGS.show_menu; - return 0; - } - - if SETTINGS.show_menu && eventcode == 1 { - match keynum { - 135 => { SETTINGS.bhop = !SETTINGS.bhop; return 0; } // F1 - 136 => { SETTINGS.chams = !SETTINGS.chams; return 0; } // F2 - _ => {} + // Retry hooking messages if they weren't found at init. + if !CLIENT_USERMSGS.is_null() { + if ORIG_TEAMINFO.is_none() { + let msg = findusermsg(b"TeamInfo\0".as_ptr() as *const c_char); + if !msg.is_null() { + ORIG_TEAMINFO = Some((*msg).fn_); + (*msg).fn_ = teaminfo; + log("INFO: TeamInfo hook applied"); + } + } + if ORIG_HEALTH.is_none() { + let msg = findusermsg(b"Health\0".as_ptr() as *const c_char); + if !msg.is_null() { + ORIG_HEALTH = Some((*msg).fn_); + (*msg).fn_ = health_hook; + log("INFO: Health hook applied"); + } + } + if ORIG_SCOREATTRIB.is_none() { + let msg = findusermsg(b"ScoreAttrib\0".as_ptr() as *const c_char); + if !msg.is_null() { + ORIG_SCOREATTRIB = Some((*msg).fn_); + (*msg).fn_ = scoreattrib_hook; + log("INFO: ScoreAttrib hook applied"); + } + } + if ORIG_RADAR.is_none() { + let msg = findusermsg(b"Radar\0".as_ptr() as *const c_char); + if !msg.is_null() { + ORIG_RADAR = Some((*msg).fn_); + (*msg).fn_ = radar_hook; + log("INFO: Radar hook applied"); + } + } + if ORIG_RESETHUD.is_none() { + let msg = findusermsg(b"ResetHUD\0".as_ptr() as *const c_char); + if !msg.is_null() { + ORIG_RESETHUD = Some((*msg).fn_); + (*msg).fn_ = resethud_hook; + log("INFO: ResetHUD hook applied"); + } } } - - if let Some(orig) = ORIG_CLIENT { - (orig.hudkeyevent)(eventcode, keynum, binding) - } else { 1 } - } -} -extern "C" fn hooked_hudredraw(time: f32, intermission: i32) -> i32 { - unsafe { + // Keep mouse uncaptured while the menu is open (the game may try to re-enable it). if SETTINGS.show_menu { - if let Some(fill_box) = FILL_RGBA_BOX { - fill_box(100, 100, 200, 100, 0, 0, 0, 200); - let bhop_c = if SETTINGS.bhop { (0, 255, 0) } else { (255, 0, 0) }; - fill_box(110, 130, 15, 15, bhop_c.0, bhop_c.1, bhop_c.2, 255); - let chams_c = if SETTINGS.chams { (0, 255, 0) } else { (255, 0, 0) }; - fill_box(110, 160, 15, 15, chams_c.0, chams_c.1, chams_c.2, 255); + sdl2_on_menu_state(true); + } + + // Toggle menu on Right Shift via SDL2 (more reliable than hudkeyevent for modifiers). + if let Some(down) = sdl2_rshift_down() { + if down && !SETTINGS.last_rshift_down { + SETTINGS.show_menu = !SETTINGS.show_menu; + sdl2_on_menu_state(SETTINGS.show_menu); + log(&format!("MENU TOGGLED (SDL2 RSHIFT): {}", SETTINGS.show_menu)); + } + SETTINGS.last_rshift_down = down; + } + + if let Some(orig) = ORIG_CLIENT { + let ret = (orig.hudredraw)(dummy, time, intermission); + // Render overlay after original HUD. + overlay_render(SETTINGS.show_menu); + ret + } else { 0 } + } +} + +extern "C" fn hooked_hudkeyevent(dummy: usize, eventcode: i32, keynum: i32, binding: *const c_char) -> i32 { + unsafe { + // Log *all* key events (press/release/etc) to learn what the engine sends for modifiers. + log(&format!( + "KEYEVENT: eventcode={}, code={}, dummy={:X}, binding={}", + eventcode, + keynum, + dummy, + cstr_to_str(binding) + )); + + // Toggle only on key-press (eventcode==1) to avoid double toggles. + if eventcode == 1 { + // "Right Shift" keycodes differ between builds and input backends. + // We also keep the old "Insert" key as a fallback in case Shift never arrives via hudkeyevent. + if matches!(keynum, 134 | 54 | 303 | 146 | 147) { + SETTINGS.show_menu = !SETTINGS.show_menu; + sdl2_on_menu_state(SETTINGS.show_menu); + log(&format!("MENU TOGGLED: {}", SETTINGS.show_menu)); + return 0; } } - if let Some(orig) = ORIG_CLIENT { (orig.hudredraw)(time, intermission) } else { 0 } + + if let Some(orig) = ORIG_CLIENT { + (orig.hudkeyevent)(dummy, eventcode, keynum, binding) + } else { 1 } } } extern "C" fn hooked_createmove(frametime: f32, cmd: *mut UserCmd, active: i32) { unsafe { - if let Some(orig) = ORIG_CLIENT { (orig.createmove)(frametime, cmd, active); } - let engine = ENGINE.load(Ordering::SeqCst); - if engine.is_null() { return; } - let lp = ((*engine).getlocalplayer)(); - if !lp.is_null() && SETTINGS.bhop { bhop(&mut *cmd); } + if let Some(orig) = ORIG_CREATEMOVE { + (orig)(frametime, cmd, active); + } + + if cmd.is_null() { + return; + } + + if SETTINGS.show_menu { + // Freeze view and suppress movement/actions while menu is open. + if SETTINGS.have_angles { + (*cmd).viewangles = SETTINGS.last_angles; + } else { + SETTINGS.last_angles = (*cmd).viewangles; + SETTINGS.have_angles = true; + } + + (*cmd).forwardmove = 0.0; + (*cmd).sidemove = 0.0; + (*cmd).upmove = 0.0; + (*cmd).buttons = 0; + return; + } else { + // Track last view angles when not in menu. + SETTINGS.last_angles = (*cmd).viewangles; + SETTINGS.have_angles = true; + } + + // Refresh localplayer pointer (needed for isalive checks). + if !ENGINE.is_null() { + let lp = ((*ENGINE).getlocalplayer)(); + if !lp.is_null() { + LOCALPLAYER = lp; + let mut view_height = Vector { x: 0.0, y: 0.0, z: 0.0 }; + if !(*ENGINE).eventapi.is_null() { + ((*(*ENGINE).eventapi).localplayerviewheight)(&mut view_height); + } + VIEW_ORIGIN = Vector { + x: (*lp).origin.x, + y: (*lp).origin.y, + z: (*lp).origin.z + view_height.z, + }; + } + } + + let cmd_ref = &mut *cmd; + let old_yaw = cmd_ref.viewangles.y; + + // bhop (proper): if enabled and Space is held, keep IN_JUMP pressed, + // but drop it mid-air to avoid repeated jumps (same as ahc). + if SETTINGS.bhop_enabled { + if let Some(space) = sdl2_space_down() { + if space { + cmd_ref.buttons |= IN_JUMP; + } + } + bhop_apply(cmd_ref); + } + + if SETTINGS.faststop_enabled { + faststop_apply(cmd_ref); + } + if SETTINGS.autostrafe_enabled { + autostrafe_apply(cmd_ref); + } + + // Preserve intended move direction if view yaw changed in the future (e.g. aimbot). + // (Doesn't hurt even if yaw stays the same.) + fixmovement(cmd_ref, old_yaw); } } - -unsafe fn bhop(cmd: &mut UserCmd) { - if (cmd.buttons & IN_JUMP) == 0 { return; } - let pmove = PLAYER_MOVE.load(Ordering::SeqCst); - if pmove.is_null() { return; } - if ((*pmove).flags & FL_ONGROUND) == 0 { cmd.buttons &= !IN_JUMP; } -} diff --git a/src/sdk.rs b/src/sdk.rs index 8a62954..ec29898 100644 --- a/src/sdk.rs +++ b/src/sdk.rs @@ -2,14 +2,42 @@ use std::ffi::c_char; #[repr(C)] #[derive(Debug, Copy, Clone, Default)] -pub struct Vector { - pub x: f32, - pub y: f32, - pub z: f32, -} +pub struct Vector { pub x: f32, pub y: f32, pub z: f32 } +// --- HLSDK button bits (from deboogerxyz/ahc sdk.h) --- pub const IN_ATTACK: u16 = 1 << 0; pub const IN_JUMP: u16 = 1 << 1; +pub const IN_DUCK: u16 = 1 << 2; +pub const IN_FORWARD: u16 = 1 << 3; +pub const IN_BACK: u16 = 1 << 4; +pub const IN_USE: u16 = 1 << 5; +pub const IN_CANCEL: u16 = 1 << 6; +pub const IN_LEFT: u16 = 1 << 7; +pub const IN_RIGHT: u16 = 1 << 8; +pub const IN_MOVELEFT: u16 = 1 << 9; +pub const IN_MOVERIGHT: u16 = 1 << 10; +pub const IN_ATTACK2: u16 = 1 << 11; +pub const IN_RUN: u16 = 1 << 12; +pub const IN_RELOAD: u16 = 1 << 13; +pub const IN_ALT1: u16 = 1 << 14; +pub const IN_SCORE: u16 = 1 << 15; + +// --- UserMsg for message handling --- +#[repr(C)] +pub struct UserMsg { + pub msg: i32, + pub size: i32, + pub name: [c_char; 16], + pub next: *mut UserMsg, + pub fn_: unsafe extern "C" fn(usize, *const c_char, i32, *mut std::ffi::c_void) -> i32, +} + +// --- move types (subset used by isalive) --- +pub const MOVETYPE_NONE: i32 = 0; +pub const MOVETYPE_TOSS: i32 = 6; + +// --- PlayerMove flags (subset used by movement helpers) --- +pub const FL_ONGROUND: i32 = 1 << 9; #[repr(C)] pub struct UserCmd { @@ -27,16 +55,26 @@ pub struct UserCmd { pub struct EntityState { pub _pad0: [u8; 12], pub msgnum: i32, - pub _pad1: [u8; 72], + pub origin: Vector, + pub angles: Vector, + pub _pad1: [u8; 48], pub movetype: i32, - pub _pad2: [u8; 248], + pub animtime: f32, + pub framerate: f32, + pub body: i32, + pub controller: [u8; 4], + pub blending: [u8; 4], + pub velocity: Vector, + pub mins: Vector, + pub maxs: Vector, + pub _pad2: [u8; 192], } #[repr(C)] pub struct Entity { pub i: i32, pub player: i32, - pub _pad0: [u8; 2 * 340], + pub _pad0: [u8; 2 * std::mem::size_of::()], pub curstate: EntityState, pub _pad1: [u8; 1860], pub origin: Vector, @@ -49,49 +87,42 @@ pub struct EventAPI { pub _pad1: [u8; 0x40], } +#[repr(C)] +pub struct PlayerInfo { + pub name: *const c_char, + pub ping: i16, + pub thisplayer: u8, + pub spectator: u8, + pub packetloss: u8, + pub _pad: [u8; 3], // Alignment padding + pub model: *const c_char, + pub topcolor: i16, + pub bottomcolor: i16, +} + +impl Default for PlayerInfo { + fn default() -> Self { + unsafe { std::mem::zeroed() } + } +} + #[repr(C)] pub struct Engine { - pub _pad0: [u8; 0x8C], + pub _pad0: [u8; 0x54], + pub getplayerinfo: unsafe extern "C" fn(i32, *mut PlayerInfo), + pub _pad1: [u8; 0x8C - 0x54 - 4], pub setviewangles: extern "C" fn(*const Vector), pub getmaxclients: extern "C" fn() -> i32, - pub _pad1: [u8; 0xC], + pub _pad2: [u8; 0xC], pub con_printf: extern "C" fn(*const c_char, ...), - pub _pad2: [u8; 0x28], + pub _pad3: [u8; 0x28], pub getlocalplayer: extern "C" fn() -> *mut Entity, - pub _pad3: [u8; 0x4], - pub getentitybyindex: extern "C" fn(i: i32) -> *mut Entity, - pub _pad4: [u8; 0x78], + pub _pad4: [u8; 0x4], + pub getentitybyindex: extern "C" fn(i32) -> *mut Entity, + pub _pad5: [u8; 0x78], pub eventapi: *mut EventAPI, } -#[repr(C)] -pub struct RefParams { - pub vieworigin: Vector, - pub viewangles: Vector, - pub forward: Vector, - pub right: Vector, - pub up: Vector, - pub _pad0: [u8; 0x64], - pub punchangle: Vector, -} - -#[repr(C)] -#[derive(Copy, Clone)] -pub struct Client { - pub _pad0: [u8; 0xC], - pub hudredraw: extern "C" fn(f32, i32) -> i32, - pub _pad1: [u8; 0x18], // Пропускаем до HUD_Key_Event (индекс 10) - pub hudkeyevent: extern "C" fn(i32, i32, *const c_char) -> i32, - pub _pad2: [u8; 0xC], // До CreateMove (индекс 14) - pub createmove: extern "C" fn(f32, *mut UserCmd, i32), - pub is3rdperson: extern "C" fn() -> i32, - pub cameraoffset: extern "C" fn(*mut Vector), - pub _pad3: [u8; 0x8], - pub calcrefdef: extern "C" fn(*mut RefParams), -} - -pub const FL_ONGROUND: i32 = 1 << 9; - #[repr(C)] pub struct PlayerMove { pub _pad0: [u8; 56], @@ -101,3 +132,17 @@ pub struct PlayerMove { pub _pad2: [u8; 80], pub flags: i32, } + +#[repr(C)] +#[derive(Copy, Clone)] +pub struct Client { + pub _pad0: [u8; 12], + // В Steam Legacy Linux функции принимают скрытый usize первым аргументом + pub hudredraw: unsafe extern "C" fn(usize, f32, i32) -> i32, + pub _pad1: [u8; 24], + pub hudkeyevent: unsafe extern "C" fn(usize, i32, i32, *const c_char) -> i32, + pub _pad2: [u8; 12], + // CreateMove in HLSDK is (frametime, cmd, active). It does NOT take the hidden usize arg. + pub createmove: unsafe extern "C" fn(f32, *mut UserCmd, i32), + pub _pad3: [u8; 32], +}