Comments that pointed into the maintainer's private reverse-engineering notes (a note by name, a path, a section or finding id, the analysis's own name for a client function, a process word) now carry the fact itself, cited by the reference client's address, by vmangos file and line, or as "the reference". Nobody outside could follow those pointers. Comment text only, plus the test-assert and log strings that carried the same pointers. No claim changes: a comment the notes have since corrected keeps its wording here, and correcting it is separate work. The two reference generators, `scripts/gen-reference-globals.py` and `scripts/gen-reference-verb-events.py`, named the private notes' layout to find their inputs. They now take the input files as arguments (`--capture`, `--framexml`, `--names`, `--extents`, `--disasm`). Run on the same inputs, both write the committed tables row for row; only the two header lines that named the old environment variable change. Checked per file: - the code is identical: each file with its comments stripped compares equal before and after; a changed string literal is compared under a mask that proves only literal text moved and every literal kept its line count; - no client address the old text cited was dropped; - a second reviewer checked every added address against its derivation; - a detector for the notes' names, paths, ids, function names and process words finds none left; - `scripts/gates.sh` is green. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
106 lines
4.2 KiB
Rust
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 (`0x6c3a60` /
|
|
//! `0x6c3f80` read chunks while the 8-byte header is in-bounds and clamp 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(())
|
|
}
|