use crate::ext::io::*; use crate::scripts::base::*; use crate::types::*; 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::sync::{Arc, Mutex}; #[derive(Debug)] pub struct BgiArchiveBuilder {} impl BgiArchiveBuilder { pub const fn new() -> Self { BgiArchiveBuilder {} } } impl ScriptBuilder for BgiArchiveBuilder { fn default_encoding(&self) -> Encoding { Encoding::Cp932 } fn default_archive_encoding(&self) -> Option { Some(Encoding::Cp932) } fn build_script( &self, data: Vec, _filename: &str, _encoding: Encoding, archive_encoding: Encoding, config: &ExtraConfig, ) -> Result> { Ok(Box::new(BgiArchive::new( MemReader::new(data), archive_encoding, config, )?)) } fn build_script_from_file( &self, _filename: &str, _encoding: Encoding, archive_encoding: Encoding, config: &ExtraConfig, ) -> Result> { if _filename == "-" { let data = crate::utils::files::read_file(_filename)?; Ok(Box::new(BgiArchive::new( MemReader::new(data), archive_encoding, config, )?)) } else { let f = std::fs::File::open(_filename)?; let reader = std::io::BufReader::new(f); Ok(Box::new(BgiArchive::new(reader, archive_encoding, config)?)) } } fn build_script_from_reader( &self, reader: Box, _filename: &str, _encoding: Encoding, archive_encoding: Encoding, config: &ExtraConfig, ) -> Result> { Ok(Box::new(BgiArchive::new(reader, archive_encoding, config)?)) } fn extensions(&self) -> &'static [&'static str] { &["arc"] } fn script_type(&self) -> &'static ScriptType { &ScriptType::BGIArcV2 } fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option { if buf_len >= 12 && buf.starts_with(b"BURIKO ARC20") { return Some(1); } None } fn is_archive(&self) -> bool { true } } #[derive(Clone, Debug, StructPack, StructUnpack)] struct BgiFileHeader { #[fstring = 0x60] filename: String, offset: u32, size: u32, #[fvec = 8] _unk: Vec, #[fvec = 16] _padding: Vec, } struct Entry { header: BgiFileHeader, reader: Arc>, pos: usize, base_offset: u64, } impl ArchiveContent for Entry { fn name(&self) -> &str { &self.header.filename } } impl Read for Entry { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { let mut reader = self.reader.lock().map_err(|e| { std::io::Error::new( std::io::ErrorKind::Other, format!("Failed to lock mutex: {}", e), ) })?; reader.seek(std::io::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); if bytes_read == 0 { return Ok(0); } let bytes_read = reader.read(&mut buf[..bytes_read])?; self.pos += bytes_read; Ok(bytes_read) } } #[derive(Debug)] pub struct BgiArchive { reader: Arc>, file_count: u32, entries: Vec, } impl BgiArchive { pub fn new(mut reader: T, archive_encoding: Encoding, _config: &ExtraConfig) -> Result { let mut header = [0u8; 12]; reader.read_exact(&mut header)?; if !header.starts_with(b"BURIKO ARC20") { return Err(anyhow::anyhow!("Invalid BGI archive header")); } let file_count = reader.read_u32()?; let mut entries = Vec::with_capacity(file_count as usize); for _ in 0..file_count { let entry = BgiFileHeader::unpack(&mut reader, false, archive_encoding)?; entries.push(entry); } Ok(BgiArchive { reader: Arc::new(Mutex::new(reader)), file_count, entries, }) } } impl Script for BgiArchive { fn default_output_script_type(&self) -> OutputScriptType { OutputScriptType::Json } fn default_format_type(&self) -> FormatOptions { FormatOptions::None } fn is_archive(&self) -> bool { true } fn iter_archive<'a>(&'a mut self) -> Result> + 'a>> { Ok(Box::new( self.entries.iter().map(|e| Ok(e.filename.clone())), )) } fn iter_archive_mut<'a>( &'a mut self, ) -> Result>> + 'a>> { Ok(Box::new(BgiArchiveIter { entries: self.entries.iter(), reader: self.reader.clone(), base_offset: 16 + (self.file_count as u64 * 32), })) } } struct BgiArchiveIter<'a, T: Iterator, R: Read + Seek> { entries: T, reader: Arc>, base_offset: u64, } impl<'a, T: Iterator, R: Read + Seek + 'static> Iterator for BgiArchiveIter<'a, T, R> { type Item = Result>; fn next(&mut self) -> Option { let entry = match self.entries.next() { Some(e) => e, None => return None, }; let entry = Entry { header: entry.clone(), reader: self.reader.clone(), pos: 0, base_offset: self.base_offset, }; Some(Ok(Box::new(entry))) } }