This branch predates CI on `main`. `cargo fmt --all` only; no behaviour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
104 lines
4.2 KiB
Rust
104 lines
4.2 KiB
Rust
//! What are the keyframe block's `+4` and `+8`?
|
|
//!
|
|
//! `+12` is decoded as a screen-plane rotation in degrees. `+4` and `+8` sit
|
|
//! immediately before it and are carried but unexplained; one standing 🟡
|
|
//! reading is that the three together are rotations about three axes, "not tied
|
|
//! to an observed rotation". This censuses them across every UI pak on the disc
|
|
//! so the reading can be argued with rather than assumed.
|
|
use std::collections::BTreeMap;
|
|
use sylpheed_formats::{pak, ui_layout};
|
|
|
|
fn main() {
|
|
let dir = std::env::args().nth(1).expect("usage: <disc>/dat");
|
|
let mut paks: Vec<_> = std::fs::read_dir(&dir)
|
|
.expect("dir")
|
|
.filter_map(|e| e.ok().map(|e| e.path()))
|
|
.filter(|p| p.extension().map(|x| x == "pak").unwrap_or(false))
|
|
.collect();
|
|
paks.sort();
|
|
|
|
let mut h4: BTreeMap<i32, usize> = BTreeMap::new();
|
|
let mut h8: BTreeMap<i32, usize> = BTreeMap::new();
|
|
let mut h12: BTreeMap<i32, usize> = BTreeMap::new();
|
|
let mut both_nz: Vec<String> = Vec::new();
|
|
let (mut kfs, mut builds) = (0usize, 0usize);
|
|
|
|
for p in &paks {
|
|
let Ok(ar) = pak::PakArchive::open(p) else {
|
|
continue;
|
|
};
|
|
let name = p.file_name().unwrap().to_string_lossy().to_string();
|
|
for (i, e) in ar.entries().to_vec().iter().enumerate() {
|
|
let Ok(bytes) = ar.read(e) else { continue };
|
|
let Some(b) = ui_layout::parse_build(&bytes) else {
|
|
continue;
|
|
};
|
|
builds += 1;
|
|
// parents and leaves alike
|
|
let mut groups: Vec<(String, Vec<ui_layout::Keyframe>)> = b
|
|
.elements
|
|
.iter()
|
|
.map(|el| (el.name.clone(), el.keyframes.clone()))
|
|
.collect();
|
|
for el in &b.elements {
|
|
if let Some(&(off, size)) = b.records.get(&el.name) {
|
|
if let Some(lb) = ui_layout::parse_build(&bytes[off..off + size]) {
|
|
for le in &lb.elements {
|
|
groups
|
|
.push((format!("{}->{}", el.name, le.name), le.keyframes.clone()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (nm, ks) in groups {
|
|
for k in &ks {
|
|
kfs += 1;
|
|
*h4.entry(k.unknown_4).or_default() += 1;
|
|
*h8.entry(k.unknown_8).or_default() += 1;
|
|
*h12.entry(k.rotation_deg).or_default() += 1;
|
|
if k.unknown_4 != 0 || k.unknown_8 != 0 {
|
|
both_nz.push(format!(
|
|
"{name} e{i} {nm} +4={} +8={} +12={}",
|
|
k.unknown_4, k.unknown_8, k.rotation_deg
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
println!("{builds} builds, {kfs} keyframes (parents + leaves)\n");
|
|
for (nm, h) in [("+4", &h4), ("+8", &h8), ("+12 (rotation)", &h12)] {
|
|
let nz: usize = h.iter().filter(|(k, _)| **k != 0).map(|(_, v)| *v).sum();
|
|
println!(
|
|
"{nm}: {} distinct values, {} non-zero keyframes ({:.4}%)",
|
|
h.len(),
|
|
nz,
|
|
100.0 * nz as f64 / kfs as f64
|
|
);
|
|
let mut top: Vec<_> = h.iter().filter(|(k, _)| **k != 0).collect();
|
|
top.sort_by_key(|(_, v)| std::cmp::Reverse(**v));
|
|
for (k, v) in top.iter().take(6) {
|
|
println!(" {k:>8} x{v}");
|
|
}
|
|
}
|
|
println!("\nkeyframes with a non-zero +4 or +8: {}", both_nz.len());
|
|
// Per-pak, so a reader can ask whether a pak they have a CAPTURE of is
|
|
// among them -- which decides whether the field is testable at all.
|
|
let mut per: BTreeMap<String, usize> = BTreeMap::new();
|
|
for l in &both_nz {
|
|
let pak = l.split_whitespace().next().unwrap_or("?").to_string();
|
|
*per.entry(pak).or_default() += 1;
|
|
}
|
|
println!(" by pak:");
|
|
for (k, v) in &per {
|
|
println!(" {k:<28} {v}");
|
|
}
|
|
println!(" paks with NONE: (any UI pak not listed above)");
|
|
if let Ok(f) = std::env::var("KF_SHOW") {
|
|
println!("\n all lines for {f}:");
|
|
for l in both_nz.iter().filter(|l| l.starts_with(&f)) {
|
|
println!(" {l}");
|
|
}
|
|
}
|
|
}
|