//! 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::PathBuf; use sylpheed_formats::hash::name_hash; use sylpheed_formats::slb::{self, VoiceLang}; use sylpheed_formats::PakArchive; fn disc_root() -> Option { let p = PathBuf::from(std::env::var("SYLPHEED_DISC").ok()?); p.join("dat").is_dir().then_some(p) } /// Read `[off, off+size)` from `dat/sound.p00..` (segments concatenated). fn read_range(root: &PathBuf, mut off: u64, size: usize) -> Vec { 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 = 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")); }