use std::collections::BTreeMap; use sylpheed_formats::PakArchive; fn be32(b: &[u8], o: usize) -> u32 { if o + 4 <= b.len() { u32::from_be_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]]) } else { 0 } } fn magic(b: &[u8]) -> String { if b.len() < 4 { return "(<4B)".into(); } let m = &b[0..4]; if m.iter().all(|&c| (0x20..0x7f).contains(&c)) { String::from_utf8_lossy(m).into() } else if m == b"\x89PNG" { "PNG".into() } else if m == [0, 1, 0, 0] { "ttf".into() } else { format!("{:02x}{:02x}{:02x}{:02x}", m[0], m[1], m[2], m[3]) } } fn main() { let disc = std::env::var("SYLPHEED_DISC").unwrap(); // Known/understood IDXD schemas (semantic parsers we have) let known_schema: BTreeMap = [ (0x067025b9, "ADVERTISE_MOVIE (movie_manifest)"), (0x13cb84ba, "sound registry / sounds.tbl"), ] .into(); let mut fmt_count: BTreeMap = BTreeMap::new(); // fmt -> (count, bytes) let mut schema_census: BTreeMap = BTreeMap::new(); // schema -> (count,bytes,sample pak) let mut unknown_magics: BTreeMap)> = BTreeMap::new(); let paks: Vec = { let mut v = vec![]; for e in std::fs::read_dir(format!("{disc}/dat")).unwrap() { let p = e.unwrap().path(); if p.extension().map(|x| x == "pak").unwrap_or(false) { v.push(p.file_stem().unwrap().to_string_lossy().into()); } } v.sort(); v }; println!("pak entries decompressed formats"); for pk in &paks { let Ok(arc) = PakArchive::open(format!("{disc}/dat/{pk}.pak")) else { continue; }; let mut per: BTreeMap = BTreeMap::new(); let mut tot = 0u64; let n = arc.entries().len(); for e in arc.entries() { let Ok(b) = arc.read(e) else { continue }; tot += b.len() as u64; let mut f = magic(&b); if f == "IDXD" { let s = be32(&b, 8); schema_census.entry(s).or_insert((0, 0, pk.clone())).0 += 1; schema_census.get_mut(&s).unwrap().1 += b.len() as u64; f = format!("IDXD:{s:08x}"); } else if ![ "XPR2", "IXUD", "LSTA", "RATC", "RIFF", "XBG7", "OTTO", "PNG", "ttf", "DDS ", "T8AD", ] .contains(&f.as_str()) { let ent = unknown_magics.entry(f.clone()).or_insert((0, vec![])); ent.0 += 1; if ent.1.len() < 3 && !ent.1.contains(pk) { ent.1.push(pk.clone()); } } *per.entry(if f.starts_with("IDXD:") { "IDXD".into() } else { f.clone() }) .or_default() += 1; let g = fmt_count .entry(if f.starts_with("IDXD:") { "IDXD".into() } else { f }) .or_insert((0, 0)); g.0 += 1; g.1 += b.len() as u64; } let mut fs: Vec<_> = per.into_iter().collect(); fs.sort_by_key(|x| std::cmp::Reverse(x.1)); let top: String = fs .iter() .take(4) .map(|(k, v)| format!("{k}×{v}")) .collect::>() .join(" "); println!("{pk:26} {n:>7} {:>10}KB {top}", tot / 1024); } println!("\n=== FORMAT TOTALS (across all paks) ==="); let mut fv: Vec<_> = fmt_count.into_iter().collect(); fv.sort_by_key(|x| std::cmp::Reverse(x.1 .1)); for (f, (c, b)) in &fv { println!(" {f:12} {c:>6} entries {:>8}KB", b / 1024); } println!( "\n=== IDXD SCHEMA CENSUS ({} distinct schemas) ===", schema_census.len() ); let mut sv: Vec<_> = schema_census.into_iter().collect(); sv.sort_by_key(|x| std::cmp::Reverse(x.1 .1)); for (s, (c, b, pk)) in &sv { let tag = known_schema.get(s).copied().unwrap_or("??? UNDECODED"); println!( " {s:08x} {c:>5} ent {:>7}KB e.g. {pk:22} {tag}", b / 1024 ); } println!("\n=== UNKNOWN / UNCLASSIFIED MAGICS ==="); for (m, (c, pks)) in &unknown_magics { println!(" {m:12} ×{c:<5} in {pks:?}"); } }