`mesh_consistency_disc.rs` had the only conflict: this branch added a `Sightings` type alias where #22 replaced the file's private `disc_root()` with the shared `common::disc_root`. Both kept — they are unrelated edits that happened to land in the same lines. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
115 lines
3.9 KiB
Rust
115 lines
3.9 KiB
Rust
//! Real-disc test: read a movie voice `.slb` out of the segmented `sound.pak`
|
|
//! and rebuild a decodable XMA1 RIFF. Skipped without `SYLPHEED_DISC`.
|
|
|
|
use std::fs::File;
|
|
use std::io::{Read, Seek, SeekFrom};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use sylpheed_formats::hash::name_hash;
|
|
use sylpheed_formats::slb::{self, VoiceLang};
|
|
use sylpheed_formats::PakArchive;
|
|
|
|
mod common;
|
|
use common::disc_root;
|
|
|
|
/// Read `[off, off+size)` from `dat/sound.p00..` (segments concatenated).
|
|
fn read_range(root: &Path, mut off: u64, size: usize) -> Vec<u8> {
|
|
let mut out = Vec::with_capacity(size);
|
|
let mut need = size;
|
|
for i in 0..100u32 {
|
|
if need == 0 {
|
|
break;
|
|
}
|
|
let seg = root.join(format!("dat/sound.p{i:02}"));
|
|
let Ok(meta) = std::fs::metadata(&seg) else {
|
|
break;
|
|
};
|
|
let len = meta.len();
|
|
if off >= len {
|
|
off -= len;
|
|
continue;
|
|
}
|
|
let mut f = File::open(&seg).unwrap();
|
|
f.seek(SeekFrom::Start(off)).unwrap();
|
|
let take = need.min((len - off) as usize);
|
|
let start = out.len();
|
|
out.resize(start + take, 0);
|
|
f.read_exact(&mut out[start..]).unwrap();
|
|
need -= take;
|
|
off = 0;
|
|
}
|
|
out
|
|
}
|
|
|
|
#[test]
|
|
fn reads_and_rebuilds_movie_voice_riff() {
|
|
let Some(root) = disc_root() else {
|
|
eprintln!("SKIP: set SYLPHEED_DISC");
|
|
return;
|
|
};
|
|
let toc = std::fs::read(root.join("dat/sound.pak")).unwrap();
|
|
let entries = PakArchive::parse_toc(&toc).unwrap();
|
|
assert_eq!(entries.len(), 9519);
|
|
|
|
for movie in ["S00A", "RT07A", "RT02D_1"] {
|
|
// RT02D_1 is a headerless variant; S00A/RT07A embed a RIFF.
|
|
let key = name_hash(&slb::movie_voice_name(movie, VoiceLang::English));
|
|
let idx = entries
|
|
.binary_search_by_key(&key, |e| e.name_hash)
|
|
.unwrap_or_else(|_| panic!("{movie} voice missing"));
|
|
let e = &entries[idx];
|
|
let bank = read_range(&root, e.offset as u64, e.comp_size as usize);
|
|
|
|
let riff = slb::to_xma_riff(&bank).expect("rebuild RIFF");
|
|
assert_eq!(&riff[0..4], b"RIFF", "{movie}");
|
|
assert_eq!(&riff[8..12], b"WAVE", "{movie}");
|
|
// fmt chunk advertises XMA1 (tag 0x0165).
|
|
let fpos = riff.windows(4).position(|w| w == b"fmt ").unwrap();
|
|
let tag = u16::from_le_bytes([riff[fpos + 8], riff[fpos + 9]]);
|
|
assert_eq!(tag, 0x0165, "{movie} should be XMA1");
|
|
// data chunk carries the XMA payload.
|
|
let dpos = riff.windows(4).position(|w| w == b"data").unwrap();
|
|
let dsz = u32::from_le_bytes([
|
|
riff[dpos + 4],
|
|
riff[dpos + 5],
|
|
riff[dpos + 6],
|
|
riff[dpos + 7],
|
|
]) as usize;
|
|
assert!(dsz > 10_000, "{movie} data too small: {dsz}");
|
|
assert_eq!(riff.len(), dpos + 8 + dsz, "{movie} data length consistent");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn enumerates_voice_clips_from_sounds_tbl() {
|
|
let Some(root) = disc_root() else {
|
|
eprintln!("SKIP: set SYLPHEED_DISC");
|
|
return;
|
|
};
|
|
let pak = PakArchive::open(root.join("dat/tables.pak")).unwrap();
|
|
let tbl = pak.read_by_name("eng\\sounds.tbl").unwrap().unwrap();
|
|
let clips = slb::list_voice_clips(&tbl, VoiceLang::English);
|
|
assert!(
|
|
clips.len() > 1000,
|
|
"expected many voice clips, got {}",
|
|
clips.len()
|
|
);
|
|
// Every clip is an eng voice path present in sound.pak.
|
|
let sound = std::fs::read(root.join("dat/sound.pak")).unwrap();
|
|
let keys: std::collections::HashSet<u32> = PakArchive::parse_toc(&sound)
|
|
.unwrap()
|
|
.iter()
|
|
.map(|e| e.name_hash)
|
|
.collect();
|
|
let present = clips
|
|
.iter()
|
|
.filter(|c| keys.contains(&name_hash(&c.name)))
|
|
.count();
|
|
assert!(
|
|
present as f32 / clips.len() as f32 > 0.95,
|
|
"{present}/{} clips resolve in sound.pak",
|
|
clips.len()
|
|
);
|
|
assert!(clips.iter().any(|c| c.speaker == "ADAN"));
|
|
}
|