mod arch;
mod perf;
use libc::{c_void, size_t};
use perf::PerfMap;
use std::os::raw::c_char;
use std::ptr::copy_nonoverlapping;
pub const GET_MODULE_BASE_MAX_MODULE_NAME_LEN: usize = 100 + 1;
pub const OP_MEMORY_MAX_OFFSETS_COUNT: usize = 10;
pub const PAGE_SIZE: usize = 4096;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct OpMemoryArgs {
pub addr: u64,
pub buffer: *mut c_void,
pub size: size_t,
pub offsets_count: size_t,
pub offsets: [u64; OP_MEMORY_MAX_OFFSETS_COUNT],
}
impl OpMemoryArgs {
fn set_offsets(&mut self, off: &[u64]) {
unsafe {
copy_nonoverlapping(off.as_ptr(), self.offsets.as_mut_ptr(), off.len());
}
}
}
pub const MIN_MMAP_SIZE: usize = std::mem::size_of::<OpMemoryArgs>();
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct GetModuleArgs {
pub name: [c_char; GET_MODULE_BASE_MAX_MODULE_NAME_LEN],
pub result: u64,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct GetTidArgs {
pub name: [c_char; GET_MODULE_BASE_MAX_MODULE_NAME_LEN],
pub result: i32,
}
#[repr(C)]
pub union OpArgs {
pub op_mem_args: OpMemoryArgs,
pub get_module_args: GetModuleArgs,
pub get_tid_args:GetTidArgs,
}
#[repr(u32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum CheatOperations {
OpReadMemory = 0x901,
OpWriteMemory = 0x902,
OpGetModuleBase = 0x903,
OpStartGuard = 0x904,
OpStopGuard = 0x905,
OpGetTid = 0x906,
}
#[repr(C)]
pub struct OpT {
pub op_type: CheatOperations,
pub args: OpArgs,
}
impl OpT {
fn get_op_mem_args(&mut self) -> &mut OpMemoryArgs {
unsafe { &mut self.args.op_mem_args }
}
fn get_module_args(&mut self) -> &mut GetModuleArgs {
unsafe { &mut self.args.get_module_args }
}
fn get_tid_args(&mut self)->&mut GetTidArgs{
unsafe { &mut self.args.get_tid_args }
}
}
pub const OP_ARGS_MIN_BUFFER_PAGE: usize = {
let size = std::mem::size_of::<OpT>();
if size % PAGE_SIZE == 0 {
size / PAGE_SIZE
} else {
size / PAGE_SIZE + 1
}
};
pub const OP_ARGS_MIN_BUFFER_LEN: usize = OP_ARGS_MIN_BUFFER_PAGE * PAGE_SIZE;
const CMD_GET_CHEAT_HANDLE: u64 = 14;
pub struct GameMem {
fd: i32,
mmap_priv: *mut c_void,
additional_offset: u64,
is_additional_offset_negative: bool,
need_additional_offset: bool,
}
impl GameMem {
pub fn init_via_pid(pid:i32)->Result<Self, &'static str>{
Self::new(pid)
}
pub fn probe<F: FnMut(perf::SampleData)->Option<u64>>(&self,tid:i32,addr:u64,condition:F)->Option<u64>{
self.start_guard();
let perf = PerfMap::new(perf_event_open_sys::bindings::HW_BREAKPOINT_X, addr, 8, tid, 4, false).unwrap();
let res = perf.events(condition);
perf.close();
self.stop_guard();
res
}
pub fn init_via_process_name(process_name: &str)->Result<Self, &'static str>{
let pid = loop {
let pid: i32=
match get_name_pid(process_name) {
Ok(pid) => {
pid
}
Err(msg) => {
println!("{}", msg);
std::thread::sleep(Duration::from_secs(3));
0
}
};
if pid == 0 {
continue;
} else {
break pid;
}
};
Self::new(pid)
}
fn new(pid:i32) -> Result<Self, &'static str> {
let mut fd = -1;
unsafe {
libc::prctl(0xdeadbeefu32 as i32, CMD_GET_CHEAT_HANDLE, &pid, 0,&mut fd);
}
if fd == -1 {
return Err("open driver failed!");
}
let mmaped = unsafe {
libc::mmap(
std::ptr::null_mut(),
OP_ARGS_MIN_BUFFER_LEN,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd,
0,
)
};
if mmaped.is_null() {
return Err("mmap failed!");
}
Ok(Self {
fd,
mmap_priv: mmaped,
additional_offset: 0,
is_additional_offset_negative: false,
need_additional_offset: false,
})
}
fn get_op_t(&self) -> *mut OpT {
self.mmap_priv as *mut OpT
}
fn blocking_request(&self) -> bool {
unsafe { libc::fdatasync(self.fd) == 0 }
}
fn start_guard(&self)->bool{
let op_t = self.get_op_t();
let op_t = unsafe{
op_t.as_mut().unwrap()
};
op_t.op_type = CheatOperations::OpStartGuard;
self.blocking_request()
}
pub fn get_tid_by_name(&self,name:&str)->Option<i32>{
let op_t = self.get_op_t();
let op_t = unsafe{
op_t.as_mut().unwrap()
};
op_t.op_type = CheatOperations::OpGetTid;
let args = op_t.get_tid_args();
let c_name = std::ffi::CString::new(name).unwrap();
let bytes = c_name.as_bytes_with_nul();
if bytes.len() > args.name.len() {
panic!("Source string too large for destination buffer");
}
for (i, &byte) in bytes.iter().enumerate() {
args.name[i] = byte as c_char;
}
if self.blocking_request() {
Some(args.result)
} else {
None
}
}
fn stop_guard(&self)->bool{
let op_t = self.get_op_t();
let op_t = unsafe{
op_t.as_mut().unwrap()
};
op_t.op_type = CheatOperations::OpStopGuard;
self.blocking_request()
}
pub fn read_memory_with_offsets<T: Default>(
&self,
addr: u64,
buffer: *mut T,
offsets: &[u64],
) -> bool {
let op_t = self.get_op_t();
let op_t = unsafe{
op_t.as_mut().unwrap()
};
op_t.op_type = CheatOperations::OpReadMemory;
let args = op_t.get_op_mem_args();
args.addr = addr;
args.buffer = buffer as _;
args.offsets_count = offsets.len();
args.size = size_of::<T>();
args.set_offsets(offsets);
if self.need_additional_offset {
if self.is_additional_offset_negative {
args.offsets[args.offsets_count - 1] -= self.additional_offset;
} else {
args.offsets[args.offsets_count - 1] += self.additional_offset;
}
}
if self.blocking_request() {
return true;
}
false
}
pub fn read_memory_with_length_and_offsets(
&self,
addr: u64,
buffer: *mut libc::c_void,
length: usize,
offsets: &[u64],
) -> bool {
let op_t = self.get_op_t();
let op_t = unsafe{
op_t.as_mut().unwrap()
};
op_t.op_type = CheatOperations::OpReadMemory;
let args = op_t.get_op_mem_args();
args.addr = addr;
args.buffer = buffer as _;
args.offsets_count = offsets.len();
args.size = length;
args.set_offsets(offsets);
if self.need_additional_offset {
if self.is_additional_offset_negative {
args.offsets[args.offsets_count - 1] -= self.additional_offset;
} else {
args.offsets[args.offsets_count - 1] += self.additional_offset;
}
}
if self.blocking_request() {
return true;
}
false
}
pub fn get_module_base(&self, name: &str) -> Option<u64> {
let op_t = self.get_op_t();
let op_t = unsafe{
op_t.as_mut().unwrap()
};
op_t.op_type = CheatOperations::OpGetModuleBase;
let args = op_t.get_module_args();
let c_name = std::ffi::CString::new(name).unwrap();
let bytes = c_name.as_bytes_with_nul();
if bytes.len() > args.name.len() {
panic!("Source string too large for destination buffer");
}
for (i, &byte) in bytes.iter().enumerate() {
args.name[i] = byte as c_char;
}
if self.blocking_request() {
Some(args.result)
} else {
None
}
}
pub fn read_with_offsets<T: Default>(&mut self, addr: u64, offsets: &[u64]) -> T {
let mut res = T::default();
if self.read_memory_with_offsets(addr, &mut res, offsets) {
return res;
}
Default::default()
}
pub fn set_additional_offset(&mut self, offset: u64, negative: bool) {
self.additional_offset = offset;
self.is_additional_offset_negative = negative;
self.need_additional_offset = true;
}
pub fn un_set_additional_offset(&mut self) {
self.need_additional_offset = false;
}
}
#[allow(unused_imports)]
use std::process::Command;
use std::time::Duration;
fn get_name_pid(name: &str) -> Result<i32, String> {
let cmd = format!("pidof {}", name);
let output = Command::new("sh").arg("-c").arg(cmd).output();
match output {
Ok(output) => {
if !output.status.success() {
Err(format!("pidof {name} command exec failed."))
} else {
let stdout = String::from_utf8(output.stdout);
let pid = stdout.unwrap_or_default().trim().parse::<i32>().ok();
if let Some(pid) = pid {
Ok(pid)
} else {
Err("cannot find process,waiting for opening game".to_string())
}
}
}
_ => Err(format!("pidof {name} command exec failed.")),
}
}