//! Where does each declared field sit inside the engine's live definition object? //! //! A live object carries no names, so the mapping has to be inferred: take a unit //! whose disc record SETS a field, look for that value in the dumped words of the //! matching live object, and keep the offsets that agree across several units. //! //! Run: live_offsets ... //! e.g. … UN_f106_TCAF_Destroyer=0xbd3ee300 UN_f105_TCAF_Cruiser=0xbd40e200 use std::collections::BTreeMap; use sylpheed_formats::idxd::IdxdObject; use sylpheed_formats::pak::PakArchive; fn main() { let mut a = std::env::args().skip(1); let disc = a.next().expect("disc root"); let dump = a.next().expect("live dump"); let pairs: Vec<(String, String)> = a .filter_map(|s| s.split_once('=').map(|(i, v)| (i.to_string(), v.to_string()))) .collect(); // live: va -> offset -> f32 let mut live: BTreeMap> = BTreeMap::new(); let mut cur = String::new(); for line in std::fs::read_to_string(&dump).expect("dump").lines() { if let Some(rest) = line.strip_prefix("=== ") { cur = rest.trim().to_string(); continue; } let f: Vec<&str> = line.split_whitespace().collect(); if f.len() >= 4 && f[1].starts_with('+') { if let (Ok(off), Ok(val)) = (usize::from_str_radix(&f[1][1..], 16), f[3].parse::()) { live.entry(cur.clone()).or_default().insert(off, val); } } } let pak = PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).expect("pak"); // key -> offset -> how many units agree let mut votes: BTreeMap> = BTreeMap::new(); let mut units = 0usize; for entry in pak.entries() { let Ok(bytes) = pak.read(entry) else { continue }; let Ok(obj) = IdxdObject::parse(&bytes) else { continue }; let Some(id) = obj.get_raw("ID") else { continue }; let Some((_, va)) = pairs.iter().find(|(i, _)| i == id) else { continue }; let Some(words) = live.get(va) else { continue }; units += 1; let mut numeric = 0usize; let mut hit = 0usize; for key in obj.tokens() { let Some(v) = obj.get_f32(key) else { continue }; if !v.is_finite() || v == 0.0 { continue; // zero matches everywhere and says nothing } numeric += 1; let mut found = false; for (off, w) in words { if (*w - v).abs() <= v.abs() * 1e-6 { *votes.entry(key.clone()).or_default().entry(*off).or_default() += 1; found = true; } } if found { hit += 1; } } eprintln!(" {id}: {numeric} numeric fields set on disc, {hit} found in the dumped window"); } println!("{units} units correlated against their live objects\n"); println!("{:<28} {:>8} offsets agreeing (votes)", "field", "unique?"); for (key, offs) in &votes { let best = offs.iter().max_by_key(|(_, n)| **n).unwrap(); if *best.1 < units.max(2) { continue; // needs every correlated unit to agree } let list: Vec = offs .iter() .filter(|(_, n)| **n == *best.1) .map(|(o, n)| format!("+{o:03x}×{n}")) .collect(); println!( "{:<28} {:>8} {}", key, if list.len() == 1 { "unique" } else { "ambig" }, list.join(" ") ); } }