msg_tool\scripts\kirikiri/
mdf.rs

1//! Kirikiri Zlib-Compressed File
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use anyhow::Result;
6use std::io::Read;
7
8#[derive(Debug)]
9/// Kirikiri MDF Script Builder
10pub struct MdfBuilder {}
11
12impl MdfBuilder {
13    /// Creates a new instance of `MdfBuilder`
14    pub fn new() -> Self {
15        Self {}
16    }
17}
18
19impl ScriptBuilder for MdfBuilder {
20    fn default_encoding(&self) -> Encoding {
21        Encoding::Utf8
22    }
23
24    fn build_script(
25        &self,
26        buf: Vec<u8>,
27        filename: &str,
28        _encoding: Encoding,
29        _archive_encoding: Encoding,
30        _config: &ExtraConfig,
31        _archive: Option<&Box<dyn Script>>,
32    ) -> Result<Box<dyn Script>> {
33        Ok(Box::new(Mdf::new(buf, filename)?))
34    }
35
36    fn extensions(&self) -> &'static [&'static str] {
37        &[]
38    }
39
40    fn script_type(&self) -> &'static ScriptType {
41        &ScriptType::KirikiriMdf
42    }
43
44    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
45        if buf_len >= 4 && buf.starts_with(b"mdf\0") {
46            Some(10)
47        } else {
48            None
49        }
50    }
51}
52
53#[derive(Debug)]
54/// Kirikiri MDF Script
55pub struct Mdf {
56    data: MemReader,
57    ext: String,
58}
59
60impl Mdf {
61    /// Creates a new `Mdf` script from the given buffer and filename
62    ///
63    /// * `buf` - The buffer containing the MDF data
64    /// * `filename` - The name of the file (used for extension detection)
65    pub fn new(buf: Vec<u8>, filename: &str) -> Result<Self> {
66        let mut data = MemReader::new(buf);
67        let mut header = [0u8; 4];
68        data.read_exact(&mut header)?;
69        if &header != b"mdf\0" {
70            return Err(anyhow::anyhow!("Invalid MDF header"));
71        }
72        Ok(Self {
73            data,
74            ext: std::path::Path::new(filename)
75                .extension()
76                .and_then(|s| s.to_str())
77                .unwrap_or("")
78                .to_string(),
79        })
80    }
81
82    pub(crate) fn unpack(mut data: MemReaderRef) -> Result<Vec<u8>> {
83        let size = data.read_u32()?;
84        let mut decoder = flate2::read::ZlibDecoder::new(data);
85        let mut result = Vec::new();
86        decoder.read_to_end(&mut result)?;
87        if size as usize != result.len() {
88            eprintln!(
89                "Warning: MDF unpacked size mismatch: expected {}, got {}",
90                size,
91                result.len()
92            );
93            crate::COUNTER.inc_warning();
94        }
95        Ok(result)
96    }
97}
98
99impl Script for Mdf {
100    fn default_output_script_type(&self) -> OutputScriptType {
101        OutputScriptType::Custom
102    }
103
104    fn default_format_type(&self) -> FormatOptions {
105        FormatOptions::None
106    }
107
108    fn is_output_supported(&self, output: OutputScriptType) -> bool {
109        matches!(output, OutputScriptType::Custom)
110    }
111
112    fn custom_output_extension<'a>(&'a self) -> &'a str {
113        &self.ext
114    }
115
116    fn custom_export(&self, filename: &std::path::Path, _encoding: Encoding) -> Result<()> {
117        let data = Self::unpack(MemReaderRef::new(&self.data.data[4..]))?;
118        let mut writer = crate::utils::files::write_file(filename)?;
119        writer.write_all(&data)?;
120        Ok(())
121    }
122}