re: three probes for the parsed record all miss -- hypothesis: there is not one
Searched RAM for HP 25000 (200+ hits, too common), for the rare float AttackVesselPoint 0.08 (64 hits, no window holds the unit's other values, one hit is a code constant), and for the ID string itself (two copies, each pointed at from -0x10, but the surrounding 256 words hold none of the numbers because that region is the IDXD string pool where values are ASCII). Hypothesis recorded as 🟡: IDXD is reflective and the engine likely reads values from the pool by key on demand, caching only the per-frame ones -- which is exactly what the 0x820af844 object contains at any dump depth. If so, a defaulted field has no value anywhere in data and the default is applied by code at read time, so Route B for the Ratio/Count family needs the read path or the static DuckDB route, not more scanning. The 13 Size_Y values worked because Size_Y is one of the cached fields. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
22
crates/sylpheed-formats/examples/unit_values.rs
Normal file
22
crates/sylpheed-formats/examples/unit_values.rs
Normal file
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user