diff --git a/src/scripts/bgi/archive/bse.rs b/src/scripts/bgi/archive/bse.rs new file mode 100644 index 0000000..11bdd51 --- /dev/null +++ b/src/scripts/bgi/archive/bse.rs @@ -0,0 +1,165 @@ +use crate::ext::io::*; +use crate::scripts::base::*; +use crate::types::*; +use anyhow::Result; +use std::io::{Read, Seek}; + +pub trait BseGenerator { + fn next_key(&mut self) -> u32; +} + +fn rtor(v: u32, count: u8) -> u32 { + let count = count & 0x1F; + return v >> count | v << (32 - count); +} + +fn rot_byte_r(v: u8, count: u8) -> u8 { + let count = count & 0x07; + return v >> count | v << (8 - count); +} + +fn rot_byte_l(v: u8, count: u8) -> u8 { + let count = count & 0x07; + return v << count | v >> (8 - count); +} + +pub struct BseGenerator100 { + key: u32, +} + +impl BseGenerator100 { + pub fn new(key: u32) -> Self { + BseGenerator100 { key } + } +} + +impl BseGenerator for BseGenerator100 { + fn next_key(&mut self) -> u32 { + let key = self + .key + .overflowing_mul(257) + .0 + .overflowing_shr(8) + .0 + .overflowing_add(self.key.overflowing_mul(97).0) + .0 + .overflowing_add(23) + .0 + ^ 0xA6CD9B75; + self.key = rtor(key, 16); + self.key + } +} + +pub struct BseGenerator101 { + key: u32, +} + +impl BseGenerator101 { + pub fn new(key: u32) -> Self { + BseGenerator101 { key } + } +} + +impl BseGenerator for BseGenerator101 { + fn next_key(&mut self) -> u32 { + let key = self + .key + .overflowing_mul(127) + .0 + .overflowing_shr(7) + .0 + .overflowing_add(self.key.overflowing_mul(83).0) + .0 + .overflowing_add(53) + .0 + ^ 0xB97A7E5C; + self.key = rtor(key, 16); + self.key + } +} + +pub struct BseReader Option<&'static ScriptType>> { + reader: T, + header: [u8; 0x40], + detect: F, + pos: u64, + filename: String, +} + +impl Option<&'static ScriptType>> BseReader { + pub fn new(mut reader: T, detect: F, filename: &str) -> Result { + let version = reader.peek_u16_at(0x8)?; + if version != 0x0100 && version != 0x0101 { + return Err(anyhow::anyhow!("Unsupported BSE version: {}", version)); + } + let _checksum = reader.peek_u16_at(0xA)?; + let key: u32 = reader.peek_u32_at(0xC)?; + let mut header = [0u8; 0x40]; + reader.peek_extract_at(0x10, &mut header)?; + let generator: Box = if version == 0x0100 { + Box::new(BseGenerator100::new(key)) + } else { + Box::new(BseGenerator101::new(key)) + }; + Self::decode_header(&mut header, generator)?; + Ok(BseReader { + reader, + header, + detect, + pos: 0, + filename: filename.to_string(), + }) + } + + fn decode_header(data: &mut [u8; 0x40], mut generator: Box) -> Result<()> { + let mut decoded = [false; 0x40]; + for _ in 0..0x40 { + let mut dst = generator.next_key() as usize & 0x3F; + while decoded[dst] { + dst = (dst + 1) & 0x3F; + } + let shift = (generator.next_key() & 7) as u8; + let right_shift = generator.next_key() & 1 == 0; + let symbol = data[dst].overflowing_sub(generator.next_key() as u8).0; + data[dst] = if right_shift { + rot_byte_r(symbol, shift) + } else { + rot_byte_l(symbol, shift) + }; + decoded[dst] = true; + } + Ok(()) + } +} + +impl Option<&'static ScriptType>> Read + for BseReader +{ + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + if self.pos < 0x40 { + let bytes_to_read = 0x40 - self.pos; + buf[..bytes_to_read as usize].copy_from_slice(&self.header[self.pos as usize..]); + self.pos += bytes_to_read; + Ok(bytes_to_read as usize) + } else { + let true_pos = self.pos + 0x10; + self.reader.seek(std::io::SeekFrom::Start(true_pos))?; + let bytes_read = self.reader.read(buf)?; + self.pos += bytes_read as u64; + Ok(bytes_read) + } + } +} + +impl Option<&'static ScriptType>> ArchiveContent + for BseReader +{ + fn name(&self) -> &str { + &self.filename + } + + fn script_type(&self) -> Option<&ScriptType> { + (self.detect)(&self.header, self.header.len(), &self.filename) + } +} diff --git a/src/scripts/bgi/archive/mod.rs b/src/scripts/bgi/archive/mod.rs index 59a6efe..3346cbb 100644 --- a/src/scripts/bgi/archive/mod.rs +++ b/src/scripts/bgi/archive/mod.rs @@ -1,3 +1,4 @@ +mod bse; mod dsc; pub mod v1; pub mod v2; diff --git a/src/scripts/bgi/archive/v2.rs b/src/scripts/bgi/archive/v2.rs index 7d2daf2..5c61fe2 100644 --- a/src/scripts/bgi/archive/v2.rs +++ b/src/scripts/bgi/archive/v2.rs @@ -1,3 +1,4 @@ +use super::bse::*; use super::dsc::*; use crate::ext::io::*; use crate::scripts::base::*; @@ -6,7 +7,7 @@ use crate::utils::encoding::encode_string; use crate::utils::struct_pack::*; use anyhow::Result; use msg_tool_macro::*; -use std::io::{Read, Seek, Write}; +use std::io::{Read, Seek, SeekFrom, Write}; use std::sync::{Arc, Mutex}; #[derive(Debug)] @@ -132,7 +133,7 @@ impl Read for Entry { format!("Failed to lock mutex: {}", e), ) })?; - reader.seek(std::io::SeekFrom::Start( + reader.seek(SeekFrom::Start( self.base_offset + self.header.offset as u64 + self.pos as u64, ))?; let bytes_read = buf.len().min(self.header.size as usize - self.pos); @@ -145,6 +146,46 @@ impl Read for Entry { } } +impl Seek for Entry { + fn seek(&mut self, pos: SeekFrom) -> std::io::Result { + let new_pos = match pos { + SeekFrom::Start(offset) => offset as usize, + SeekFrom::End(offset) => { + if offset < 0 { + if (-offset) as usize > self.header.size as usize { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Seek from end exceeds file length", + )); + } + self.header.size as usize - (-offset) as usize + } else { + self.header.size as usize + offset as usize + } + } + SeekFrom::Current(offset) => { + if offset < 0 { + if (-offset) as usize > self.pos { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Seek from current exceeds current position", + )); + } + self.pos.saturating_sub((-offset) as usize) + } else { + self.pos + offset as usize + } + } + }; + self.pos = new_pos; + Ok(self.pos as u64) + } + + fn stream_position(&mut self) -> std::io::Result { + Ok(self.pos as u64) + } +} + struct MemEntry { name: String, data: MemReader, @@ -314,11 +355,41 @@ impl<'a, T: Iterator, R: Read + Seek + 'static> Iterat ))); } }; + let reader = MemReader::new(decoded); + if reader.data.starts_with(b"BSE 1.") { + match BseReader::new(reader, detect_script_type, &entry.header.filename) { + Ok(bse_reader) => { + return Some(Ok(Box::new(bse_reader))); + } + Err(e) => { + return Some(Err(anyhow::anyhow!( + "Failed to create BSE reader for '{}': {}", + entry.header.filename, + e + ))); + } + }; + } return Some(Ok(Box::new(MemEntry { name: entry.header.filename.clone(), - data: MemReader::new(decoded), + data: reader, }))); } + if buf.starts_with(b"BSE 1.") { + let filename = entry.header.filename.clone(); + match BseReader::new(entry, detect_script_type, &filename) { + Ok(bse_reader) => { + return Some(Ok(Box::new(bse_reader))); + } + Err(e) => { + return Some(Err(anyhow::anyhow!( + "Failed to create BSE reader for '{}': {}", + &filename, + e + ))); + } + }; + } entry.script_type = detect_script_type(&buf, buf.len(), &entry.header.filename).cloned(); Some(Ok(Box::new(entry))) }