Files
basturma/src/lib.rs
2026-05-15 21:14:16 +03:00

1505 lines
51 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<Client> = None;
static mut HOOKS_APPLIED: bool = false;
static mut ORIG_CREATEMOVE: Option<unsafe extern "C" fn(f32, *mut UserCmd, i32)> = 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<unsafe extern "C" fn(usize, *const c_char, i32, *mut c_void) -> i32> = None;
static mut ORIG_HEALTH: Option<unsafe extern "C" fn(usize, *const c_char, i32, *mut c_void) -> i32> = None;
static mut ORIG_SCOREATTRIB: Option<unsafe extern "C" fn(usize, *const c_char, i32, *mut c_void) -> i32> = None;
static mut ORIG_RADAR: Option<unsafe extern "C" fn(usize, *const c_char, i32, *mut c_void) -> i32> = None;
static mut ORIG_RESETHUD: Option<unsafe extern "C" fn(usize, *const c_char, i32, *mut c_void) -> 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<Option<GetPlayerInfoFn>> = 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<i32> {
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::<EntityState>().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 "<null>";
}
unsafe { CStr::from_ptr(ptr) }.to_str().unwrap_or("<non-utf8>")
}
// -------- 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<String> {
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<unsafe extern "C" fn(i32) -> i32>,
sdl_show_cursor: Option<unsafe extern "C" fn(i32) -> i32>,
sdl_capture_mouse: Option<unsafe extern "C" fn(i32) -> i32>,
sdl_get_mouse_focus: Option<unsafe extern "C" fn() -> *mut c_void>,
sdl_set_window_grab: Option<unsafe extern "C" fn(*mut c_void, i32)>,
}
static SDL2_API: OnceLock<Option<Sdl2Api>> = OnceLock::new();
fn sdl2_init() -> Option<Sdl2Api> {
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<bool> {
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<bool> {
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<Sdl2Mouse> {
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<unsafe extern "C" fn() -> i32>,
x_lock_display: Option<unsafe extern "C" fn(*mut Display)>,
x_unlock_display: Option<unsafe extern "C" fn(*mut Display)>,
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<Option<X11State>> = OnceLock::new();
fn x11_init() -> Option<X11State> {
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<bool> {
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<X11Mouse> {
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<Overlay> = None;
fn get_gl_loader() -> Option<GlXGetProcAddressARB> {
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<Overlay> {
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);
}
}