177 lines
5.7 KiB
Rust
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)
|
|
}
|
|
}
|