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>
51 lines
3.4 KiB
Rust
51 lines
3.4 KiB
Rust
use sylpheed_formats::PakArchive;
|
||
use std::collections::BTreeMap;
|
||
fn be32(b:&[u8],o:usize)->u32{ if o+4<=b.len(){u32::from_be_bytes([b[o],b[o+1],b[o+2],b[o+3]])}else{0} }
|
||
fn magic(b:&[u8])->String{
|
||
if b.len()<4 {return "(<4B)".into();}
|
||
let m=&b[0..4];
|
||
if m.iter().all(|&c|(0x20..0x7f).contains(&c)){ String::from_utf8_lossy(m).into() }
|
||
else if m==b"\x89PNG"{"PNG".into()} else if m==[0,1,0,0]{"ttf".into()}
|
||
else { format!("{:02x}{:02x}{:02x}{:02x}",m[0],m[1],m[2],m[3]) }
|
||
}
|
||
fn main(){
|
||
let disc=std::env::var("SYLPHEED_DISC").unwrap();
|
||
// Known/understood IDXD schemas (semantic parsers we have)
|
||
let known_schema:BTreeMap<u32,&str>=[(0x067025b9,"ADVERTISE_MOVIE (movie_manifest)"),(0x13cb84ba,"sound registry / sounds.tbl")].into();
|
||
let mut fmt_count:BTreeMap<String,(u64,u64)>=BTreeMap::new(); // fmt -> (count, bytes)
|
||
let mut schema_census:BTreeMap<u32,(u64,u64,String)>=BTreeMap::new(); // schema -> (count,bytes,sample pak)
|
||
let mut unknown_magics:BTreeMap<String,(u64,Vec<String>)>=BTreeMap::new();
|
||
let paks:Vec<String>={let mut v=vec![]; for e in std::fs::read_dir(format!("{disc}/dat")).unwrap(){let p=e.unwrap().path(); if p.extension().map(|x|x=="pak").unwrap_or(false){v.push(p.file_stem().unwrap().to_string_lossy().into());}} v.sort(); v};
|
||
println!("pak entries decompressed formats");
|
||
for pk in &paks{
|
||
let Ok(arc)=PakArchive::open(format!("{disc}/dat/{pk}.pak")) else{continue};
|
||
let mut per:BTreeMap<String,u64>=BTreeMap::new();
|
||
let mut tot=0u64; let n=arc.entries().len();
|
||
for e in arc.entries(){
|
||
let Ok(b)=arc.read(e) else{continue};
|
||
tot+=b.len() as u64;
|
||
let mut f=magic(&b);
|
||
if f=="IDXD"{ let s=be32(&b,8); schema_census.entry(s).or_insert((0,0,pk.clone())).0+=1; schema_census.get_mut(&s).unwrap().1+=b.len() as u64; f=format!("IDXD:{s:08x}"); }
|
||
else if !["XPR2","IXUD","LSTA","RATC","RIFF","XBG7","OTTO","PNG","ttf","DDS ","T8AD"].contains(&f.as_str()){
|
||
let ent=unknown_magics.entry(f.clone()).or_insert((0,vec![])); ent.0+=1; if ent.1.len()<3 && !ent.1.contains(pk){ent.1.push(pk.clone());}
|
||
}
|
||
*per.entry(if f.starts_with("IDXD:"){"IDXD".into()}else{f.clone()}).or_default()+=1;
|
||
let g=fmt_count.entry(if f.starts_with("IDXD:"){"IDXD".into()}else{f}).or_insert((0,0)); g.0+=1; g.1+=b.len() as u64;
|
||
}
|
||
let mut fs:Vec<_>=per.into_iter().collect(); fs.sort_by_key(|x|std::cmp::Reverse(x.1));
|
||
let top:String=fs.iter().take(4).map(|(k,v)|format!("{k}×{v}")).collect::<Vec<_>>().join(" ");
|
||
println!("{pk:26} {n:>7} {:>10}KB {top}", tot/1024);
|
||
}
|
||
println!("\n=== FORMAT TOTALS (across all paks) ===");
|
||
let mut fv:Vec<_>=fmt_count.into_iter().collect(); fv.sort_by_key(|x|std::cmp::Reverse(x.1.1));
|
||
for (f,(c,b)) in &fv{ println!(" {f:12} {c:>6} entries {:>8}KB", b/1024); }
|
||
println!("\n=== IDXD SCHEMA CENSUS ({} distinct schemas) ===", schema_census.len());
|
||
let mut sv:Vec<_>=schema_census.into_iter().collect(); sv.sort_by_key(|x|std::cmp::Reverse(x.1.1));
|
||
for (s,(c,b,pk)) in &sv{
|
||
let tag=known_schema.get(s).copied().unwrap_or("??? UNDECODED");
|
||
println!(" {s:08x} {c:>5} ent {:>7}KB e.g. {pk:22} {tag}", b/1024);
|
||
}
|
||
println!("\n=== UNKNOWN / UNCLASSIFIED MAGICS ===");
|
||
for (m,(c,pks)) in &unknown_magics{ println!(" {m:12} ×{c:<5} in {pks:?}"); }
|
||
}
|