2 Commits

Author SHA1 Message Date
e317c487fb add more error info to error msg 2025-07-05 23:17:49 +08:00
5a23a3f33c Add mdf decompress support 2025-07-05 22:41:17 +08:00
6 changed files with 162 additions and 10 deletions

113
src/scripts/kirikiri/mdf.rs Normal file
View File

@@ -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<u8>,
filename: &str,
_encoding: Encoding,
_archive_encoding: Encoding,
_config: &ExtraConfig,
) -> Result<Box<dyn Script>> {
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<u8> {
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<u8>, filename: &str) -> Result<Self> {
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<Vec<u8>> {
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(())
}
}

View File

@@ -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;

View File

@@ -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<R: Read + Seek>(reader: R, filename: &str, config: &ExtraConfig) -> Result<Self> {
pub fn new<R: Read + Seek>(
mut reader: R,
filename: &str,
config: &ExtraConfig,
) -> Result<Self> {
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
@@ -340,7 +354,7 @@ impl Script for ScnScript {
if !scene.is_object() {
return Err(anyhow::anyhow!("scene at {} is not an object", i));
}
for text in scene["texts"].members_mut() {
for (j, text) in scene["texts"].members_mut().enumerate() {
if text.is_list() {
if text.len() <= 1 {
continue; // Skip if there are not enough values
@@ -358,7 +372,9 @@ impl Script for ScnScript {
let m = match cur_mes.take() {
Some(m) => m,
None => {
return Err(anyhow::anyhow!("No enough messages."));
return Err(anyhow::anyhow!(
"No enough messages. (text {j} at scene {i})"
));
}
};
if has_name {
@@ -371,7 +387,9 @@ impl Script for ScnScript {
}
text[0].set_string(name);
} else {
return Err(anyhow::anyhow!("Name is missing for message."));
return Err(anyhow::anyhow!(
"Name is missing for message. (text {j} at scene {i})"
));
}
}
let mut message = m.message.clone();
@@ -396,7 +414,9 @@ impl Script for ScnScript {
let m = match cur_mes.take() {
Some(m) => m,
None => {
return Err(anyhow::anyhow!("No enough messages."));
return Err(anyhow::anyhow!(
"No enough messages. (text {j} at scene {i})"
));
}
};
if has_name {
@@ -414,7 +434,7 @@ impl Script for ScnScript {
}
} else {
return Err(anyhow::anyhow!(
"Name is missing for message."
"Name is missing for message. (text {j} at scene {i})"
));
}
}
@@ -441,7 +461,9 @@ impl Script for ScnScript {
let m = match cur_mes.take() {
Some(m) => m,
None => {
return Err(anyhow::anyhow!("No enough messages."));
return Err(anyhow::anyhow!(
"No enough messages.(text {j} at scene {i})"
));
}
};
if has_name {
@@ -458,7 +480,9 @@ impl Script for ScnScript {
text[0].set_string(name);
}
} else {
return Err(anyhow::anyhow!("Name is missing for message."));
return Err(anyhow::anyhow!(
"Name is missing for message.(text {j} at scene {i})"
));
}
}
let mut message = m.message.clone();
@@ -483,7 +507,9 @@ impl Script for ScnScript {
let m = match cur_mes.take() {
Some(m) => m,
None => {
return Err(anyhow::anyhow!("No enough messages."));
return Err(anyhow::anyhow!(
"No enough messages.(text {j} at scene {i})"
));
}
};
if has_name {
@@ -501,7 +527,7 @@ impl Script for ScnScript {
}
} else {
return Err(anyhow::anyhow!(
"Name is missing for message."
"Name is missing for message.(text {j} at scene {i})"
));
}
}

View File

@@ -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<String> =
BUILDER.iter().flat_map(|b| b.extensions()).map(|s| s.to_string()).collect();

View File

@@ -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

View File

@@ -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