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>
48 lines
2.8 KiB
Rust
48 lines
2.8 KiB
Rust
use sylpheed_formats::{idxd::IdxdObject, PakArchive};
|
|
fn main(){
|
|
let disc=std::env::var("SYLPHEED_DISC").unwrap();
|
|
let arc=PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).unwrap();
|
|
// 1. All stages: BackGroundID + phases + stage tag (from EnumUnit_SNN)
|
|
println!("=== STAGES (StageResource 3c9ae32e) ===");
|
|
let mut rows=vec![];
|
|
for e in arc.entries(){
|
|
let Ok(b)=arc.read(e) else{continue}; let Ok(o)=IdxdObject::parse(&b) else{continue};
|
|
if o.schema_hash!=0x3c9ae32e {continue;}
|
|
let t=o.tokens();
|
|
let bg=o.get_raw("BackGroundID").unwrap_or("?").to_string();
|
|
let stage=t.iter().find_map(|s|s.strip_prefix("EnumUnit_").map(|x|x.trim_end_matches(".tbl").to_string())).unwrap_or("?".into());
|
|
let phases=t.iter().filter(|s|s.starts_with("Phase_")).count();
|
|
let unitgrp=t.iter().any(|s|s.starts_with("UnitGroup_"));
|
|
let route=t.iter().any(|s|s.starts_with("Route_"));
|
|
let formation=t.iter().any(|s|s.starts_with("FormationSet_"));
|
|
rows.push((stage,bg,phases,unitgrp,route,formation));
|
|
}
|
|
rows.sort();
|
|
for (s,bg,p,ug,rt,fm) in &rows{ println!(" {s:8} location={bg:14} phases={p} [squadrons:{} route:{} formations:{}]", if *ug{"Y"}else{"·"},if *rt{"Y"}else{"·"},if *fm{"Y"}else{"·"}); }
|
|
|
|
// 2. Parse objectives table into (stage,phase) -> key counts
|
|
println!("\n=== OBJECTIVES (033b5b7e) — per stage/phase key groups ===");
|
|
for e in arc.entries(){
|
|
let Ok(b)=arc.read(e) else{continue}; let Ok(o)=IdxdObject::parse(&b) else{continue};
|
|
if o.schema_hash!=0x033b5b7e {continue;}
|
|
let t=o.tokens();
|
|
let mut groups:std::collections::BTreeMap<String,u32>=Default::default();
|
|
for tok in t{ if let Some(p)=tok.find("_Objective_").or_else(||tok.find("_Lose_")).or_else(||tok.find("_Hint_")){ let key=&tok[..p]; *groups.entry(key.trim_start_matches(|c:char|!c.is_ascii_alphabetic()).into()).or_default()+=1; } }
|
|
let stages:std::collections::BTreeSet<String>=groups.keys().filter_map(|k|k.get(..3).map(|s|s.to_string())).collect();
|
|
println!(" covers {} stages: {:?}", stages.len(), stages);
|
|
println!(" sample S01_P1 group counts: {:?}", groups.iter().filter(|(k,_)|k.starts_with("S01_P1")).collect::<Vec<_>>());
|
|
break;
|
|
}
|
|
|
|
// 3. Resolve objective TEXT: search all pak entries for the key "S01_P1_Objective_00" as an ID with English text
|
|
println!("\n=== OBJECTIVE TEXT resolution probe ===");
|
|
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();
|
|
if let Some(i)=t.iter().position(|s|s=="S01_P1_Objective_00"){
|
|
let lo=i.saturating_sub(1); let hi=(i+3).min(t.len());
|
|
println!(" schema {:08x}: ...{:?}...", o.schema_hash, &t[lo..hi]);
|
|
}
|
|
}
|
|
}
|