use std::fmt;
use windows::Win32::Foundation::HANDLE;
use crate::memory::MemoryError;
#[derive(Debug, Clone)]
pub enum ParseError {
EmptyInput,
InvalidHex(String),
InvalidDecimal(String),
InvalidOffset(String),
InvalidModuleFormat(String),
InvalidPointerSyntax(String),
UnexpectedCharacter(char),
MultipleModules,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::EmptyInput => write!(f, "Empty address string"),
ParseError::InvalidHex(s) => write!(f, "Invalid hexadecimal: {}", s),
ParseError::InvalidDecimal(s) => write!(f, "Invalid decimal: {}", s),
ParseError::InvalidOffset(s) => write!(f, "Invalid offset: {}", s),
ParseError::InvalidModuleFormat(s) => write!(f, "Invalid module format: {}", s),
ParseError::InvalidPointerSyntax(s) => write!(f, "Invalid pointer syntax: {}", s),
ParseError::UnexpectedCharacter(ch) => write!(f, "Unexpected character: '{}'", ch),
ParseError::MultipleModules => write!(f, "Multiple module names not allowed"),
}
}
}
impl std::error::Error for ParseError {}
#[derive(Debug)]
pub enum ResolveError {
ModuleNotFound(String),
PointerReadFailed(usize, MemoryError),
NullPointer(usize),
}
impl fmt::Display for ResolveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ResolveError::ModuleNotFound(name) => {
write!(f, "Module '{}' not found in target process", name)
},
ResolveError::PointerReadFailed(addr, e) => {
write!(f, "Failed to read pointer at 0x{:X}: {}", addr, e)
},
ResolveError::NullPointer(addr) => {
write!(f, "Null pointer encountered at 0x{:X}", addr)
}
}
}
}
impl std::error::Error for ResolveError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PointerSize {
Bits32,
Bits64,
}
impl PointerSize {
pub fn bytes(&self) -> usize {
match self {
PointerSize::Bits32 => 4,
PointerSize::Bits64 => 8,
}
}
pub fn default_architecture() -> Self {
#[cfg(target_pointer_width = "64")]
return PointerSize::Bits64;
#[cfg(target_pointer_width = "32")]
return PointerSize::Bits32;
}
}
impl Default for PointerSize {
fn default() -> Self {
Self::default_architecture()
}
}
#[derive(Debug, Clone)]
pub struct MemoryAddress {
pub base: AddressBase,
pub operations: Vec<AddressOp>,
pub pointer_size: PointerSize,
}
#[derive(Debug, Clone)]
pub enum AddressBase {
Absolute(usize),
Module { name: String, offset: usize },
}
#[derive(Debug, Clone)]
pub enum AddressOp {
DirectOffset(usize),
PointerJump(usize),
}
impl MemoryAddress {
pub fn builder() -> crate::memory_resolver::builder::MemoryAddressBuilder {
crate::memory_resolver::builder::MemoryAddressBuilder::new()
}
pub fn parse(input: &str) -> Result<Self, ParseError> {
let mut chars = input.chars().peekable();
let mut base = None;
let mut operations = Vec::new();
if input.starts_with("0x") || input.starts_with("0X") {
chars.next(); chars.next();
let address = Self::parse_number(&mut chars)?;
base = Some(AddressBase::Absolute(address));
}
else if let Some(&first_ch) = chars.peek() {
if first_ch.is_ascii_digit() || first_ch == '#' {
let address = Self::parse_number(&mut chars)?;
base = Some(AddressBase::Absolute(address));
}
}
while let Some(&ch) = chars.peek() {
match ch {
'-' => {
chars.next(); if chars.next() != Some('>') {
return Err(ParseError::InvalidPointerSyntax("Expected '>' after '-'".to_string()));
}
let offset = Self::parse_number(&mut chars)?;
operations.push(AddressOp::PointerJump(offset));
},
'+' => {
chars.next();
let next_chars: String = chars.clone().take(2).collect();
if next_chars == "0x" || next_chars == "0X" {
chars.next(); chars.next(); }
let offset = Self::parse_number(&mut chars)?;
operations.push(AddressOp::DirectOffset(offset));
},
_ if ch.is_alphabetic() || ch == '_' || ch == '.' => {
if base.is_none() {
base = Some(Self::parse_module_name(&mut chars)?);
} else {
return Err(ParseError::MultipleModules);
}
},
_ => {
return Err(ParseError::UnexpectedCharacter(ch));
}
}
}
Ok(MemoryAddress {
base: base.ok_or(ParseError::EmptyInput)?,
operations,
pointer_size: PointerSize::default_architecture(),
})
}
pub fn new_x86(input: &str) -> Result<Self, ParseError> {
let mut addr = Self::parse(input)?;
addr.pointer_size = PointerSize::Bits32;
Ok(addr)
}
pub fn new_x64(input: &str) -> Result<Self, ParseError> {
let mut addr = Self::parse(input)?;
addr.pointer_size = PointerSize::Bits64;
Ok(addr)
}
fn parse_number(chars: &mut std::iter::Peekable<std::str::Chars>) -> Result<usize, ParseError> {
let mut num_str = String::new();
let mut is_decimal = false;
if let Some(&'#') = chars.peek() {
chars.next();
is_decimal = true;
}
let next_two: String = chars.clone().take(2).collect();
if next_two == "0x" || next_two == "0X" {
chars.next(); chars.next(); }
while let Some(&ch) = chars.peek() {
if ch.is_ascii_hexdigit() || ch == '_' {
num_str.push(ch);
chars.next();
} else {
break;
}
}
if num_str.is_empty() {
return Err(ParseError::InvalidOffset("Empty number".to_string()));
}
num_str.retain(|c| c != '_');
if is_decimal {
usize::from_str_radix(&num_str, 10)
.map_err(|_| ParseError::InvalidDecimal(num_str))
} else {
usize::from_str_radix(&num_str, 16)
.map_err(|_| ParseError::InvalidHex(num_str))
}
}
fn parse_module_name(chars: &mut std::iter::Peekable<std::str::Chars>) -> Result<AddressBase, ParseError> {
let mut name = String::new();
while let Some(&ch) = chars.peek() {
if ch.is_alphanumeric() || ch == '_' || ch == '.' || ch == '-' {
name.push(ch);
chars.next();
} else {
break;
}
}
if !name.contains(".dll") && !name.contains(".exe") {
return Err(ParseError::InvalidModuleFormat(name));
}
if let Some(&'+') = chars.peek() {
chars.next();
let next_chars: String = chars.clone().take(2).collect();
if next_chars == "0x" || next_chars == "0X" {
chars.next(); chars.next(); }
let offset = Self::parse_number(chars)?;
Ok(AddressBase::Module { name, offset })
} else {
Ok(AddressBase::Module { name, offset: 0 })
}
}
pub fn with_pointer_size(mut self, size: PointerSize) -> Self {
self.pointer_size = size;
self
}
pub fn x86(self) -> Self {
self.with_pointer_size(PointerSize::Bits32)
}
pub fn x64(self) -> Self {
self.with_pointer_size(PointerSize::Bits64)
}
pub fn resolve_address(&self, handle: HANDLE, pid: u32) -> Result<usize, ResolveError> {
let mut current = match &self.base {
AddressBase::Absolute(address) => *address,
AddressBase::Module { name, offset } => {
let base = crate::snapshot::get_module_base_address(pid, name)
.ok_or_else(|| ResolveError::ModuleNotFound(name.clone()))?;
base + offset
}
};
for op in &self.operations {
match op {
AddressOp::DirectOffset(offset) => {
current += offset;
}
AddressOp::PointerJump(offset) => {
let ptr_value = match self.pointer_size {
PointerSize::Bits32 => {
crate::memory::read_memory_u32(handle, current)
.map_err(|e| ResolveError::PointerReadFailed(current, e))? as u64
}
PointerSize::Bits64 => {
crate::memory::read_memory_u64(handle, current)
.map_err(|e| ResolveError::PointerReadFailed(current, e))?
}
};
if ptr_value == 0 {
return Err(ResolveError::NullPointer(current));
}
current = ptr_value as usize + offset;
}
}
}
Ok(current)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_absolute_hex_address() {
let addr = MemoryAddress::parse("0x1234").unwrap();
assert!(matches!(addr.base, AddressBase::Absolute(0x1234)));
assert!(addr.operations.is_empty());
}
#[test]
fn test_parse_absolute_hex_without_prefix() {
let addr = MemoryAddress::parse("1234").unwrap();
assert!(matches!(addr.base, AddressBase::Absolute(0x1234)));
}
#[test]
fn test_parse_decimal_address() {
let addr = MemoryAddress::parse("#100").unwrap();
assert!(matches!(addr.base, AddressBase::Absolute(100)));
}
#[test]
fn test_parse_module_address() {
let addr = MemoryAddress::parse("target.exe+1000").unwrap();
match addr.base {
AddressBase::Module { name, offset } => {
assert_eq!(name, "target.exe");
assert_eq!(offset, 0x1000);
}
_ => panic!("Expected Module base"),
}
}
#[test]
fn test_parse_pointer_chain() {
let addr = MemoryAddress::parse("target.exe+100->20->30").unwrap();
assert_eq!(addr.operations.len(), 2);
assert!(matches!(addr.operations[0], AddressOp::PointerJump(0x20)));
assert!(matches!(addr.operations[1], AddressOp::PointerJump(0x30)));
}
#[test]
fn test_parse_direct_offset() {
let addr = MemoryAddress::parse("0x1000+100").unwrap();
assert_eq!(addr.operations.len(), 1);
assert!(matches!(addr.operations[0], AddressOp::DirectOffset(0x100)));
}
#[test]
fn test_parse_with_underscore() {
let addr = MemoryAddress::parse("1_0000").unwrap();
assert!(matches!(addr.base, AddressBase::Absolute(0x10000)));
}
#[test]
fn test_parse_invalid_format() {
assert!(MemoryAddress::parse("").is_err());
assert!(MemoryAddress::parse("invalid").is_err());
}
#[test]
fn test_parse_mixed_operations() {
let addr = MemoryAddress::parse("target.exe+100->20+30").unwrap();
assert_eq!(addr.operations.len(), 2);
assert!(matches!(addr.operations[0], AddressOp::PointerJump(0x20)));
assert!(matches!(addr.operations[1], AddressOp::DirectOffset(0x30)));
}
#[test]
fn test_new_x86_shortcut() {
let addr = MemoryAddress::new_x86("lf2.exe+58C94->308").unwrap();
assert_eq!(addr.pointer_size, PointerSize::Bits32);
match addr.base {
AddressBase::Module { name, offset } => {
assert_eq!(name, "lf2.exe");
assert_eq!(offset, 0x58C94);
}
_ => panic!("Expected Module base"),
}
}
#[test]
fn test_new_x64_shortcut() {
let addr = MemoryAddress::new_x64("game.exe+1000->20").unwrap();
assert_eq!(addr.pointer_size, PointerSize::Bits64);
match addr.base {
AddressBase::Module { name, offset } => {
assert_eq!(name, "game.exe");
assert_eq!(offset, 0x1000);
}
_ => panic!("Expected Module base"),
}
}
#[test]
fn test_parse_default_architecture() {
let addr = MemoryAddress::parse("test.exe+100").unwrap();
#[cfg(target_pointer_width = "64")]
assert_eq!(addr.pointer_size, PointerSize::Bits64);
#[cfg(target_pointer_width = "32")]
assert_eq!(addr.pointer_size, PointerSize::Bits32);
}
#[test]
fn test_resolve_null_pointer_error() {
let addr = MemoryAddress::parse("0x1000->10").unwrap();
let handle = HANDLE(std::ptr::null_mut());
let result = addr.resolve_address(handle, 0);
assert!(result.is_err());
}
}