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>
60 lines
2.8 KiB
Rust
60 lines
2.8 KiB
Rust
use sylpheed_formats::{ratc, idxd::IdxdObject, PakArchive};
|
||
use std::collections::BTreeMap;
|
||
fn main(){
|
||
let disc=std::env::var("SYLPHEED_DISC").unwrap();
|
||
// 1. RATC child-type census across the big RATC paks
|
||
let mut childtypes:BTreeMap<String,u64>=BTreeMap::new();
|
||
let mut ratc_unparsed=0u64;
|
||
for pk in ["GP_READY_ROOM","GP_MOVIE_THEATER","GP_DIALOG","GP_DEBRIEFING_PILOTLOG","GP_TITLE","GP_BUNK"]{
|
||
let Ok(arc)=PakArchive::open(format!("{disc}/dat/{pk}.pak")) else{continue};
|
||
for e in arc.entries(){
|
||
let Ok(b)=arc.read(e) else{continue};
|
||
if !ratc::is_ratc(&b){continue;}
|
||
match ratc::parse(&b){
|
||
Some(kids)=> for k in kids{
|
||
let ext=k.name.rsplit('.').next().unwrap_or("?").to_lowercase();
|
||
*childtypes.entry(ext).or_default()+=1;
|
||
},
|
||
None=>ratc_unparsed+=1,
|
||
}
|
||
}
|
||
}
|
||
println!("=== RATC child-type census (big RATC paks) ===");
|
||
let mut cv:Vec<_>=childtypes.into_iter().collect(); cv.sort_by_key(|x|std::cmp::Reverse(x.1));
|
||
for (e,c) in &cv{ println!(" .{e:8} ×{c}"); }
|
||
println!(" (RATC bundles that failed to parse: {ratc_unparsed})");
|
||
|
||
// 2. The 00000002 mystery format (GP_MAIN_GAME_E)
|
||
let arc=PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).unwrap();
|
||
for e in arc.entries(){
|
||
let Ok(b)=arc.read(e) else{continue};
|
||
if b.len()>=4 && b[0..4]==[0,0,0,2]{
|
||
let hx:String=b[..48.min(b.len())].iter().map(|x|format!("{x:02x}")).collect::<Vec<_>>().join(" ");
|
||
let asc:String=b[..64.min(b.len())].iter().map(|&x|if(0x20..0x7f).contains(&x){x as char}else{'.'}).collect();
|
||
println!("\n=== 00000002 format sample ({}B) ===\n{hx}\n{asc}",b.len());
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 3. Sample the top undecoded IDXD schemas: first tokens (guess semantics)
|
||
println!("\n=== top undecoded IDXD schemas — sample tokens (semantic hints) ===");
|
||
let targets:[u32;6]=[0xb412e6d8,0x026379ab,0x43faa517,0x3c5b0549,0x6ab4825a,0x0426e81d];
|
||
for want in targets{
|
||
let mut shown=false;
|
||
for e in arc.entries(){
|
||
if shown{break;}
|
||
let Ok(b)=arc.read(e) else{continue};
|
||
if b.len()<12 || &b[0..4]!=b"IDXD"{continue;}
|
||
let s=u32::from_be_bytes([b[8],b[9],b[10],b[11]]);
|
||
if s!=want{continue;}
|
||
if let Ok(o)=IdxdObject::parse(&b){
|
||
let t=o.tokens();
|
||
let sample:Vec<String>=t.iter().take(14).map(|s|{let s=s.chars().take(18).collect::<String>();s}).collect();
|
||
println!(" {want:08x}: {sample:?}");
|
||
shown=true;
|
||
}
|
||
}
|
||
if !shown{ println!(" {want:08x}: (parse failed / not found)"); }
|
||
}
|
||
}
|