mod sdk; use sdk::*; use std::ffi::{c_char, c_void}; use std::ffi::CStr; use std::ptr; 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; use glow::HasContext; static mut ORIG_CLIENT: 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, 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, 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); } ORIG_TEAMINFO.unwrap()(dummy, name, size, buf) } 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; } } } ORIG_HEALTH.unwrap()(dummy, name, size, buf) } 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; } ORIG_SCOREATTRIB.unwrap()(dummy, name, size, buf) } 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 }; } ORIG_RADAR.unwrap()(dummy, name, size, buf) } 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; // Keep pos as last known? Or clear it? // Better clear it to avoid "ghost" ESP. PLAYER_DATA[i].pos = Vector { x: 0.0, y: 0.0, z: 0.0 }; } ORIG_RESETHUD.unwrap()(dummy, name, size, buf) } fn log(msg: &str) { if let Ok(mut file) = OpenOptions::new().create(true).append(true).open("/tmp/basturma.log") { let _ = writeln!(file, "[DEBUG] {}", msg); } } 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("") } // -------- 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; } 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; } 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; } else { // Try to extract from CL_ShutDownUsrMessages signature: // mov clientusermsgs, eax (or similar) // This is very architecture/build dependent. CLIENT_USERMSGS = *( (shutdownusermsgs as *mut u8).offset(5) as *mut *mut UserMsg ); } } 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."); } }); } extern "C" fn hooked_hudredraw(dummy: usize, time: f32, intermission: i32) -> i32 { unsafe { // 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"); } } } // Keep mouse uncaptured while the menu is open (the game may try to re-enable it). if SETTINGS.show_menu { 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.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_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); } }