From b98380fcd325fc6a78c955b60b14fc9c502729cf Mon Sep 17 00:00:00 2001 From: lifegpc Date: Mon, 16 Jun 2025 17:15:30 +0800 Subject: [PATCH] Add support to DSC file in BSE encryped files --- src/scripts/bgi/archive/bse.rs | 10 +++- src/scripts/bgi/archive/dsc.rs | 88 ++++++++++++++++++++++++++++++++++ src/scripts/bgi/archive/mod.rs | 2 +- src/scripts/bgi/archive/v1.rs | 44 ++++++++++++++++- src/scripts/bgi/archive/v2.rs | 44 ++++++++++++++++- src/scripts/mod.rs | 2 + src/types.rs | 4 ++ 7 files changed, 189 insertions(+), 5 deletions(-) diff --git a/src/scripts/bgi/archive/bse.rs b/src/scripts/bgi/archive/bse.rs index 88f314d..5896b26 100644 --- a/src/scripts/bgi/archive/bse.rs +++ b/src/scripts/bgi/archive/bse.rs @@ -119,6 +119,10 @@ impl Option<&'static ScriptType>> B } Ok(()) } + + pub fn is_dsc(&self) -> bool { + self.header.starts_with(b"DSC FORMAT 1.00") + } } impl Option<&'static ScriptType>> Read @@ -126,8 +130,10 @@ impl Option<&'static ScriptType>> R { 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..]); + let bytes_to_read = (0x40 - self.pos).min(buf.len() as u64); + buf[..bytes_to_read as usize].copy_from_slice( + &self.header[self.pos as usize..self.pos as usize + bytes_to_read as usize], + ); self.pos += bytes_to_read; Ok(bytes_to_read as usize) } else { diff --git a/src/scripts/bgi/archive/dsc.rs b/src/scripts/bgi/archive/dsc.rs index e2e8b95..5879679 100644 --- a/src/scripts/bgi/archive/dsc.rs +++ b/src/scripts/bgi/archive/dsc.rs @@ -1,7 +1,10 @@ use crate::ext::io::*; use crate::ext::vec::*; +use crate::scripts::base::*; +use crate::types::*; use crate::utils::bit_stream::*; use anyhow::Result; +use std::io::Write; #[derive(Debug)] struct HuffmanCode { @@ -204,3 +207,88 @@ impl<'a> DscDecoder<'a> { v1 as u8 } } + +#[derive(Debug)] +pub struct DscBuilder {} + +impl DscBuilder { + pub fn new() -> Self { + DscBuilder {} + } +} + +impl ScriptBuilder for DscBuilder { + fn default_encoding(&self) -> Encoding { + Encoding::Cp932 + } + + fn default_archive_encoding(&self) -> Option { + Some(Encoding::Cp932) + } + + fn build_script( + &self, + buf: Vec, + _filename: &str, + _encoding: Encoding, + _archive_encoding: Encoding, + _config: &ExtraConfig, + ) -> Result> { + Ok(Box::new(Dsc::new(buf)?)) + } + + fn extensions(&self) -> &'static [&'static str] { + &[] + } + + fn script_type(&self) -> &'static ScriptType { + &ScriptType::BGIDsc + } + + fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option { + if buf_len >= 16 && buf.starts_with(b"DSC FORMAT 1.00\0") { + return Some(255); + } + None + } +} + +#[derive(Debug)] +pub struct Dsc { + data: Vec, +} + +impl Dsc { + pub fn new(buf: Vec) -> Result { + if buf.len() < 16 || !buf.starts_with(b"DSC FORMAT 1.00\0") { + return Err(anyhow::anyhow!("Invalid DSC format")); + } + let decoder = DscDecoder::new(&buf)?; + let data = decoder.unpack()?; + Ok(Dsc { data }) + } +} + +impl Script for Dsc { + fn default_output_script_type(&self) -> OutputScriptType { + OutputScriptType::Custom + } + + fn is_output_supported(&self, output: OutputScriptType) -> bool { + matches!(output, OutputScriptType::Custom) + } + + fn default_format_type(&self) -> FormatOptions { + FormatOptions::None + } + + fn custom_output_extension(&self) -> &'static str { + "unk" + } + + fn custom_export(&self, filename: &std::path::Path, _encoding: Encoding) -> Result<()> { + let mut f = std::fs::File::create(filename)?; + f.write_all(&self.data)?; + Ok(()) + } +} diff --git a/src/scripts/bgi/archive/mod.rs b/src/scripts/bgi/archive/mod.rs index 3346cbb..717da69 100644 --- a/src/scripts/bgi/archive/mod.rs +++ b/src/scripts/bgi/archive/mod.rs @@ -1,4 +1,4 @@ mod bse; -mod dsc; +pub mod dsc; pub mod v1; pub mod v2; diff --git a/src/scripts/bgi/archive/v1.rs b/src/scripts/bgi/archive/v1.rs index 900c7fd..9db4d63 100644 --- a/src/scripts/bgi/archive/v1.rs +++ b/src/scripts/bgi/archive/v1.rs @@ -309,6 +309,10 @@ fn detect_script_type(buf: &[u8], buf_len: usize, filename: &str) -> Option<&'st if buf_len >= 28 && buf.starts_with(b"BurikoCompiledScriptVer1.00\0") { return Some(&ScriptType::BGI); } + #[cfg(feature = "bgi-img")] + if buf_len >= 16 && buf.starts_with(b"CompressedBG___") { + return Some(&ScriptType::BGICbg); + } let filename = filename.to_lowercase(); if filename.ends_with("._bp") { return Some(&ScriptType::BGIBp); @@ -435,7 +439,45 @@ impl<'a, T: Iterator, R: Read + Seek + 'static> Iterat #[cfg(not(feature = "bgi-img"))] let detect = detect_script_type; match BseReader::new(entry, detect, &filename) { - Ok(bse_reader) => { + Ok(mut bse_reader) => { + if bse_reader.is_dsc() { + let data = match bse_reader.data() { + Ok(data) => data, + Err(e) => { + return Some(Err(anyhow::anyhow!( + "Failed to read BSE data for '{}': {}", + &filename, + e + ))); + } + }; + let dsc = match DscDecoder::new(&data) { + Ok(dsc) => dsc, + Err(e) => { + return Some(Err(anyhow::anyhow!( + "Failed to create DSC decoder for '{}': {}", + &filename, + e + ))); + } + }; + let decoded = match dsc.unpack() { + Ok(decoded) => decoded, + Err(e) => { + return Some(Err(anyhow::anyhow!( + "Failed to unpack DSC data for '{}': {}", + &filename, + e + ))); + } + }; + let reader = MemReader::new(decoded); + return Some(Ok(Box::new(MemEntry { + name: filename, + data: reader, + detect, + }))); + } return Some(Ok(Box::new(bse_reader))); } Err(e) => { diff --git a/src/scripts/bgi/archive/v2.rs b/src/scripts/bgi/archive/v2.rs index 50fcef0..b41780d 100644 --- a/src/scripts/bgi/archive/v2.rs +++ b/src/scripts/bgi/archive/v2.rs @@ -311,6 +311,10 @@ fn detect_script_type(buf: &[u8], buf_len: usize, filename: &str) -> Option<&'st if buf_len >= 28 && buf.starts_with(b"BurikoCompiledScriptVer1.00\0") { return Some(&ScriptType::BGI); } + #[cfg(feature = "bgi-img")] + if buf_len >= 16 && buf.starts_with(b"CompressedBG___") { + return Some(&ScriptType::BGICbg); + } let filename = filename.to_lowercase(); if filename.ends_with("._bp") { return Some(&ScriptType::BGIBp); @@ -437,7 +441,45 @@ impl<'a, T: Iterator, R: Read + Seek + 'static> Iterat #[cfg(not(feature = "bgi-img"))] let detect = detect_script_type; match BseReader::new(entry, detect, &filename) { - Ok(bse_reader) => { + Ok(mut bse_reader) => { + if bse_reader.is_dsc() { + let data = match bse_reader.data() { + Ok(data) => data, + Err(e) => { + return Some(Err(anyhow::anyhow!( + "Failed to read BSE data for '{}': {}", + &filename, + e + ))); + } + }; + let dsc = match DscDecoder::new(&data) { + Ok(dsc) => dsc, + Err(e) => { + return Some(Err(anyhow::anyhow!( + "Failed to create DSC decoder for '{}': {}", + &filename, + e + ))); + } + }; + let decoded = match dsc.unpack() { + Ok(decoded) => decoded, + Err(e) => { + return Some(Err(anyhow::anyhow!( + "Failed to unpack DSC data for '{}': {}", + &filename, + e + ))); + } + }; + let reader = MemReader::new(decoded); + return Some(Ok(Box::new(MemEntry { + name: filename, + data: reader, + detect, + }))); + } return Some(Ok(Box::new(bse_reader))); } Err(e) => { diff --git a/src/scripts/mod.rs b/src/scripts/mod.rs index fd5951e..ceee5fc 100644 --- a/src/scripts/mod.rs +++ b/src/scripts/mod.rs @@ -24,6 +24,8 @@ lazy_static::lazy_static! { Box::new(bgi::archive::v1::BgiArchiveBuilder::new()), #[cfg(feature = "bgi-arc")] Box::new(bgi::archive::v2::BgiArchiveBuilder::new()), + #[cfg(feature = "bgi-arc")] + Box::new(bgi::archive::dsc::DscBuilder::new()), #[cfg(feature = "bgi-img")] Box::new(bgi::image::img::BgiImageBuilder::new()), #[cfg(feature = "bgi-img")] diff --git a/src/types.rs b/src/types.rs index 95d5bdf..5bac6b8 100644 --- a/src/types.rs +++ b/src/types.rs @@ -231,6 +231,10 @@ pub enum ScriptType { #[value(alias = "ethornell-arc-v2", alias = "bgi-arc", alias = "ethornell-arc")] /// Buriko General Interpreter/Ethornell archive v2 BGIArcV2, + #[cfg(feature = "bgi-arc")] + #[value(alias("ethornell-dsc"))] + /// Buriko General Interpreter/Ethornell compressed file (DSC) + BGIDsc, #[cfg(feature = "bgi-img")] #[value(alias("ethornell-img"))] /// Buriko General Interpreter/Ethornell image (Image files in sysgrp.arc)