Skip to main content

siglus_assets/
ovk.rs

1//! OVK (Ogg/Vorbis pack) and OWP (XORed Ogg) helpers.
2//!
3//! Helpers for OVK (Ogg/Vorbis pack) and OWP (XORed Ogg) audio formats.
4
5use crate::ogg_xor::{validate_subrange, BoundedFile};
6use crate::vorbis;
7use anyhow::{bail, Result};
8use std::fs::File;
9use std::io::{Read, Seek, SeekFrom};
10use std::path::{Path, PathBuf};
11
12#[derive(Debug, Clone, Copy)]
13pub struct OvkEntry {
14    pub size: u32,
15    pub offset: u32,
16    pub no: u32,
17    pub sample_count: u32,
18}
19
20#[derive(Debug, Clone)]
21pub struct OvkPack {
22    path: PathBuf,
23    entries: Vec<OvkEntry>,
24    file_len: u64,
25}
26
27impl OvkPack {
28    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
29        let path = path.as_ref().to_path_buf();
30        let mut f = File::open(&path)?;
31        let file_len = f.metadata()?.len();
32
33        let mut head = [0u8; 4];
34        f.read_exact(&mut head)?;
35        let count = u32::from_le_bytes(head) as usize;
36        if count == 0 {
37            bail!("OVK: zero entries");
38        }
39        let mut entries = Vec::with_capacity(count);
40        for _ in 0..count {
41            let mut buf = [0u8; 16];
42            f.read_exact(&mut buf)?;
43            let size = u32::from_le_bytes(buf[0..4].try_into().unwrap());
44            let offset = u32::from_le_bytes(buf[4..8].try_into().unwrap());
45            let no = u32::from_le_bytes(buf[8..12].try_into().unwrap());
46            let smp = u32::from_le_bytes(buf[12..16].try_into().unwrap());
47            entries.push(OvkEntry {
48                size,
49                offset,
50                no,
51                sample_count: smp,
52            });
53        }
54        // Basic bounds checks (size==0 is allowed but pointless; treat as empty).
55        for (i, e) in entries.iter().enumerate() {
56            if e.size == 0 {
57                continue;
58            }
59            validate_subrange(file_len, e.offset as u64, e.size as u64)
60                .map_err(|err| anyhow::anyhow!("OVK entry[{i}] out of range: {err}"))?;
61        }
62
63        Ok(Self {
64            path,
65            entries,
66            file_len,
67        })
68    }
69
70    pub fn entries(&self) -> &[OvkEntry] {
71        &self.entries
72    }
73
74    pub fn get(&self, idx: usize) -> Option<OvkEntry> {
75        self.entries.get(idx).copied()
76    }
77
78    /// Create a bounded reader for an entry.
79    pub fn open_entry_stream(&self, idx: usize) -> Result<BoundedFile> {
80        let e = self
81            .entries
82            .get(idx)
83            .copied()
84            .ok_or_else(|| anyhow::anyhow!("OVK: entry index out of range: {idx}"))?;
85        if e.size == 0 {
86            bail!("OVK: entry[{idx}] has zero size");
87        }
88        BoundedFile::open(&self.path, e.offset as u64, e.size as u64, None)
89    }
90
91    /// Extract an entry into memory.
92    pub fn extract_entry(&self, idx: usize) -> Result<Vec<u8>> {
93        self.open_entry_stream(idx)?.read_all()
94    }
95
96    /// Decode an entry (expected to be Ogg/Vorbis) into interleaved PCM16.
97    pub fn decode_entry_vorbis_pcm16(&self, idx: usize) -> Result<vorbis::Pcm16> {
98        let s = self.open_entry_stream(idx)?;
99        vorbis::decode_ogg_vorbis_reader(s)
100    }
101
102    /// Decode an entry (expected to be Ogg/Vorbis) and return a WAV (PCM16) buffer.
103    pub fn decode_entry_vorbis_wav(&self, idx: usize) -> Result<Vec<u8>> {
104        let s = self.open_entry_stream(idx)?;
105        vorbis::decode_ogg_vorbis_reader_to_wav(s)
106    }
107}
108
109/// OWP: XOR-obfuscated Ogg file. The original engine uses key 0x39.
110#[derive(Debug, Clone)]
111pub struct OwpFile {
112    path: PathBuf,
113    file_len: u64,
114    pub xor_key: u8,
115}
116
117impl OwpFile {
118    pub const DEFAULT_XOR_KEY: u8 = 0x39;
119
120    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
121        let path = path.as_ref().to_path_buf();
122        let file_len = File::open(&path)?.metadata()?.len();
123        Ok(Self {
124            path,
125            file_len,
126            xor_key: Self::DEFAULT_XOR_KEY,
127        })
128    }
129
130    pub fn open_stream(&self) -> Result<BoundedFile> {
131        BoundedFile::open(&self.path, 0, self.file_len, Some(self.xor_key))
132    }
133
134    pub fn decrypt_to_vec(&self) -> Result<Vec<u8>> {
135        self.open_stream()?.read_all()
136    }
137
138    /// Decode the XORed Ogg/Vorbis file into interleaved PCM16.
139    pub fn decode_vorbis_pcm16(&self) -> Result<vorbis::Pcm16> {
140        let s = self.open_stream()?;
141        vorbis::decode_ogg_vorbis_reader(s)
142    }
143
144    /// Decode the XORed Ogg/Vorbis file and return a WAV (PCM16) buffer.
145    pub fn decode_vorbis_wav(&self) -> Result<Vec<u8>> {
146        let s = self.open_stream()?;
147        vorbis::decode_ogg_vorbis_reader_to_wav(s)
148    }
149}