Decode Project Sylpheed's IDXD data model into plain, cloneable structs (from GP_MAIN_GAME_E.pak; the D/F/I/J/S paks are localized dups): - Weapon (121), CraftUnit (89, with the AI flight model in .fields), Vessel (23, structural component counts), PlayerConfig (24) — the full combat balance. - Character (68 — faction + portrait set), Stage (29 missions: location, phases, and the per-stage resource tables it wires up). - Generic Record / load_records(schema) reads any of the 105 IDXD schemas without a bespoke struct. Each blob = one entity; token[0] names the table, fields are value-before-key. Named Option<> fields for the common stats + a full `fields` map; defaulted fields stay None (they're code-resident, not on disc — honest blanks, never guessed). 8 tests against the real disc. Examples: iso_map / deep_map census the ISO's formats and IDXD schemas; dm_extract / dm_rows / dm_roster / mission_map dump the decoded data. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
34 lines
2.2 KiB
Rust
34 lines
2.2 KiB
Rust
use sylpheed_formats::{idxd::IdxdObject, PakArchive};
|
||
use std::collections::BTreeMap;
|
||
fn main(){
|
||
let disc=std::env::var("SYLPHEED_DISC").unwrap();
|
||
let arc=PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).unwrap();
|
||
// schemas whose type0/content is squadron/formation/route/group/position
|
||
let mut hit:BTreeMap<u32,(u32,String,String)>=BTreeMap::new();
|
||
for e in arc.entries(){
|
||
let Ok(b)=arc.read(e) else{continue}; let Ok(o)=IdxdObject::parse(&b) else{continue};
|
||
let t=o.tokens(); let t0=t.first().cloned().unwrap_or_default();
|
||
let joined:String=t.iter().take(20).map(|s|s.as_str()).collect::<Vec<_>>().join(" ");
|
||
if ["Squadron","Formation","Route","UnitGroup","Position","Spawn","Wave","Frame","NullFrame"].iter().any(|k|t0.contains(k)||joined.contains(k)){
|
||
let ent=hit.entry(o.schema_hash).or_insert((0,t0.clone(),joined.chars().take(90).collect()));
|
||
ent.0+=1;
|
||
}
|
||
}
|
||
let mut v:Vec<_>=hit.into_iter().collect(); v.sort_by_key(|x|std::cmp::Reverse(x.1.0));
|
||
for (s,(c,t0,sample)) in &v{ println!("[{s:08x}] ×{c:<4} type0={t0:<18.18} :: {sample}"); }
|
||
// dump a Squadron/UnitGroup blob fully to see if it has positions (binary floats) or refs
|
||
println!("\n─── sample squadron/formation blob (first matching) ───");
|
||
for e in arc.entries(){
|
||
let Ok(b)=arc.read(e) else{continue}; let Ok(o)=IdxdObject::parse(&b) else{continue};
|
||
let t=o.tokens(); let t0=t.first().cloned().unwrap_or_default();
|
||
if t0.contains("Squadron")||t0.contains("Formation")||t0.contains("UnitGroup"){
|
||
println!("schema {:08x} type0={t0} count={} tokens={}, blob {}B", o.schema_hash,o.count,t.len(),b.len());
|
||
for (i,tk) in t.iter().take(30).enumerate(){ print!("{i:>2}:{tk:<20.20}"); if i%4==3{println!();} } println!();
|
||
// scan blob for float-like values (reasonable coords) in the binary region
|
||
let nfloats=(0..b.len().saturating_sub(4)).step_by(4).filter(|&i|{let f=f32::from_be_bytes([b[i],b[i+1],b[i+2],b[i+3]]); f.is_finite()&&f.abs()>1.0&&f.abs()<1e6}).count();
|
||
println!(" ~{nfloats} plausible BE-float words in blob (positions live in binary region if high)");
|
||
break;
|
||
}
|
||
}
|
||
}
|