//! RE probe: the GamePart_ChallengeMission screen config. //! //! The class's code range (`0x82187E60`..`0x8218CF10`, bounded by the factory //! creator thunks either side) references these config keys: //! MISSIONS, MISSION_ID, NEW_STAGE, REQUIREMENT, REQUIREMENT_DESC, "Always", //! GRAY_BUTTON, NORMAL_BUTTON, THUMBNAIL, STAGE_DESC, TEXT_STAGE, //! RECORD_TYPE, "Time", TEXT_RECORD, BASE_INFO //! i.e. the challenge list is a *config record* with a per-mission REQUIREMENT. //! It lives in `tables.pak` (one copy per language), not in GP_CHALLENGE.pak. //! //! Run: cargo run --release -p sylpheed-formats --example challenge_screen -- use sylpheed_formats::{idxd::IdxdObject, pak::PakArchive}; const KEYS: &[&str] = &[ "MISSIONS", "MISSION_ID", "REQUIREMENT", "REQUIREMENT_DESC", "NEW_STAGE", "GRAY_BUTTON", "NORMAL_BUTTON", "RECORD_TYPE", "THUMBNAIL", "STAGE_DESC", ]; fn find(h: &[u8], n: &[u8]) -> bool { h.windows(n.len()).any(|w| w == n) } 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") }); let dat = format!("{disc}/dat"); let mut paks: Vec<_> = std::fs::read_dir(&dat) .expect("dat dir") .filter_map(|e| e.ok()) .map(|e| e.path()) .filter(|p| p.extension().is_some_and(|x| x == "pak")) .collect(); paks.sort(); for p in &paks { let Ok(arc) = PakArchive::open(p) else { continue }; for (i, e) in arc.entries().iter().enumerate() { let Ok(b) = arc.read(e) else { continue }; if KEYS.iter().filter(|k| find(&b, k.as_bytes())).count() < KEYS.len() { continue; } let name = p.file_name().unwrap().to_string_lossy().to_string(); match IdxdObject::parse(&b) { Ok(o) => { let toks = o.tokens(); // language tag: the PATH value, e.g. "dat\GP_CHALLENGE.pak+eng\" let lang = toks .iter() .find(|t| t.contains("GP_CHALLENGE.pak+")) .map(|t| t.to_string()) .unwrap_or_default(); println!( "\n===== {name} entry #{i} schema {:08x} {} tokens [{lang}] =====", o.schema_hash, toks.len() ); // Print the pool in order; the record is key/value interleaved and // IDXD dedupes repeats, so pairing is read by eye, not asserted. for (j, t) in toks.iter().enumerate() { println!(" {j:3} {t}"); } } Err(e) => println!("\n===== {name} entry #{i}: IDXD parse failed: {e} ====="), } } } }