//! Check the code-derived offset→field map against the live dump, then read out //! the runtime value of every field the disc record leaves defaulted. //! Run: verify_fieldmap ... use std::collections::BTreeMap; use sylpheed_formats::idxd::IdxdObject; use sylpheed_formats::pak::PakArchive; // Offsets read out of sub_82341A20's own key strings (stfs stores only). const MAP: &[(usize, &str)] = &[ (48, "Size_X"), (52, "Size_Y"), (56, "Size_Z"), (80, "Size_Radius"), (64, "Color_R"), (68, "Color_G"), (72, "Color_B"), (88, "HQRatio"), (92, "ShieldRatio"), (96, "ThrusterRatio"), (116, "ResistanceToOptics"), (120, "ResistanceToShell"), (124, "ResistanceToExplosion"), (128, "ResistanceToPlayer"), (132, "ResistanceParalyze"), (672, "RadarRange"), (676, "FCSRange"), (680, "FiringRange"), (692, "AttackVesselPoint"), (696, "AttackCraftPoint"), (700, "DefencePoint"), ]; fn main() { let mut a = std::env::args().skip(1); let disc = a.next().unwrap(); let dump = a.next().unwrap(); let pairs: Vec<(String, String)> = a.filter_map(|s| s.split_once('=').map(|(i, v)| (i.into(), v.into()))).collect(); let mut live: BTreeMap> = BTreeMap::new(); let mut cur = String::new(); for line in std::fs::read_to_string(&dump).unwrap().lines() { if let Some(r) = line.strip_prefix("=== ") { cur = r.trim().into(); continue; } let f: Vec<&str> = line.split_whitespace().collect(); // dump columns: addr +off hex u32 f32 -- the float is f[4], not f[3] if f.len() >= 5 && f[1].starts_with('+') { if let (Ok(o), Ok(v)) = (usize::from_str_radix(&f[1][1..], 16), f[4].parse::()) { live.entry(cur.clone()).or_default().insert(o, v); } } } let pak = PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).unwrap(); let (mut agree, mut disagree) = (0, 0); let mut defaults: BTreeMap> = BTreeMap::new(); for e in pak.entries() { let Ok(b) = pak.read(e) else { continue }; let Ok(o) = IdxdObject::parse(&b) else { continue }; let Some(id) = o.get_raw("ID") else { continue }; let Some((_, va)) = pairs.iter().find(|(i, _)| i == id) else { continue }; let Some(w) = live.get(va) else { continue }; for (off, key) in MAP { let Some(got) = w.get(off) else { continue }; match o.get_f32(key) { Some(want) => { if (want - got).abs() <= want.abs() * 1e-4 { agree += 1 } else { disagree += 1; println!(" MISMATCH {id} {key}: disc {want}, live +{off} = {got}"); } } None => defaults.entry((*key).into()).or_default().push((id.into(), *got)), } } } println!("\nmap check: {agree} fields agree with the disc, {disagree} disagree\n"); println!("runtime value where the disc record DEFAULTS the field:"); for (key, hits) in &defaults { let vals: Vec = hits.iter().map(|(_, v)| format!("{v}")).collect(); let uniq: std::collections::BTreeSet<&String> = vals.iter().collect(); println!(" {:<22} {:<28} ({} units)", key, uniq.iter().map(|s| s.as_str()).collect::>().join(", "), hits.len()); } }