memory-tool-4-cheat 0.2.0

A memory tool for cheat
Documentation
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; // Assuming standard page size

#[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> {
        //get the pid
        
        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(); // 获取带 \0 的字节切片

        // 确保目标缓冲区足够大(包括 \0)
        if bytes.len() > args.name.len() {
            panic!("Source string too large for destination buffer");
        }

        // 逐个字节拷贝(转换为 c_char)
        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(); // 获取带 \0 的字节切片

        // 确保目标缓冲区足够大(包括 \0)
        if bytes.len() > args.name.len() {
            panic!("Source string too large for destination buffer");
        }

        // 逐个字节拷贝(转换为 c_char)
        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);

                // 尝试从输出中解析 PID
                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.")),
    }
}