Files
benilla-octo/crates/benilla-formats/examples/wmo_chunk_census.rs
T
SamandClaude Fable 5 6b980b9efb the player build: one binary that carries its content
- a player build ships beside the dev one — dev chords and probe surfaces go dark, and a gate enforces what a player can still reach, by medium not by module
- the binary carries its own shaders and its shipped UI, finds the WoW folder by itself, and keeps its local state in one benilla-config folder
- the glue screens get a capture shutter, and the engine stops depending on the protocol crate

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 18:18:02 +07:00

106 lines
4.2 KiB
Rust

//! Chunk-integrity census over WMO files: walk the top-level chunk list the way the loader's
//! [`find_wmo_chunk`] walk does and report every file whose chunk stream does not tile exactly — a
//! chunk whose declared size overruns EOF. The reference **clamps** such a chunk (wow-re
//! `models.md` "WMO chunk-structure contract": *"reads chunks while the 8-byte header is in-bounds
//! and clamps the last chunk to EOF … e.g. `Undercity_144.wmo`'s MOGP runs 1 B past EOF,
//! tolerated"*), so an overrun here must cost us nothing; a file listed as losing a chunk is a bug.
//!
//! ```text
//! cargo run -p benilla-formats --example wmo_chunk_census -- <root.wmo> # one building
//! cargo run -p benilla-formats --example wmo_chunk_census -- --all # the whole corpus
//! ```
//!
//! Output is Blizzard-derived — pipe it to the scratchpad, never into the repo.
/// Walk `b`'s chunk stream as a pure **data** question: is it exactly tiled, or does some chunk
/// declare more bytes than the file holds? Returns `(tag, declared_end, file_len)` for the first
/// over-declared chunk. Whether the loader survives it is asked separately, of the loader itself.
fn overrun(b: &[u8]) -> Option<(String, usize, usize)> {
let mut off = 0usize;
while off + 8 <= b.len() {
let magic: String = b[off..off + 4].iter().rev().map(|&c| c as char).collect();
let size = u32::from_le_bytes([b[off + 4], b[off + 5], b[off + 6], b[off + 7]]) as usize;
let end = match off.checked_add(8).and_then(|s| s.checked_add(size)) {
Some(e) => e,
None => return Some((magic, usize::MAX, b.len())),
};
if end > b.len() {
return Some((magic, end, b.len()));
}
off = end;
}
None
}
fn main() -> anyhow::Result<()> {
let arg = std::env::args()
.nth(1)
.ok_or_else(|| anyhow::anyhow!("usage: wmo_chunk_census <root.wmo> | --all"))?;
let data = benilla_formats::wow_data().expect("no WoW install found (set $WOW_DATA)");
let mut chain = benilla_formats::open_chain(&data)?;
let paths: Vec<String> = if arg == "--all" {
let mut v: Vec<String> = chain
.list()?
.into_iter()
.map(|e| e.name)
.filter(|n| n.to_ascii_lowercase().ends_with(".wmo"))
.collect();
v.sort();
v.dedup();
v
} else {
let root = chain.read_file(&arg)?;
let root = benilla_formats::parse_wmo_root(&root)?;
let stem = arg.strip_suffix(".wmo").unwrap_or(&arg);
std::iter::once(arg.clone())
.chain((0..root.group_count()).map(|gi| format!("{stem}_{gi:03}.wmo")))
.collect()
};
let (mut checked, mut empty, mut overrun_n, mut lost) = (0u32, 0u32, 0u32, 0u32);
for p in &paths {
let Ok(b) = chain.read_file(p) else { continue };
if b.is_empty() {
empty += 1;
continue;
}
checked += 1;
// The loader's own verdict, not a re-implementation of it: a group file must yield its MOGP
// header (flags + portal-ref span — losing it dead-ends the portal flood), a root must parse.
let is_group = b.len() > 16 && &b[12..16] == b"PGOM";
let loads = if is_group {
benilla_formats::wmo_group_header(&b).is_some()
} else {
benilla_formats::parse_wmo_root(&b).is_ok()
};
if !loads {
lost += 1;
println!("{p}: the loader gets NO {} out of it", {
if is_group {
"MOGP header"
} else {
"root"
}
});
}
if let Some((tag, end, len)) = overrun(&b) {
overrun_n += 1;
println!(
"{p}: {tag} declares end {end}, file is {len} ({} over) — loader {}",
end.saturating_sub(len),
if loads {
"tolerates it"
} else {
"LOSES THE CHUNK"
}
);
}
}
println!(
"{checked} wmo files walked ({empty} empty stubs): {overrun_n} with an over-declared chunk, \
{lost} the loader cannot read"
);
Ok(())
}