use std::sync::Arc; use anyhow::{Context, Result}; use druid::Data; use sysinfo::{PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt}; use vmemory::ProcessMemory; #[derive(Clone, Data)] pub struct GameState { pub location: u32, pub gil: u32, pub steps: u32, pub stage: u16, pub traveler_steps: u16, pub chain: u16, pid: u32, #[data(ignore)] base: usize, #[data(ignore)] base_size: usize, #[data(ignore)] mem: Arc>, #[data(ignore)] proc_mem: Arc, #[data(ignore)] data_pattern: Vec, #[data(ignore)] data_addr: usize, #[data(ignore)] traveler_pattern: Vec, #[data(ignore)] traveler_addr: usize, #[data(ignore)] chain_pattern: Vec, #[data(ignore)] chain_addr: usize, } macro_rules! replace { ($binding: expr, $value: expr) => {{ let same = *$binding == $value; *$binding = $value; same }}; } impl GameState { const LOCATION_OFFSET: isize = -0x90; const GIL_OFFSET: isize = 0x8; const STEPS_OFFSET: isize = 0xC; const STAGE_OFFSET: isize = 0x200; const TRAVELER_OFFSET: isize = 0x5B00; pub fn new() -> Result { let sys = System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::new())); let proc = sys.processes() .iter() .find(|(_, proc)| proc.name() == "FFXII_TZA.exe"); let (pid, _) = match proc { Some(p) => p, None => anyhow::bail!("could not find TZA process") }; let mem = ProcessMemory::attach_process(pid.as_u32()).unwrap(); let mut base = 0; let mut base_size = 0; process_list::for_each_module(pid.as_u32(), |(address, size), name| { let stem = name.file_stem().unwrap_or_default(); if stem == "FFXII_TZA" { base = address; base_size = size; } }).context("could not loop TZA modules")?; if base == 0 || base_size == 0 { anyhow::bail!("could not find TZA base address"); } let tza_mem = mem.read_memory(base, base_size, false); let data_pattern = crate::util::parse_pattern("48 8D 0D ?? ?? ?? ?? 41 B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 33 C9 E8").unwrap(); let traveler_pattern = crate::util::parse_pattern("48 8B 05 ?? ?? ?? ?? 48 85 C0 75 28 E8").unwrap(); let chain_pattern = crate::util::parse_pattern("89 05 ?? ?? ?? ?? 89 1D ?? ?? ?? ?? 48 8B 87").unwrap(); let mut state = Self { location: 0, gil: 0, steps: 0, stage: 0, traveler_steps: 0, chain: 0, pid: pid.as_u32(), base, base_size, mem: Arc::new(tza_mem), proc_mem: Arc::new(mem), data_pattern, data_addr: 0, traveler_pattern, traveler_addr: 0, chain_pattern, chain_addr: 0, }; state.set_addresses()?; Ok(state) } fn find_address_indirect(&self, pattern: &[u8]) -> Result { let ptr_ptr = match crate::util::find_pattern(&self.mem, pattern) { Some(ptr) => ptr, None => anyhow::bail!("could not find pointer"), }; let ptr_offset = match crate::util::get_static_address(&self.mem, ptr_ptr, self.base) { Some(addr) => addr, None => anyhow::bail!("could not find pointer offset"), }; let ptr_vec = self.proc_mem.read_memory(ptr_offset, 8, true); let ptr_array: [u8; 8] = ptr_vec.try_into().unwrap(); Ok(u64::from_le_bytes(ptr_array) as usize - self.base) } fn find_address(&self, pattern: &[u8]) -> Result { let ptr = match crate::util::find_pattern(&self.mem, pattern) { Some(ptr) => ptr, None => anyhow::bail!("could not find location pointer"), }; let ptr_offset = match crate::util::get_static_address(&self.mem, ptr, self.base) { Some(addr) => addr, None => anyhow::bail!("could not find location pointer offset"), }; Ok(ptr_offset) } fn set_addresses(&mut self) -> Result<()> { self.data_addr = self.find_address(&self.data_pattern)?; self.traveler_addr = self.find_address_indirect(&self.traveler_pattern)?; self.chain_addr = self.find_address(&self.chain_pattern)?; Ok(()) } fn read_data(&self, addr: usize, offset: isize) -> [u8; SIZE] { let addr = addr as isize + offset; let vec = self.proc_mem.read_memory(addr as usize, SIZE, true); vec.try_into().unwrap() } fn replace(binding: &mut T, value: T) -> bool { let same = *binding == value; *binding = value; same } pub fn refresh(&mut self) -> Result { // boolean or short-circuits let any_changed = replace!(&mut self.location, u32::from_le_bytes(self.read_data(self.data_addr, Self::LOCATION_OFFSET))) | replace!(&mut self.gil, u32::from_le_bytes(self.read_data(self.data_addr, Self::GIL_OFFSET))) | replace!(&mut self.steps, u32::from_le_bytes(self.read_data(self.data_addr, Self::STEPS_OFFSET))) | replace!(&mut self.stage, u16::from_le_bytes(self.read_data(self.data_addr, Self::STAGE_OFFSET))) | replace!(&mut self.traveler_steps, u16::from_le_bytes(self.read_data(self.traveler_addr, Self::TRAVELER_OFFSET))) | replace!(&mut self.chain, u16::from_le_bytes(self.read_data(self.chain_addr, 0))); Ok(any_changed) } pub fn location_name(&self) -> &'static str { crate::util::location_name(self.location) } }