80 findings, not the 14 the first run showed -- clippy stops at the first failing compilation unit, so `--keep-going` is what makes the list complete. 60 were machine-applicable (`cargo clippy --fix`). The rest by hand: * five descending `sort_by` -> `sort_by_key(Reverse(..))` * `chunks_exact(4)` on both sides of four zips, so the compared items stay `[u8; 4]` rather than one array against one slice * three `type` aliases for the census maps and the captured-quad tuple * `&PathBuf` -> `&Path` in two disc tests * two range loops; one of them keeps `#[allow(needless_range_loop)]` with the reason -- the index is into a map's value, which changes each iteration * the module doc list in `invert_capture` re-indented to markdown's rules * `blit`'s eight arguments get `#[allow(too_many_arguments)]`, not a struct One dead `let off = b.len();` in a `ratc` test is dropped rather than renamed. The sibling test at :162 is the one that asserts an offset; if this one was meant to as well, that is a test change and not a lint fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
119 lines
4.3 KiB
Rust
119 lines
4.3 KiB
Rust
//! Scratch: confirm the `.rat` layout record offsets against a real screen pak.
|
|
//! Run: cargo run -p sylpheed-formats --example rat_inspect -- <GP_SCREEN.pak>
|
|
use sylpheed_formats::{pak::PakArchive, ratc, t8ad};
|
|
|
|
fn be32(b: &[u8], o: usize) -> u32 {
|
|
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("pak path");
|
|
let ar = PakArchive::open(&path).expect("open pak");
|
|
println!("entries: {}", ar.len());
|
|
|
|
let mut decoded: Vec<Vec<u8>> = Vec::new();
|
|
for (i, e) in ar.entries().iter().enumerate() {
|
|
let d = ar.read(e).unwrap_or_default();
|
|
let magic = String::from_utf8_lossy(&d[..4.min(d.len())]).to_string();
|
|
let (nt, nr) = if ratc::is_ratc(&d) {
|
|
let k = ratc::parse(&d).unwrap_or_default();
|
|
(
|
|
k.iter().filter(|c| c.kind == "T8aD").count(),
|
|
k.iter()
|
|
.filter(|c| c.name.to_lowercase().ends_with(".rat"))
|
|
.count(),
|
|
)
|
|
} else {
|
|
(0, 0)
|
|
};
|
|
println!(
|
|
" entry[{:2}] {:>8} B magic={:4} t32={:2} rat={:2}",
|
|
i,
|
|
d.len(),
|
|
magic,
|
|
nt,
|
|
nr
|
|
);
|
|
decoded.push(d);
|
|
}
|
|
// Pick the entry with the most children as "build0".
|
|
let bi = (0..decoded.len())
|
|
.filter(|&i| ratc::is_ratc(&decoded[i]))
|
|
.max_by_key(|&i| ratc::parse(&decoded[i]).map(|k| k.len()).unwrap_or(0))
|
|
.unwrap();
|
|
println!("=> inspecting richest build: entry[{}]", bi);
|
|
let bundle = &decoded[bi];
|
|
let kids = ratc::parse(bundle).unwrap_or_default();
|
|
let t32: Vec<_> = kids.iter().filter(|c| c.kind == "T8aD").collect();
|
|
let rat: Vec<_> = kids
|
|
.iter()
|
|
.filter(|c| c.name.to_lowercase().ends_with(".rat"))
|
|
.collect();
|
|
println!(
|
|
"build0: {} children, {} t32 sprites, {} rat records",
|
|
kids.len(),
|
|
t32.len(),
|
|
rat.len()
|
|
);
|
|
for c in t32.iter().take(5) {
|
|
let b = &bundle[c.offset..(c.offset + c.size).min(bundle.len())];
|
|
if let Some(img) = t8ad::parse(b) {
|
|
println!(" T8aD {:28} {}x{}", c.name, img.width, img.height);
|
|
}
|
|
}
|
|
println!(" -- .rat records --");
|
|
for c in rat.iter().take(10) {
|
|
let r = &bundle[c.offset..(c.offset + c.size).min(bundle.len())];
|
|
if r.len() < 0x60 {
|
|
println!(" .rat {:20} (len {}, too short)", c.name, r.len());
|
|
continue;
|
|
}
|
|
let (dw, dh) = (be32(r, 0x18), be32(r, 0x1c));
|
|
let sprite = {
|
|
let s = &r[0x20..0x30.min(r.len())];
|
|
let end = s.iter().position(|&b| b == 0).unwrap_or(s.len());
|
|
String::from_utf8_lossy(&s[..end]).to_string()
|
|
};
|
|
let (pvx, pvy) = (be32(r, 0x50), be32(r, 0x54));
|
|
// scan for placement block [scaleX=100, scaleY=100, tint, X<dw, Y<dh]
|
|
let mut placement = None;
|
|
let mut o = 0x58;
|
|
while o + 20 <= r.len() {
|
|
if be32(r, o) == 100 && be32(r, o + 4) == 100 {
|
|
let (tint, x, y) = (be32(r, o + 8), be32(r, o + 12), be32(r, o + 16));
|
|
if x < dw && y < dh {
|
|
placement = Some((o, tint, x, y));
|
|
break;
|
|
}
|
|
}
|
|
o += 4;
|
|
}
|
|
println!(
|
|
" .rat {:22} {}x{} sprite={:16} pivot=({},{}) place={:x?}",
|
|
c.name, dw, dh, sprite, pvx, pvy, placement
|
|
);
|
|
}
|
|
|
|
// End-to-end: composite the build and write it out as a PNG.
|
|
if let Some(screen) = sylpheed_formats::ui_layout::compose_build(bundle, false) {
|
|
println!(
|
|
"compose: {}x{}, drew {} records: {:?}",
|
|
screen.width,
|
|
screen.height,
|
|
screen.drawn.len(),
|
|
screen.drawn
|
|
);
|
|
if let Some(out) = std::env::args().nth(2) {
|
|
// Dependency-free PPM (P6, RGB — alpha already composited over the backdrop).
|
|
let mut buf = format!("P6\n{} {}\n255\n", screen.width, screen.height).into_bytes();
|
|
for px in screen.rgba.as_chunks::<4>().0 {
|
|
buf.extend_from_slice(&px[..3]);
|
|
}
|
|
std::fs::write(&out, buf).unwrap();
|
|
println!("wrote {out}");
|
|
}
|
|
} else {
|
|
println!("compose: build produced no image");
|
|
}
|
|
}
|