`ratc::parse` named each child by scanning backwards for the last printable run of bytes before its magic. The format states the name explicitly instead, in an `opt ` block: `"opt " | BE32 len | name | NUL | 3 bytes | magic` -- the same block `ui_layout::opt_link` already read for a button's focus link. The scan agrees with it 17 918 times out of 17 942 and is wrong 24 times, every one the same failure: the 3 trailing payload bytes are themselves printable and beat the real name. For `pteff05.t32` those bytes are `38 41 58` = `8AX`, so the full-resolution background of all five menu screens registered under a name no element declares, resolved to no sprite, and `compose` dropped it through an early `continue` that -- unlike the two arms above it -- records nothing. The screen lost its background and `screen render` still reported "all resolved". `8AX` was never a name. Docs that treated it as one are corrected here. Disc-wide, and the control is the 17 918 the scan already got right: the `opt ` reading reproduces every one of them. Effect on the five screens is the signature of the same art at twice the resolution -- mean brightness unmoved, high-frequency detail x1.15..x1.30 -- which is what the separately-measured `ui-8ax-fullres-background` result said the game draws. Also closes a long-standing dangling reference: `pmbase.t32`, recorded as "on the disc nowhere", is the `GP_STAGE_CLEAR` child the scan called `8AX`. RATC sibling references now resolve 10 148 of 10 148. Verified: 114/114 sylpheed-formats unit tests (including two new ones pinning the `8AX` case byte for byte and the no-block fallback), and every disc-gated integration suite in sylpheed-formats/sylpheed-cli.
68 lines
3.3 KiB
Rust
68 lines
3.3 KiB
Rust
//! Does any unread declaration word POINT at the element's T8aD child?
|
|
//!
|
|
//! `pteff05.t32`/`pteff04.t32` resolve to no sprite because the `T8aD` they want
|
|
//! is registered under the name `8AX`. Elimination says `8AX` is the one they
|
|
//! mean -- one unresolved element, one unclaimed non-focus-state child, in 6 of
|
|
//! 6 title-side builds. Elimination is not a pointer, so: the 60-byte
|
|
//! declaration reads name[0..28], parent@32, kind@40, pivot@48/52. The words at
|
|
//! +28, +36, +44 and +56 are unread. If one of them indexes the RATC child
|
|
//! table, the RESOLVED elements are the control -- their child index is known,
|
|
//! so a candidate field must reproduce it for them before it may be believed for
|
|
//! the unresolved one.
|
|
//!
|
|
//! cargo run -p sylpheed-formats --example decl_word_probe -- <pak> [entry]
|
|
use sylpheed_formats::{pak, ratc, ui_layout};
|
|
|
|
const OFFS: [usize; 4] = [28, 36, 44, 56];
|
|
|
|
fn be32(b: &[u8], o: usize) -> u32 {
|
|
if o + 4 > b.len() { return 0; }
|
|
u32::from_be_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]])
|
|
}
|
|
|
|
fn main() {
|
|
let path = std::env::args().nth(1).expect("usage: decl_word_probe <pak> [entry]");
|
|
let want: Option<usize> = std::env::args().nth(2).and_then(|s| s.parse().ok());
|
|
let ar = pak::PakArchive::open(&path).expect("open pak");
|
|
let entries: Vec<_> = ar.entries().to_vec();
|
|
// Per candidate offset, across every build: control hits / control total.
|
|
let mut hit = [0usize; 4];
|
|
let mut tot = 0usize;
|
|
for (i, e) in entries.iter().enumerate() {
|
|
if want.is_some_and(|w| w != i) { continue; }
|
|
let Ok(bytes) = ar.read(e) else { continue };
|
|
let Some(build) = ui_layout::parse_build(&bytes) else { continue };
|
|
let Some(kids) = ratc::parse(&bytes) else { continue };
|
|
// Index space to test against: the T8aD children, in child order.
|
|
let t8: Vec<&ratc::RatcChild> = kids.iter().filter(|c| c.kind == "T8aD").collect();
|
|
if build.elements.iter().all(|el| el.sprite.is_some() || el.kind & 0x10 != 0) {
|
|
continue;
|
|
}
|
|
println!("== entry {i} ({} elements, {} T8aD children)", build.elements.len(), t8.len());
|
|
for (n, c) in t8.iter().enumerate() { println!(" child[{n:2}] {}", c.name); }
|
|
for el in &build.elements {
|
|
if el.kind & 0x10 != 0 { continue; }
|
|
let d = &bytes[0x20 + el.index * 60..0x20 + (el.index + 1) * 60];
|
|
let words: Vec<u32> = OFFS.iter().map(|&o| be32(d, o)).collect();
|
|
// The control: for a RESOLVED element, which T8aD child is it?
|
|
let truth = el.sprite.as_ref()
|
|
.and_then(|s| t8.iter().position(|c| &c.name == s));
|
|
if let Some(t) = truth {
|
|
tot += 1;
|
|
for (k, w) in words.iter().enumerate() {
|
|
if *w as usize == t { hit[k] += 1; }
|
|
}
|
|
}
|
|
println!(
|
|
" [{:2}] {:26} sprite={:?} child={:?} +28={} +36={} +44={} +56={}",
|
|
el.index, el.name, el.sprite, truth,
|
|
words[0] as i32, words[1] as i32, words[2] as i32, words[3] as i32
|
|
);
|
|
}
|
|
}
|
|
println!("\nCONTROL: resolved elements whose child index a word reproduces, of {tot}:");
|
|
for (k, o) in OFFS.iter().enumerate() {
|
|
println!(" +{o:<3} {:3}/{tot}", hit[k]);
|
|
}
|
|
}
|