Files
Sylpheed/crates/sylpheed-formats/examples/voice_stream_cue_map.rs
MechaCat02 ccd49ac31f fix(lint): clear the clippy gate across examples and tests
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>
2026-09-12 16:42:41 +02:00

247 lines
10 KiB
Rust

//! Who owns the bytes in front of a movie-voice region's first `RIFF`?
//!
//! [`voice-region-leading-chunk.md`] left one thing open: the leading chunk of
//! the 17 stream-opening regions is real XMA audio that **no other movie-voice
//! region claims** — but the census only enumerated the 95 movie cues, while the
//! same continuous stream also carries the in-mission `VOICE_D_*` lines. The
//! leading hypothesis was that the bytes belong to one of those, and the port
//! pointed out that the byte-span test already written settles it *without
//! anyone listening* if the enumeration is widened.
//!
//! So this widens it the whole way: rather than resolving cues one at a time
//! through the manifest, scan the stream itself for **every** trailer descriptor
//! — the `(id: u32be, 0x11, …)` pair whose id repeats at `+0x800`, which
//! `movie_voice` documents as the end of a cue's audio. Cue N's audio is
//! `[descriptor(N-1) .. descriptor(N)]`, so the full descriptor list IS the
//! complete cue partition of the stream, movie and mission alike.
//!
//! cargo run -p sylpheed-formats --example voice_stream_cue_map -- <disc-dir>
use sylpheed_formats::media::{self, DirectorySource, DiscSource};
use sylpheed_formats::slb::VoiceLang;
const DESC_MARK: u32 = 0x11;
const DESC_REPEAT: usize = 0x800;
const ID_MAX: u32 = 0x1_0000;
/// Every trailer descriptor in `buf`, as `(offset, id)`.
///
/// Same predicate `movie_voice::find_descriptor` uses — the id-repeat at +0x800
/// is what makes a false match inside XMA audio ~2^-64.
fn all_descriptors(buf: &[u8]) -> Vec<(usize, u32)> {
let be = |o: usize| u32::from_be_bytes([buf[o], buf[o + 1], buf[o + 2], buf[o + 3]]);
let mut out = Vec::new();
if buf.len() < DESC_REPEAT + 8 {
return out;
}
let end = buf.len() - (DESC_REPEAT + 4);
let mut o = 0;
while o <= end {
let id = be(o);
if (1..ID_MAX).contains(&id) && be(o + 4) == DESC_MARK && be(o + DESC_REPEAT) == id {
out.push((o, id));
}
o += 4;
}
out
}
fn main() {
let disc = std::env::args()
.nth(1)
.unwrap_or_else(|| std::env::var("SYLPHEED_DISC").expect("disc dir"));
let src = DirectorySource::new(std::path::PathBuf::from(&disc));
// The cue-name -> id registry, so a descriptor id can be named.
let tpak = src.open_pak("dat/tables.pak").expect("tables.pak");
let marker = "eng\\Movie\\VOICE_ADV.slb";
let registry = tpak
.entries()
.iter()
.find_map(|e| {
tpak.read(e)
.ok()
.filter(|b| b.windows(marker.len()).any(|w| w == marker.as_bytes()))
})
.expect("voice registry");
let ids = sylpheed_formats::movie_voice::registry_voice_ids(&registry);
let name_of: std::collections::HashMap<u32, String> =
ids.iter().map(|(n, &i)| (i, n.clone())).collect();
println!(
"registry: {} cue names, {} distinct ids",
ids.len(),
name_of.len()
);
// The 17 regions that open with a headerless stream, from the manifest.
let movies: Vec<String> = {
use sylpheed_formats::movie_manifest;
let manifest = tpak
.entries()
.iter()
.find_map(|e| tpak.read(e).ok().filter(|b| movie_manifest::is_manifest(b)))
.expect("manifest");
movie_manifest::parse(&manifest)
.into_iter()
.map(|m| m.movie)
.collect()
};
let mut regions = Vec::new();
for m in &movies {
if let Some((s, e)) = media::resolve_movie_voice_region(&src, m, VoiceLang::English) {
regions.push((m.clone(), s, e));
}
}
let lo = regions.iter().map(|r| r.1).min().unwrap();
let hi = regions.iter().map(|r| r.2).max().unwrap();
// Scan a window covering every region, with margin for cues either side.
let win_start = lo.saturating_sub(8 * 1024 * 1024) & !3;
let win_len = (hi - win_start + 8 * 1024 * 1024) as usize;
println!(
"scanning dat/sound {win_start}..{} ({:.1} MB)",
win_start + win_len as u64,
win_len as f64 / 1e6
);
let buf = src
.read_segment_range("dat/sound", win_start, win_len)
.expect("stream window");
let descs = all_descriptors(&buf);
println!("{} trailer descriptors found\n", descs.len());
let named = descs
.iter()
.filter(|(_, id)| name_of.contains_key(id))
.count();
println!(
" of those, {named} carry an id the registry names, {} do not\n",
descs.len() - named
);
// For each stream-opening region, name the cue that OWNS the leading span:
// the cue whose [prev_desc .. desc] interval contains it.
println!("leading span -> owning cue\n");
let mut verdicts: std::collections::BTreeMap<&str, usize> = Default::default();
for (m, s, e) in &regions {
let Ok(bytes) = src.read_segment_range("dat/sound", *s, (e - s) as usize) else {
continue;
};
let Some(fr) = bytes.windows(4).position(|w| w == b"RIFF") else {
continue;
};
if fr == 0 || sylpheed_formats::slb::bank_header_len(&bytes) == Some(fr) {
continue; // bank-header case, not ours
}
let (a, b) = (*s, *s + fr as u64); // the leading span, global offsets
// Descriptors bracketing the MIDDLE of the leading span.
let mid = (a + b) / 2;
let mid_local = (mid - win_start) as usize;
let before = descs
.iter()
.rev()
.find(|(o, _)| (*o as u64) < mid_local as u64);
let after = descs.iter().find(|(o, _)| *o >= mid_local);
let owner = after.map(|(_, id)| *id);
let owner_name = owner
.and_then(|id| name_of.get(&id).cloned())
.unwrap_or_else(|| {
owner
.map(|i| format!("<unnamed id {i}>"))
.unwrap_or("<none>".into())
});
let kind = if owner_name.starts_with("VOICE_D_") {
"MISSION line"
} else if owner_name.starts_with("VOICE_") {
"movie cue"
} else {
"unknown"
};
*verdicts.entry(kind).or_default() += 1;
println!(
" {m:8} lead {:8} B bracketed by desc@{:?} .. desc@{:?} owner {owner_name} [{kind}]",
b - a,
before.map(|(o, i)| (*o as u64 + win_start, *i)),
after.map(|(o, i)| (*o as u64 + win_start, *i)),
);
}
println!("\nverdicts: {verdicts:?}");
// WHY do exactly these 17 open mid-cue? `resolve_movie_voice_region` takes
// the predecessor trailer as the region start, but guards it with
// `end - start < 1_500_000` and falls back to the .slb TOC anchor when that
// fails. If the guard is the cause, then the stream-opening regions are
// exactly the cues whose true span exceeds the guard.
println!("\ncue span vs the 1.5 MB guard, and what the region actually starts at:\n");
let (mut over, mut under, mut over_is_stream, mut under_is_stream) = (0, 0, 0, 0);
for (m, s, e) in &regions {
let Ok(bytes) = src.read_segment_range("dat/sound", *s, (e - s) as usize) else {
continue;
};
let fr = bytes.windows(4).position(|w| w == b"RIFF");
let is_stream = matches!(fr, Some(f) if f > 0
&& sylpheed_formats::slb::bank_header_len(&bytes) != Some(f));
// The true predecessor trailer for this cue, from the full descriptor list.
let end_local = (*e - win_start) as usize;
let prev = descs
.iter()
.rev()
.find(|(o, _)| *o < end_local)
.map(|(o, _)| *o as u64 + win_start);
let Some(prev) = prev else { continue };
let span = e - prev;
let guarded = span >= 1_500_000;
if guarded {
over += 1;
if is_stream {
over_is_stream += 1
}
} else {
under += 1;
if is_stream {
under_is_stream += 1
}
}
if is_stream {
println!(
" {m:8} true cue span {span:8} B (> guard: {guarded}) region starts at {s}, \
true start {prev} -> {} B of the cue's own audio is OUTSIDE the region",
s.saturating_sub(prev)
);
}
}
println!("\ncues over the 1.5 MB guard: {over}, of which stream-opening: {over_is_stream}");
println!("cues under the guard: {under}, of which stream-opening: {under_is_stream}");
// How many streams is ONE cue stored as? The port measured that a region's
// leading chunk is the TAIL of its first full-length chunk, i.e. the cue is
// re-presented. Structurally that predicts a fixed number of stream starts
// inside a cue's TRUE span [desc(N-1) .. desc(N)] -- which is measurable
// from the bytes alone, with no decoder.
println!("\nstream starts inside each cue's TRUE span (desc(N-1)..desc(N)):\n");
let mut hist: std::collections::BTreeMap<usize, usize> = Default::default();
let mut hist_long: std::collections::BTreeMap<usize, usize> = Default::default();
for w in descs.windows(2) {
let (a, b) = (w[0].0, w[1].0);
if b <= a || b - a < 4096 {
continue;
}
let span = &buf[a..b];
// A stream start is a RIFF; plus the run before the first one, when it
// is not a bank header, is itself a stream.
let riffs = span
.windows(4)
.enumerate()
.filter(|(_, w)| *w == b"RIFF")
.count();
let lead_is_stream = match span.windows(4).position(|w| w == b"RIFF") {
Some(f) if f > 0 => sylpheed_formats::slb::bank_header_len(span) != Some(f),
_ => false,
};
let streams = riffs + usize::from(lead_is_stream);
*hist.entry(streams).or_default() += 1;
if b - a >= 1_500_000 {
*hist_long.entry(streams).or_default() += 1;
}
}
println!(" all inter-descriptor spans: {hist:?}");
println!(" spans >= 1.5 MB (the long cues): {hist_long:?}");
}