This branch predates CI on `main`. `cargo fmt --all` only; no behaviour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
101 lines
4.2 KiB
Rust
101 lines
4.2 KiB
Rust
//! What ARE the chunks a movie-voice region decodes to?
|
|
//!
|
|
//! The port reports a resolved voice region decoding to **three** chunks — two
|
|
//! of equal duration each spanning the whole movie, and a leading one that
|
|
//! "matches nothing" — and notes that this is the same 2+1 signature the BGM
|
|
//! banks showed before `bank_header_len` attributed the extra to a bank header.
|
|
//! Two different asset kinds with one signature is worth checking, because if
|
|
//! the same explanation applies then `bank_header_len` is incomplete, and if it
|
|
//! does not then the leading chunk is something we are discarding.
|
|
//!
|
|
//! `slb.rs`'s own doc comment already predicts the answer and disagrees with
|
|
//! "drop it": the header signature fires on 28 entries, all music banks, with
|
|
//! "zero false positives on the 7 993 mid-bank windows, WHERE THE LEADING
|
|
//! REGION IS REAL". A voice region is a mid-bank window by construction —
|
|
//! `resolve_movie_voice_region` starts it at the PREDECESSOR cue's trailer.
|
|
//!
|
|
//! cargo run -p sylpheed-formats --example voice_region_chunks -- <disc-dir>
|
|
use sylpheed_formats::media::{self, DirectorySource, DiscSource};
|
|
use sylpheed_formats::slb::{self, VoiceLang};
|
|
|
|
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));
|
|
|
|
// Disc-wide, not the four movies that motivated the question: every movie
|
|
// the manifest binds a voice to.
|
|
let movies: Vec<String> = {
|
|
use sylpheed_formats::movie_manifest;
|
|
let tpak = src.open_pak("dat/tables.pak").expect("tables.pak");
|
|
let manifest = tpak
|
|
.entries()
|
|
.iter()
|
|
.find_map(|e| tpak.read(e).ok().filter(|b| movie_manifest::is_manifest(b)))
|
|
.expect("movie manifest");
|
|
movie_manifest::parse(&manifest)
|
|
.into_iter()
|
|
.map(|m| m.movie)
|
|
.collect()
|
|
};
|
|
println!("{} movies in the manifest\n", movies.len());
|
|
let mut census: std::collections::BTreeMap<usize, usize> = Default::default();
|
|
let (mut with_header, mut with_leading, mut no_leading) = (0, 0, 0);
|
|
for movie in movies.iter().map(|s| s.as_str()) {
|
|
let Some((start, end)) = media::resolve_movie_voice_region(&src, movie, VoiceLang::English)
|
|
else {
|
|
continue;
|
|
};
|
|
let len = end - start;
|
|
let Ok(bytes) = src.read_segment_range("dat/sound", start, len as usize) else {
|
|
println!("{movie:8} region {start}..{end} unreadable");
|
|
continue;
|
|
};
|
|
// Where does the first RIFF sit? Everything before it is the leading
|
|
// headerless packet region.
|
|
let first_riff = bytes
|
|
.windows(4)
|
|
.position(|w| w == b"RIFF")
|
|
.map(|p| p as i64)
|
|
.unwrap_or(-1);
|
|
let hdr = slb::bank_header_len(&bytes);
|
|
let riffs = slb::to_xma_riffs(&bytes);
|
|
let kind = if first_riff <= 0 {
|
|
no_leading += 1;
|
|
"no leading region".to_string()
|
|
} else if hdr == Some(first_riff as usize) {
|
|
with_header += 1;
|
|
format!(
|
|
"BANK HEADER ({first_riff} B = {} packets exactly)",
|
|
first_riff / 2048
|
|
)
|
|
} else {
|
|
with_leading += 1;
|
|
*census.entry(first_riff as usize % 2048).or_default() += 1;
|
|
format!(
|
|
"leading STREAM ({first_riff} B = {} packets + {} B)",
|
|
first_riff / 2048,
|
|
first_riff % 2048
|
|
)
|
|
};
|
|
println!(
|
|
"{movie:10} {start:12}..{end:12} {len:9} B chunks {} {kind}",
|
|
riffs.len()
|
|
);
|
|
if let Ok(dir) = std::env::var("VOICE_CHUNK_DUMP") {
|
|
for (i, r) in riffs.iter().enumerate() {
|
|
let _ = std::fs::write(format!("{dir}/{movie}-chunk{i}.wav"), r);
|
|
}
|
|
}
|
|
}
|
|
println!(
|
|
"\n{with_header} region(s) open with a BANK HEADER (bank_header_len fires)\n\
|
|
{with_leading} open with a leading STREAM\n{no_leading} start at a RIFF"
|
|
);
|
|
println!("leading-stream length mod 2048, i.e. the derived data offset:");
|
|
for (rem, n) in &census {
|
|
println!(" {rem:5} B x{n}");
|
|
}
|
|
}
|