diff --git a/crates/sylpheed-formats/examples/unit_values.rs b/crates/sylpheed-formats/examples/unit_values.rs new file mode 100644 index 0000000..83af466 --- /dev/null +++ b/crates/sylpheed-formats/examples/unit_values.rs @@ -0,0 +1,22 @@ +use sylpheed_formats::idxd::IdxdObject; +use sylpheed_formats::pak::PakArchive; +fn main() { + let disc = std::env::args().nth(1).unwrap(); + let want = std::env::args().nth(2).unwrap(); + let pak = PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).unwrap(); + 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 }; + if !id.contains(&want) { continue; } + println!("=== {id}"); + let mut seen = std::collections::BTreeSet::new(); + for k in o.tokens() { + if let Some(v) = o.get_f32(k) { + if v != 0.0 && seen.insert(k.clone()) { + println!(" {:<28} {:<12} 0x{:08x}", k, v, v.to_bits()); + } + } + } + } +} diff --git a/docs/re/live-unit-definitions.md b/docs/re/live-unit-definitions.md index 4f4ce39..b64f040 100644 --- a/docs/re/live-unit-definitions.md +++ b/docs/re/live-unit-definitions.md @@ -164,3 +164,35 @@ elsewhere, presumably behind a pointer. Next probe, therefore, is not a bigger window: search guest RAM for a value that is distinctive to one unit (e.g. the Acropolis' `HP 25000` = `0x46c35000`) and see what *other* structure holds it, then map that one. + +## Where the rest of the record is — three probes, and a hypothesis + +Following "search RAM for a unit-distinctive value" produced no parsed record: + +1. **`HP 25000`** (`0x46c35000`, the Acropolis) — **200+ hits**, the search limit. + Too common to anchor on: it is also a generic constant. +2. **`AttackVesselPoint 0.08`** (`0x3da3d70a`, an unusual float) — 64+ hits, and + **none of the eight windows dumped around them contained the unit's other + values** (`Size_X 400`, `Size_Z 1400`, `HP 25000`, `RadarRange 60000`). One hit + sits in the executable range (`0x820b62f4`), i.e. a code/rdata constant. +3. **The ID string itself.** `UN_f101_TCAF_Acropolis` appears **twice** in RAM, + each copy referenced by a pointer exactly **16 bytes before the string**. But a + 256-word window around it contains **none** of the unit's numbers — because + that region is the IDXD **string pool**, where `25000.0` is stored as *text*. + +🟡 **Hypothesis: there is no flat parsed record.** IDXD is a reflective format, +and the evidence fits the engine reading values out of the pool by key when it +needs them, caching only the few it touches per frame. That is exactly what the +`0x820af844` object looks like: sizes (collision), HP (damage), a couple of +ratios — and nothing else, no matter how deep it is dumped. + +If that is right, it reframes Route B. For a **defaulted** field there is no value +in the pool at all, so the default is applied by *code* at read time and can never +be found by scanning data structures. It would have to come from either the read +path (trace where a key hash is looked up and what the miss path substitutes) or +from the title's own code — which is the DuckDB static route this project already +has. The 13 `Size_Y` values recovered above worked precisely because `Size_Y` is +one of the cached, every-frame fields. + +This is a hypothesis, not a finding: it explains three negative probes but has not +been confirmed by watching a read happen.