//! RE probe: what does the disc say about CHALLENGE / EXTRA missions? //! //! The title's stage-config reader (`0x82184b98`..`0x82184e94`) selects one of three //! config sections by a mode field at `obj+144`: //! mode == 3 -> "EXTRA" //! mode == 5 or 6 -> "CHALLENGE" //! otherwise -> "FILE" //! so challenge missions are a *mode*, not a separate stage numbering. This probe //! asks the disc which stage records exist and what GP_CHALLENGE.pak carries. //! //! Run: cargo run --release -p sylpheed-formats --example challenge_map -- use sylpheed_formats::{idxd::IdxdObject, pak::PakArchive}; fn main() { let disc = std::env::args().nth(1).unwrap_or_else(|| { std::env::var("SYLPHEED_DISC").expect("pass disc root or set SYLPHEED_DISC") }); println!("=== 1. StageResource records in GP_MAIN_GAME_E.pak ==="); let arc = PakArchive::open(format!("{disc}/dat/GP_MAIN_GAME_E.pak")).unwrap(); 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 stage = t .iter() .find_map(|s| s.strip_prefix("EnumUnit_").map(|x| x.trim_end_matches(".tbl").to_string())) .unwrap_or("?".into()); let bg = o.get_raw("BackGroundID").unwrap_or("?").to_string(); rows.push((stage, bg, t.len())); } rows.sort(); println!(" {} stage records", rows.len()); for (s, bg, n) in &rows { println!(" {s:8} bg={bg:16} tokens={n}"); } println!("\n=== 2. GP_CHALLENGE.pak contents ==="); match PakArchive::open(format!("{disc}/dat/GP_CHALLENGE.pak")) { Ok(ch) => { let mut by_schema: std::collections::BTreeMap = Default::default(); let mut all_tokens: Vec<(u32, Vec)> = vec![]; for e in ch.entries() { let Ok(b) = ch.read(e) else { continue }; let Ok(o) = IdxdObject::parse(&b) else { continue }; *by_schema.entry(o.schema_hash).or_default() += 1; all_tokens.push((o.schema_hash, o.tokens().iter().map(|s| s.to_string()).collect())); } println!(" {} entries, {} IDXD objects", ch.entries().len(), all_tokens.len()); for (h, n) in &by_schema { println!(" schema {h:08x} x{n}"); } for (h, t) in all_tokens.iter().take(12) { println!(" -- {h:08x}: {:?}", &t[..t.len().min(60)]); } } Err(e) => println!(" open failed: {e}"), } println!("\n=== 3. tokens mentioning Challenge / EX across the main pak ==="); let mut hits: std::collections::BTreeSet = Default::default(); for e in arc.entries() { let Ok(b) = arc.read(e) else { continue }; let Ok(o) = IdxdObject::parse(&b) else { continue }; for t in o.tokens() { let l = t.to_ascii_lowercase(); if l.contains("challenge") || t.ends_with("_EX") || t.contains("_EX4") || t.contains("_EX5") { hits.insert(format!("{:08x} {t}", o.schema_hash)); } } } for h in hits.iter().take(120) { println!(" {h}"); } println!(" ({} distinct)", hits.len()); }