slb: derive the leading-stream data offset instead of assuming 1392
HEADERLESS_DATA_OFFSET is the value the offset takes in <lang>\etc\, not a property of the format. The leading stream is a whole number of 2048-byte XMA1 packets ending at the first RIFF, so its start is first_riff % XMA1_PACKET. Disc-wide that takes four values -- 1392, 1468, 1600, 1728 -- varying by language and subdirectory. Verified by decoding, not by arithmetic: on a random 140-bank sample with a non-empty leading region, the derived offset yields more audio in 85, identical in 54 (the eng\etc controls, where it must and does reproduce the old behaviour) and less in 1. Median gain among the improved is 70x -- eng\Voice\VOICE_TCAF_592 goes 1506 -> 97152 bytes, jpn 2910 -> 127178. This withdraws my own claim from earlier today that the Japanese banks were a different undecoded layout. They are the same format with a different offset; I had treated a constant derived from one subdirectory as a property of the format. The same error was hiding the identical defect in 1873 eng\Voice banks.
This commit is contained in:
@@ -131,6 +131,19 @@ fn parse_voice_clip(name: &str) -> VoiceClip {
|
||||
/// that yields the full track for segment banks while the clamp drops the
|
||||
/// duplicate takes for alternate-take banks. (Dynamic RE via Canary file-I/O
|
||||
/// tracing confirmed the movie→voice binding; this fixes the *decode* of `RT*`.)
|
||||
/// Where a bank's leading headerless packet stream starts.
|
||||
///
|
||||
/// The stream is a whole number of 2048-byte XMA1 packets ending at the first
|
||||
/// `RIFF`, so its start is simply `first_riff % XMA1_PACKET`. Disc-wide that
|
||||
/// lands on 1392, 1468, 1600 or 1728 depending on language and subdirectory —
|
||||
/// [`HEADERLESS_DATA_OFFSET`] is just the `<lang>\etc\` case. Measured over a
|
||||
/// 140-bank sample, deriving the offset instead of assuming 1392 recovers a
|
||||
/// median **70×** more decoded audio and never less except in one bank where
|
||||
/// neither offset decodes (see `docs/re/structures/slb-data-offset.md`).
|
||||
pub fn leading_data_offset(first_riff: usize) -> usize {
|
||||
first_riff % XMA1_PACKET
|
||||
}
|
||||
|
||||
pub fn to_xma_riffs(slb: &[u8]) -> Vec<Vec<u8>> {
|
||||
let mut out = Vec::new();
|
||||
let first_riff = find(slb, b"RIFF", 0);
|
||||
@@ -150,7 +163,12 @@ pub fn to_xma_riffs(slb: &[u8]) -> Vec<Vec<u8>> {
|
||||
// trailing fragment survived.
|
||||
//
|
||||
// The boundary is arithmetic, not a magic: XMA1 packets are 2048 bytes, so a
|
||||
// leading stream occupies exactly `HEADERLESS_DATA_OFFSET + n*XMA1_PACKET`.
|
||||
// leading stream is a whole number of packets ending at the first `RIFF`.
|
||||
// Its START is therefore `first_riff % XMA1_PACKET` — **not** the constant
|
||||
// `HEADERLESS_DATA_OFFSET`, which is only the value that offset happens to
|
||||
// take in `<lang>\etc\`. Disc-wide it takes four values (1392, 1468, 1600,
|
||||
// 1728), varying by language and subdirectory, and assuming 1392 starts the
|
||||
// decode mid-packet everywhere else. See docs/re/structures/slb-data-offset.md.
|
||||
// It decodes as **mono** — at two channels every bank yields exactly 1792
|
||||
// bytes, one frame, whatever its size.
|
||||
//
|
||||
@@ -162,8 +180,9 @@ pub fn to_xma_riffs(slb: &[u8]) -> Vec<Vec<u8>> {
|
||||
// bound to `VOICE_D_453`/`454`, i.e. precisely the broken ones — and
|
||||
// ≤0.25 s to 66 of the rest. Callers clamp to the movie length anyway.
|
||||
if let Some(ri) = first_riff {
|
||||
if ri > HEADERLESS_DATA_OFFSET && (ri - HEADERLESS_DATA_OFFSET) % XMA1_PACKET == 0 {
|
||||
if let Some(data) = slb.get(HEADERLESS_DATA_OFFSET..ri) {
|
||||
let start = leading_data_offset(ri);
|
||||
if ri > start {
|
||||
if let Some(data) = slb.get(start..ri) {
|
||||
if data.iter().any(|b| *b != 0) {
|
||||
out.push(build_riff(&synth_xma1_fmt(1, 0, 48000), data));
|
||||
}
|
||||
|
||||
@@ -84,3 +84,72 @@ fn all_zero_leading_region_is_skipped() {
|
||||
// Two sub-waves, both from the RIFF section — no synthesised third.
|
||||
assert_eq!(slb::to_xma_riffs(&b).len(), 2);
|
||||
}
|
||||
|
||||
fn bank_named(root: &Path, path: &str) -> Vec<u8> {
|
||||
let snd = PakArchive::open(root.join("dat/sound.pak")).expect("sound.pak");
|
||||
let entry = snd.find_by_name(path).unwrap_or_else(|| panic!("{path} present"));
|
||||
snd.read(entry).expect("read")
|
||||
}
|
||||
|
||||
/// `HEADERLESS_DATA_OFFSET` is the `<lang>\etc\` case, not the format.
|
||||
///
|
||||
/// The leading stream is a whole number of packets ending at the first `RIFF`,
|
||||
/// so its start is `first_riff % XMA1_PACKET`. Disc-wide that takes four values
|
||||
/// and only 1392 matches the old constant — assuming it elsewhere starts the
|
||||
/// decode mid-packet. See docs/re/structures/slb-data-offset.md.
|
||||
#[test]
|
||||
fn leading_data_offset_is_derived_not_assumed() {
|
||||
skip_without_disc!(root);
|
||||
// (bank, expected derived offset). The `etc` banks must still land on the
|
||||
// old constant — that is the no-regression half of the test.
|
||||
for (path, want) in [
|
||||
("eng\\etc\\VOICE_D_452.slb", 1392usize),
|
||||
("eng\\etc\\VOICE_D_453.slb", 1392),
|
||||
("eng\\Voice\\VOICE_TCAF_592.slb", 1468),
|
||||
("jpn\\Voice\\VOICE_TCAF_592.slb", 1728),
|
||||
("jpn\\etc\\VOICE_D_452.slb", 1600),
|
||||
] {
|
||||
let b = bank_named(&root, path);
|
||||
let ri = b.windows(4).position(|w| w == b"RIFF").expect("has a RIFF");
|
||||
let got = slb::leading_data_offset(ri);
|
||||
assert_eq!(got, want, "{path}: derived offset");
|
||||
assert_eq!(
|
||||
(ri - got) % slb::XMA1_PACKET,
|
||||
0,
|
||||
"{path}: leading stream is not a whole packet count"
|
||||
);
|
||||
assert!(
|
||||
got == slb::HEADERLESS_DATA_OFFSET || got > slb::HEADERLESS_DATA_OFFSET,
|
||||
"{path}: offsets below the old constant are unexplained"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The banks the old constant mis-decoded now carry a leading sub-wave, and the
|
||||
/// ones it decoded correctly are untouched.
|
||||
#[test]
|
||||
fn derived_offset_recovers_voice_banks_without_regressing_etc() {
|
||||
skip_without_disc!(root);
|
||||
for path in ["eng\\Voice\\VOICE_TCAF_592.slb", "jpn\\Voice\\VOICE_TCAF_592.slb"] {
|
||||
let b = bank_named(&root, path);
|
||||
let ri = b.windows(4).position(|w| w == b"RIFF").expect("has a RIFF");
|
||||
// Under the old constant this leading region was not a whole packet
|
||||
// count, so `to_xma_riffs` emitted no leading sub-wave at all.
|
||||
assert_ne!(
|
||||
(ri - slb::HEADERLESS_DATA_OFFSET) % slb::XMA1_PACKET,
|
||||
0,
|
||||
"{path}: expected the OLD constant to mis-align here"
|
||||
);
|
||||
let riffs = slb::to_xma_riffs(&b);
|
||||
assert!(
|
||||
riffs.len() >= 2,
|
||||
"{path}: expected a leading sub-wave plus at least one RIFF, got {}",
|
||||
riffs.len()
|
||||
);
|
||||
}
|
||||
// Control: an `etc` bank still produces what it did before.
|
||||
let b = bank_named(&root, "eng\\etc\\VOICE_D_452.slb");
|
||||
assert_eq!(slb::leading_data_offset(
|
||||
b.windows(4).position(|w| w == b"RIFF").unwrap()),
|
||||
slb::HEADERLESS_DATA_OFFSET);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user