//! RE probe: the `ACHIEVEMENTS_REQUIREMENTS` config list. //! //! `GamePart_Debriefing` (`0x8218CF38`..`0x82191B18`) walks this list after a //! mission (`sub_8218F9A8`): for each entry it takes the entry's **index** `n`, //! tests bit `n` of an awarded-mask, and if the bit is clear it evaluates the //! entry (`0x8218FAB0`) and sets the bit when satisfied. `GamePart_ChallengeMission` //! then gates each challenge mission on a bit of the same space via its own //! `REQUIREMENT` key. So this list *is* the achievement/bit numbering. //! //! Run: cargo run --release -p sylpheed-formats --example achievements_map -- use sylpheed_formats::{idxd::IdxdObject, pak::PakArchive}; 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 !find(&b, b"ACHIEVEMENTS_REQUIREMENTS") { continue; } let name = p.file_name().unwrap().to_string_lossy().to_string(); match IdxdObject::parse(&b) { Ok(o) => { let t = o.tokens(); println!( "\n===== {name} entry #{i} schema {:08x} {} tokens =====", o.schema_hash, t.len() ); for (j, tok) in t.iter().enumerate() { println!(" {j:3} {tok}"); } } Err(err) => println!("\n===== {name} entry #{i}: not IDXD ({err}) ====="), } } } }