ffxii-tza-auto-notes/src/game_state.rs

177 lines
5.7 KiB
Rust

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<Vec<u8>>,
#[data(ignore)]
proc_mem: Arc<ProcessMemory>,
#[data(ignore)]
data_pattern: Vec<u8>,
#[data(ignore)]
data_addr: usize,
#[data(ignore)]
traveler_pattern: Vec<u8>,
#[data(ignore)]
traveler_addr: usize,
#[data(ignore)]
chain_pattern: Vec<u8>,
#[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<Self> {
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<usize> {
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<usize> {
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<const SIZE: usize>(&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<T: PartialEq>(binding: &mut T, value: T) -> bool {
let same = *binding == value;
*binding = value;
same
}
pub fn refresh(&mut self) -> Result<bool> {
// 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)
}
}