use windows::{
Win32::Foundation::HANDLE,
Win32::System::Diagnostics::Debug::{ReadProcessMemory, WriteProcessMemory},
};
#[derive(Debug)]
pub enum MemoryError {
ReadFailed(String),
WriteFailed(String),
InvalidAddress(String),
}
impl std::fmt::Display for MemoryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MemoryError::ReadFailed(msg) => write!(f, "Failed to read memory: {}", msg),
MemoryError::WriteFailed(msg) => write!(f, "Failed to write memory: {}", msg),
MemoryError::InvalidAddress(msg) => write!(f, "Invalid address: {}", msg),
}
}
}
impl std::error::Error for MemoryError {}
pub fn read_memory_bytes(
handle: HANDLE,
address: usize,
size: usize,
) -> Result<Vec<u8>, MemoryError> {
if address == 0 {
return Err(MemoryError::InvalidAddress(
"Address cannot be zero".to_string(),
));
}
let mut buffer = vec![0u8; size];
let mut bytes_read = 0usize;
unsafe {
match ReadProcessMemory(
handle,
address as *const _,
buffer.as_mut_ptr() as *mut _,
size,
Some(&mut bytes_read),
) {
Ok(_) if bytes_read == size => Ok(buffer),
Ok(_) => Err(MemoryError::ReadFailed(format!(
"Incomplete read: expected {} bytes, got {}",
size, bytes_read
))),
Err(e) => Err(MemoryError::ReadFailed(format!(
"Failed to read {} bytes from address 0x{:X}. Windows error: {:?}",
size, address, e
))),
}
}
}
pub fn write_memory_bytes(handle: HANDLE, address: usize, data: &[u8]) -> Result<(), MemoryError> {
if address == 0 {
return Err(MemoryError::InvalidAddress(
"Address cannot be zero".to_string(),
));
}
let mut bytes_written = 0usize;
unsafe {
match WriteProcessMemory(
handle,
address as *mut _,
data.as_ptr() as *const _,
data.len(),
Some(&mut bytes_written),
) {
Ok(_) if bytes_written == data.len() => Ok(()),
Ok(_) => Err(MemoryError::WriteFailed(format!(
"Incomplete write: expected {} bytes, wrote {}",
data.len(),
bytes_written
))),
Err(e) => Err(MemoryError::WriteFailed(format!(
"Failed to write {} bytes to address 0x{:X}. Windows error: {:?}",
data.len(),
address,
e
))),
}
}
}
pub fn read_memory_t<T: Copy + Sized>(handle: HANDLE, address: usize) -> Result<T, MemoryError> {
let size = std::mem::size_of::<T>();
let bytes = read_memory_bytes(handle, address, size)?;
unsafe {
let ptr = bytes.as_ptr() as *const T;
Ok(ptr.read_unaligned())
}
}
pub fn write_memory_t<T: Copy + Sized>(
handle: HANDLE,
address: usize,
value: T,
) -> Result<(), MemoryError> {
let size = std::mem::size_of::<T>();
let bytes = unsafe { std::slice::from_raw_parts(&value as *const T as *const u8, size) };
write_memory_bytes(handle, address, bytes)
}
pub fn read_memory_i8(handle: HANDLE, address: usize) -> Result<i8, MemoryError> {
read_memory_t(handle, address)
}
pub fn read_memory_u8(handle: HANDLE, address: usize) -> Result<u8, MemoryError> {
read_memory_t(handle, address)
}
pub fn read_memory_i16(handle: HANDLE, address: usize) -> Result<i16, MemoryError> {
read_memory_t(handle, address)
}
pub fn read_memory_u16(handle: HANDLE, address: usize) -> Result<u16, MemoryError> {
read_memory_t(handle, address)
}
pub fn read_memory_i32(handle: HANDLE, address: usize) -> Result<i32, MemoryError> {
read_memory_t(handle, address)
}
pub fn read_memory_u32(handle: HANDLE, address: usize) -> Result<u32, MemoryError> {
read_memory_t(handle, address)
}
pub fn read_memory_i64(handle: HANDLE, address: usize) -> Result<i64, MemoryError> {
read_memory_t(handle, address)
}
pub fn read_memory_u64(handle: HANDLE, address: usize) -> Result<u64, MemoryError> {
read_memory_t(handle, address)
}
pub fn read_memory_f32(handle: HANDLE, address: usize) -> Result<f32, MemoryError> {
read_memory_t(handle, address)
}
pub fn read_memory_f64(handle: HANDLE, address: usize) -> Result<f64, MemoryError> {
read_memory_t(handle, address)
}
pub fn write_memory_i8(handle: HANDLE, address: usize, value: i8) -> Result<(), MemoryError> {
write_memory_t(handle, address, value)
}
pub fn write_memory_u8(handle: HANDLE, address: usize, value: u8) -> Result<(), MemoryError> {
write_memory_t(handle, address, value)
}
pub fn write_memory_i16(handle: HANDLE, address: usize, value: i16) -> Result<(), MemoryError> {
write_memory_t(handle, address, value)
}
pub fn write_memory_u16(handle: HANDLE, address: usize, value: u16) -> Result<(), MemoryError> {
write_memory_t(handle, address, value)
}
pub fn write_memory_i32(handle: HANDLE, address: usize, value: i32) -> Result<(), MemoryError> {
write_memory_t(handle, address, value)
}
pub fn write_memory_u32(handle: HANDLE, address: usize, value: u32) -> Result<(), MemoryError> {
write_memory_t(handle, address, value)
}
pub fn write_memory_i64(handle: HANDLE, address: usize, value: i64) -> Result<(), MemoryError> {
write_memory_t(handle, address, value)
}
pub fn write_memory_u64(handle: HANDLE, address: usize, value: u64) -> Result<(), MemoryError> {
write_memory_t(handle, address, value)
}
pub fn write_memory_f32(handle: HANDLE, address: usize, value: f32) -> Result<(), MemoryError> {
write_memory_t(handle, address, value)
}
pub fn write_memory_f64(handle: HANDLE, address: usize, value: f64) -> Result<(), MemoryError> {
write_memory_t(handle, address, value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zero_address_read() {
let handle = HANDLE(std::ptr::null_mut());
let result = read_memory_bytes(handle, 0, 10);
assert!(result.is_err());
match result {
Err(MemoryError::InvalidAddress(_)) => {}
_ => panic!("Expected InvalidAddress error"),
}
}
#[test]
fn test_zero_address_write() {
let handle = HANDLE(std::ptr::null_mut());
let data = vec![0x90, 0x90];
let result = write_memory_bytes(handle, 0, &data);
assert!(result.is_err());
match result {
Err(MemoryError::InvalidAddress(_)) => {}
_ => panic!("Expected InvalidAddress error"),
}
}
#[test]
fn test_null_handle_read() {
let handle = HANDLE(std::ptr::null_mut());
let result = read_memory_u32(handle, 0x1000);
assert!(result.is_err());
}
#[test]
fn test_null_handle_write() {
let handle = HANDLE(std::ptr::null_mut());
let result = write_memory_u32(handle, 0x1000, 42u32);
assert!(result.is_err());
}
#[test]
fn test_read_memory_t_type_safety() {
let handle = HANDLE(std::ptr::null_mut());
let _: Result<u8, _> = read_memory_t(handle, 0x1000);
let _: Result<i32, _> = read_memory_t(handle, 0x1000);
let _: Result<f64, _> = read_memory_t(handle, 0x1000);
}
}