diff --git a/src/scripts/kirikiri/mdf.rs b/src/scripts/kirikiri/mdf.rs new file mode 100644 index 0000000..295785a --- /dev/null +++ b/src/scripts/kirikiri/mdf.rs @@ -0,0 +1,113 @@ +use crate::ext::io::*; +use crate::scripts::base::*; +use crate::types::*; +use anyhow::Result; +use std::io::Read; + +#[derive(Debug)] +pub struct MdfBuilder {} + +impl MdfBuilder { + pub fn new() -> Self { + Self {} + } +} + +impl ScriptBuilder for MdfBuilder { + fn default_encoding(&self) -> Encoding { + Encoding::Utf8 + } + + fn build_script( + &self, + buf: Vec, + filename: &str, + _encoding: Encoding, + _archive_encoding: Encoding, + _config: &ExtraConfig, + ) -> Result> { + Ok(Box::new(Mdf::new(buf, filename)?)) + } + + fn extensions(&self) -> &'static [&'static str] { + &[] + } + + fn script_type(&self) -> &'static ScriptType { + &ScriptType::KirikiriMdf + } + + fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option { + if buf_len >= 4 && buf.starts_with(b"mdf\0") { + Some(10) + } else { + None + } + } +} + +#[derive(Debug)] +pub struct Mdf { + data: MemReader, + ext: String, +} + +impl Mdf { + pub fn new(buf: Vec, filename: &str) -> Result { + let mut data = MemReader::new(buf); + let mut header = [0u8; 4]; + data.read_exact(&mut header)?; + if &header != b"mdf\0" { + return Err(anyhow::anyhow!("Invalid MDF header")); + } + Ok(Self { + data, + ext: std::path::Path::new(filename) + .extension() + .and_then(|s| s.to_str()) + .unwrap_or("") + .to_string(), + }) + } + + pub fn unpack(mut data: MemReaderRef) -> Result> { + let size = data.read_u32()?; + let mut decoder = flate2::read::ZlibDecoder::new(data); + let mut result = Vec::new(); + decoder.read_to_end(&mut result)?; + if size as usize != result.len() { + eprintln!( + "Warning: MDF unpacked size mismatch: expected {}, got {}", + size, + result.len() + ); + crate::COUNTER.inc_warning(); + } + Ok(result) + } +} + +impl Script for Mdf { + fn default_output_script_type(&self) -> OutputScriptType { + OutputScriptType::Custom + } + + fn default_format_type(&self) -> FormatOptions { + FormatOptions::None + } + + fn is_output_supported(&self, output: OutputScriptType) -> bool { + matches!(output, OutputScriptType::Custom) + } + + fn custom_output_extension<'a>(&'a self) -> &'a str { + &self.ext + } + + fn custom_export(&self, filename: &std::path::Path, _encoding: Encoding) -> Result<()> { + let data = Self::unpack(MemReaderRef::new(&self.data.data[4..]))?; + let mut writer = crate::utils::files::write_file(filename)?; + writer.write_all(&data)?; + Ok(()) + } +} diff --git a/src/scripts/kirikiri/mod.rs b/src/scripts/kirikiri/mod.rs index ad40679..ed32a2f 100644 --- a/src/scripts/kirikiri/mod.rs +++ b/src/scripts/kirikiri/mod.rs @@ -1,6 +1,7 @@ #[cfg(feature = "kirikiri-img")] pub mod image; pub mod ks; +pub mod mdf; pub mod scn; pub mod simple_crypt; use std::collections::HashMap; diff --git a/src/scripts/kirikiri/scn.rs b/src/scripts/kirikiri/scn.rs index 8f655d5..27a9465 100644 --- a/src/scripts/kirikiri/scn.rs +++ b/src/scripts/kirikiri/scn.rs @@ -1,3 +1,4 @@ +use super::mdf::Mdf; use crate::ext::io::*; use crate::ext::psb::*; use crate::scripts::base::*; @@ -107,7 +108,20 @@ pub struct ScnScript { } impl ScnScript { - pub fn new(reader: R, filename: &str, config: &ExtraConfig) -> Result { + pub fn new( + mut reader: R, + filename: &str, + config: &ExtraConfig, + ) -> Result { + let mut header = [0u8; 4]; + reader.read_exact(&mut header)?; + if &header == b"mdf\0" { + let mut data = Vec::new(); + reader.read_to_end(&mut data)?; + let decoded = Mdf::unpack(MemReaderRef::new(&data))?; + return Self::new(MemReader::new(decoded), filename, config); + } + reader.rewind()?; let mut psb = PsbReader::open_psb(reader) .map_err(|e| anyhow::anyhow!("Failed to open PSB from {}: {:?}", filename, e))?; let psb = psb diff --git a/src/scripts/mod.rs b/src/scripts/mod.rs index f27e9dc..15cfca0 100644 --- a/src/scripts/mod.rs +++ b/src/scripts/mod.rs @@ -60,6 +60,8 @@ lazy_static::lazy_static! { Box::new(kirikiri::image::pimg::PImgBuilder::new()), #[cfg(feature = "kirikiri-img")] Box::new(kirikiri::image::dref::DrefBuilder::new()), + #[cfg(feature = "kirikiri")] + Box::new(kirikiri::mdf::MdfBuilder::new()), ]; pub static ref ALL_EXTS: Vec = BUILDER.iter().flat_map(|b| b.extensions()).map(|s| s.to_string()).collect(); diff --git a/src/types.rs b/src/types.rs index 752610c..1b7dc50 100644 --- a/src/types.rs +++ b/src/types.rs @@ -300,6 +300,10 @@ pub enum ScriptType { #[value(alias("kr-dref"))] /// Kirikiri DREF(DPAK-referenced) image KirikiriDref, + #[cfg(feature = "kirikiri")] + #[value(alias("kr-mdf"))] + /// Kirikiri MDF (zlib compressed) file + KirikiriMdf, #[cfg(feature = "yaneurao-itufuru")] #[value(alias("itufuru"))] /// Yaneurao Itufuru script diff --git a/src/utils/encoding.rs b/src/utils/encoding.rs index bbdafde..c09e350 100644 --- a/src/utils/encoding.rs +++ b/src/utils/encoding.rs @@ -29,7 +29,13 @@ pub fn decode_with_bom_detect( #[cfg(feature = "kirikiri")] { use crate::ext::io::*; + use crate::scripts::kirikiri::mdf::Mdf; use crate::scripts::kirikiri::simple_crypt::SimpleCrypt; + if data.len() >= 8 && data.starts_with(b"mdf\0") { + let reader = MemReaderRef::new(&data[4..]); + let decoded = Mdf::unpack(reader)?; + return decode_with_bom_detect(encoding, &decoded); + } if data.len() >= 5 && data[0] == 0xFE && data[1] == 0xFE