Add mdf decompress support when unpack xp3 archive

This commit is contained in:
2025-10-10 16:23:46 +08:00
parent fdbfab18ab
commit 021fe5b71a
4 changed files with 62 additions and 2 deletions

View File

@@ -484,6 +484,14 @@ pub struct Arg {
#[arg(long, global = true, visible_alias = "softpal-idx")] #[arg(long, global = true, visible_alias = "softpal-idx")]
/// Whether to add message index to Softpal src script when exporting. /// Whether to add message index to Softpal src script when exporting.
pub softpal_add_message_index: bool, pub softpal_add_message_index: bool,
#[cfg(feature = "kirikiri-arc")]
#[arg(long, global = true)]
/// Disable decrypt SimpleCrypt files in Kirikiri XP3 archive when extracting.
pub xp3_no_simple_crypt: bool,
#[cfg(feature = "kirikiri-arc")]
#[arg(long, global = true)]
/// Disable decompressing mdf files in Kirikiri XP3 archive when extracting.
pub xp3_no_mdf_decompress: bool,
#[command(subcommand)] #[command(subcommand)]
/// Command /// Command
pub command: Command, pub command: Command,

View File

@@ -2744,6 +2744,10 @@ fn main() {
softpal_add_message_index: arg.softpal_add_message_index, softpal_add_message_index: arg.softpal_add_message_index,
#[cfg(feature = "kirikiri")] #[cfg(feature = "kirikiri")]
kirikiri_chat_multilang: !arg.kirikiri_chat_no_multilang, kirikiri_chat_multilang: !arg.kirikiri_chat_no_multilang,
#[cfg(feature = "kirikiri-arc")]
xp3_simple_crypt: !arg.xp3_no_simple_crypt,
#[cfg(feature = "kirikiri-arc")]
xp3_mdf_decompress: !arg.xp3_no_mdf_decompress,
}); });
match &arg.command { match &arg.command {
args::Command::Export { input, output } => { args::Command::Export { input, output } => {

View File

@@ -83,11 +83,13 @@ impl ScriptBuilder for Xp3ArchiveBuilder {
pub struct Xp3Archive<T: Read + Seek + std::fmt::Debug> { pub struct Xp3Archive<T: Read + Seek + std::fmt::Debug> {
reader: Arc<Mutex<T>>, reader: Arc<Mutex<T>>,
entries: Vec<(String, XP3FileIndex)>, entries: Vec<(String, XP3FileIndex)>,
decrypt_simple_crypt: bool,
decompress_mdf: bool,
} }
impl<T: Read + Seek + std::fmt::Debug> Xp3Archive<T> { impl<T: Read + Seek + std::fmt::Debug> Xp3Archive<T> {
/// Create a new Kirikiri XP3 Archive /// Create a new Kirikiri XP3 Archive
pub fn new(reader: T, _config: &ExtraConfig) -> Result<Self> { pub fn new(reader: T, config: &ExtraConfig) -> Result<Self> {
let xp3_reader = XP3Reader::open_archive(reader) let xp3_reader = XP3Reader::open_archive(reader)
.map_err(|e| anyhow::anyhow!("Failed to open XP3 archive: {:?}", e))?; .map_err(|e| anyhow::anyhow!("Failed to open XP3 archive: {:?}", e))?;
let entries = xp3_reader let entries = xp3_reader
@@ -106,6 +108,8 @@ impl<T: Read + Seek + std::fmt::Debug> Xp3Archive<T> {
Ok(Self { Ok(Self {
reader: Arc::new(Mutex::new(xp3_reader.close().1)), reader: Arc::new(Mutex::new(xp3_reader.close().1)),
entries, entries,
decrypt_simple_crypt: config.xp3_simple_crypt,
decompress_mdf: config.xp3_mdf_decompress,
}) })
} }
} }
@@ -143,7 +147,8 @@ impl<T: Read + Seek + std::fmt::Debug + 'static> Script for Xp3Archive<T> {
let mut header = [0u8; 16]; let mut header = [0u8; 16];
let header_len = entry.read(&mut header)?; let header_len = entry.read(&mut header)?;
entry.rewind()?; entry.rewind()?;
if header_len >= 5 if self.decrypt_simple_crypt
&& header_len >= 5
&& header[0] == 0xFE && header[0] == 0xFE
&& header[1] == 0xFE && header[1] == 0xFE
&& header[3] == 0xFF && header[3] == 0xFF
@@ -159,6 +164,14 @@ impl<T: Read + Seek + std::fmt::Debug + 'static> Script for Xp3Archive<T> {
return Ok(Box::new(SimpleCrypt::new(entry, index, crypt)?)); return Ok(Box::new(SimpleCrypt::new(entry, index, crypt)?));
} }
} }
if self.decompress_mdf
&& header_len >= 4
&& &header[0..4] == b"mdf\0"
&& entry.index.info().file_size() > 8
{
let index = entry.index.clone();
return Ok(Box::new(MdfEntry::new(entry, index)?));
}
Ok(Box::new(entry)) Ok(Box::new(entry))
} }
} }
@@ -461,3 +474,30 @@ impl<T: Read + Seek + std::fmt::Debug> Seek for SimpleCrypt<T> {
self.inner.stream_position() self.inner.stream_position()
} }
} }
#[derive(Debug)]
struct MdfEntry<T: Read + Seek + std::fmt::Debug> {
inner: ZlibDecoder<StreamRegion<Entry<T>>>,
index: XP3FileIndex,
}
impl<T: Read + Seek + std::fmt::Debug> MdfEntry<T> {
fn new(mut entry: Entry<T>, index: XP3FileIndex) -> Result<Self> {
entry.seek(SeekFrom::Start(8))?;
let entry = StreamRegion::new(entry, 8, index.info().file_size())?;
let inner = ZlibDecoder::new(entry);
Ok(Self { inner, index })
}
}
impl<T: Read + Seek + std::fmt::Debug> ArchiveContent for MdfEntry<T> {
fn name(&self) -> &str {
&self.index.info().name()
}
}
impl<T: Read + Seek + std::fmt::Debug> Read for MdfEntry<T> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.inner.read(buf)
}
}

View File

@@ -472,6 +472,14 @@ pub struct ExtraConfig {
/// Enable multi-language support for Kirikiri chat messages. Default is true. /// Enable multi-language support for Kirikiri chat messages. Default is true.
/// Note: This requires [Self::kirikiri_language_index] and [Self::kirikiri_languages] to be set correctly. /// Note: This requires [Self::kirikiri_language_index] and [Self::kirikiri_languages] to be set correctly.
pub kirikiri_chat_multilang: bool, pub kirikiri_chat_multilang: bool,
#[cfg(feature = "kirikiri-arc")]
#[default(true)]
/// Decrypt SimpleCrypt files in Kirikiri XP3 archive when extracting. Default is true.
pub xp3_simple_crypt: bool,
#[cfg(feature = "kirikiri-arc")]
#[default(true)]
/// Decompress mdf files in Kirikiri XP3 archive when extracting. Default is true.
pub xp3_mdf_decompress: bool,
} }
#[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)]