use crate::memory::{self, Bl4Process, MemorySource};
use anyhow::{bail, Context, Result};
fn parse_address(address: &str) -> Result<usize> {
if address.starts_with("0x") || address.starts_with("0X") {
usize::from_str_radix(&address[2..], 16).context("Invalid hex address")
} else {
address.parse::<usize>().context("Invalid address")
}
}
fn parse_hex_bytes(bytes: &str) -> Result<Vec<u8>> {
let parts: Vec<&str> = bytes.split_whitespace().collect();
let mut data = Vec::new();
for part in parts {
let byte =
u8::from_str_radix(part, 16).with_context(|| format!("Invalid hex byte: {}", part))?;
data.push(byte);
}
Ok(data)
}
pub fn handle_read(source: &dyn MemorySource, address: &str, size: usize) -> Result<()> {
let addr = parse_address(address)?;
let data = source.read_bytes(addr, size)?;
println!("Reading {} bytes at {:#x}:", size, addr);
for (i, chunk) in data.chunks(16).enumerate() {
print!("{:08x} ", addr + i * 16);
for (j, byte) in chunk.iter().enumerate() {
print!("{:02x} ", byte);
if j == 7 {
print!(" ");
}
}
if chunk.len() < 16 {
for j in chunk.len()..16 {
print!(" ");
if j == 7 {
print!(" ");
}
}
}
print!(" |");
for byte in chunk {
let c = *byte as char;
if c.is_ascii_graphic() || c == ' ' {
print!("{}", c);
} else {
print!(".");
}
}
println!("|");
}
Ok(())
}
pub fn handle_write(proc: &Bl4Process, address: &str, bytes: &str) -> Result<()> {
let addr = parse_address(address)?;
let data = parse_hex_bytes(bytes)?;
println!("Writing {} bytes to {:#x}:", data.len(), addr);
print!(" ");
for byte in &data {
print!("{:02x} ", byte);
}
println!();
let original = proc.read_bytes_direct(addr, data.len())?;
print!("Original: ");
for byte in &original {
print!("{:02x} ", byte);
}
println!();
proc.write_bytes(addr, &data)?;
println!("Write successful!");
Ok(())
}
pub fn handle_scan(source: &dyn MemorySource, pattern: &str) -> Result<()> {
let parts: Vec<&str> = pattern.split_whitespace().collect();
let mut bytes = Vec::new();
let mut mask = Vec::new();
for part in parts {
if part == "??" || part == "?" {
bytes.push(0u8);
mask.push(0u8); } else {
let byte = u8::from_str_radix(part, 16)
.with_context(|| format!("Invalid hex byte: {}", part))?;
bytes.push(byte);
mask.push(1u8); }
}
println!("Scanning for pattern: {}", pattern);
println!("This may take a while...");
let results = memory::scan_pattern(source, &bytes, &mask)?;
if results.is_empty() {
println!("No matches found.");
} else {
println!("Found {} matches:", results.len());
for (i, addr) in results.iter().take(20).enumerate() {
println!(" {}: {:#x}", i + 1, addr);
}
if results.len() > 20 {
println!(" ... and {} more", results.len() - 20);
}
}
Ok(())
}
pub fn handle_patch(
proc: &Bl4Process,
address: &str,
nop: Option<usize>,
bytes: Option<&str>,
) -> Result<()> {
let addr = parse_address(address)?;
let patch_bytes = if let Some(nop_count) = nop {
vec![0x90u8; nop_count]
} else if let Some(hex_bytes) = bytes {
parse_hex_bytes(hex_bytes)?
} else {
bail!("Must specify either --nop <count> or --bytes <hex>");
};
let original = proc.read_bytes_direct(addr, patch_bytes.len())?;
println!("Patching {} bytes at {:#x}", patch_bytes.len(), addr);
print!("Original: ");
for byte in &original {
print!("{:02x} ", byte);
}
println!();
print!("New: ");
for byte in &patch_bytes {
print!("{:02x} ", byte);
}
println!();
proc.write_bytes(addr, &patch_bytes)?;
println!("Patch applied!");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::memory::source::tests::MockMemorySource;
#[test]
fn test_parse_address_hex() {
assert_eq!(parse_address("0x1000").unwrap(), 0x1000);
assert_eq!(parse_address("0X1000").unwrap(), 0x1000);
assert_eq!(parse_address("0xDEADBEEF").unwrap(), 0xDEADBEEF);
}
#[test]
fn test_parse_address_decimal() {
assert_eq!(parse_address("4096").unwrap(), 4096);
assert_eq!(parse_address("0").unwrap(), 0);
}
#[test]
fn test_parse_address_invalid() {
assert!(parse_address("0xGGGG").is_err());
assert!(parse_address("not_a_number").is_err());
}
#[test]
fn test_parse_hex_bytes() {
let result = parse_hex_bytes("48 8B 05").unwrap();
assert_eq!(result, vec![0x48, 0x8B, 0x05]);
}
#[test]
fn test_parse_hex_bytes_single() {
let result = parse_hex_bytes("90").unwrap();
assert_eq!(result, vec![0x90]);
}
#[test]
fn test_parse_hex_bytes_invalid() {
assert!(parse_hex_bytes("GG").is_err());
assert!(parse_hex_bytes("48 GG 05").is_err());
}
#[test]
fn test_handle_read() {
let data = vec![0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00];
let source = MockMemorySource::new(data, 0x1000);
let result = handle_read(&source, "0x1000", 7);
assert!(result.is_ok());
}
#[test]
fn test_handle_read_invalid_address() {
let source = MockMemorySource::new(vec![], 0x1000);
let result = handle_read(&source, "invalid", 16);
assert!(result.is_err());
}
#[test]
fn test_handle_scan_pattern_parsing() {
let source = MockMemorySource::new(vec![], 0x1000);
let result = handle_scan(&source, "48 8B ?? ??");
assert!(result.is_ok());
}
}