From b7815d7516f8dbb8704700eb6050bc282b388028 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Tue, 12 May 2026 10:26:55 +0800 Subject: [PATCH] Add support to unpack xp3 file in pe(exe) file --- Cargo.toml | 2 +- src/scripts/kirikiri/archive/xp3/mod.rs | 54 +++++++++++++++++++++--- src/scripts/kirikiri/archive/xp3/pe.rs | 34 +++++++++++++++ src/scripts/kirikiri/archive/xp3/read.rs | 2 +- 4 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 src/scripts/kirikiri/archive/xp3/pe.rs diff --git a/Cargo.toml b/Cargo.toml index 2c9354f..c72a2c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,7 +102,7 @@ hexen-haus = ["memchr", "utils-str"] hexen-haus-arc = ["hexen-haus"] hexen-haus-img = ["hexen-haus", "image"] kirikiri = ["emote-psb", "fancy-regex", "flate2", "json", "lz4", "utils-escape"] -kirikiri-arc = ["kirikiri", "adler", "aes", "base64", "blake2", "bytes", "cbc", "chacha20/legacy", "fastcdc", "flate2", "hex", "int-enum", "md5", "msg_tool_macro/kirikiri-arc", "msg_tool_xp3data", "parse-size", "sha2", "siphasher", "utils-case-insensitive-string", "utils-lzss", "utils-serde-base64bytes", "utils-simple-pack", "zopfli", "zstd"] +kirikiri-arc = ["kirikiri", "adler", "aes", "base64", "blake2", "bytes", "cbc", "chacha20/legacy", "fastcdc", "flate2", "hex", "int-enum", "md5", "memchr", "msg_tool_macro/kirikiri-arc", "msg_tool_xp3data", "parse-size", "pelite", "sha2", "siphasher", "utils-case-insensitive-string", "utils-lzss", "utils-serde-base64bytes", "utils-simple-pack", "zopfli", "zstd"] kirikiri-img = ["kirikiri", "image", "libtlg-rs"] musica = [] musica-arc = ["musica", "crc32fast", "flate2", "include-flate", "utils-blowfish", "utils-rc4", "utils-serde-base64bytes", "utils-xored-stream"] diff --git a/src/scripts/kirikiri/archive/xp3/mod.rs b/src/scripts/kirikiri/archive/xp3/mod.rs index cdd7534..999bb5c 100644 --- a/src/scripts/kirikiri/archive/xp3/mod.rs +++ b/src/scripts/kirikiri/archive/xp3/mod.rs @@ -2,6 +2,7 @@ mod archive; #[allow(dead_code)] mod consts; mod crypt; +mod pe; mod read; mod reader; mod segmenter; @@ -134,10 +135,15 @@ impl ScriptBuilder for Xp3ArchiveBuilder { config: &ExtraConfig, _archive: Option<&Box>, ) -> Result> { + let mut base_offset = 0; + if buf.starts_with(b"MZ") { + base_offset = pe::get_base_offset(&buf)?; + } Ok(Box::new(Xp3Archive::new( MemReader::new(buf), config, filename, + base_offset, )?)) } @@ -149,24 +155,47 @@ impl ScriptBuilder for Xp3ArchiveBuilder { config: &ExtraConfig, _archive: Option<&Box>, ) -> Result> { - let file = std::fs::File::open(filename)?; - Ok(Box::new(Xp3Archive::new(file, config, filename)?)) + let mut file = std::fs::File::open(filename)?; + let mut base_offset = 0; + if file.peek_and_equal(b"MZ").is_ok() { + let mp = pelite::FileMap::open(filename)?; + base_offset = pe::get_base_offset(&mp)?; + } + Ok(Box::new(Xp3Archive::new( + file, + config, + filename, + base_offset, + )?)) } fn build_script_from_reader<'a>( &self, - reader: Box, + mut reader: Box, filename: &str, _encoding: Encoding, _archive_encoding: Encoding, config: &ExtraConfig, _archive: Option<&Box>, ) -> Result> { - Ok(Box::new(Xp3Archive::new(reader, config, filename)?)) + let mut base_offset = 0; + if reader.peek_and_equal(b"MZ").is_ok() { + let mut data = Vec::new(); + let pos = reader.stream_position()?; + reader.read_to_end(&mut data)?; + reader.seek(SeekFrom::Start(pos))?; + base_offset = pe::get_base_offset(&data)?; + } + Ok(Box::new(Xp3Archive::new( + reader, + config, + filename, + base_offset, + )?)) } fn extensions(&self) -> &'static [&'static str] { - &["xp3", "bin", "dat"] + &["xp3", "bin", "dat", "exe"] } fn script_type(&self) -> &'static ScriptType { @@ -187,10 +216,20 @@ impl ScriptBuilder for Xp3ArchiveBuilder { Ok(Box::new(Xp3ArchiveWriter::new(filename, files, config)?)) } - fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option { + fn is_this_format(&self, filename: &str, buf: &[u8], buf_len: usize) -> Option { if buf_len >= 11 && buf.starts_with(consts::XP3_MAGIC) { return Some(100); } + if buf_len >= 2 && buf.starts_with(b"MZ") { + let p = std::path::Path::new(filename); + if p.exists() { + if let Ok(file) = pelite::FileMap::open(p) { + if pe::get_base_offset(&file).is_ok() { + return Some(100); + } + } + } + } None } } @@ -210,8 +249,9 @@ impl<'a> Xp3Archive<'a> { stream: T, config: &ExtraConfig, filename: &str, + base_offset: u64, ) -> Result { - let mut archive = archive::Xp3Archive::new(stream, config, filename)?; + let mut archive = archive::Xp3Archive::new(stream, config, filename, base_offset)?; if config.xp3_debug_archive { println!("Debug info for {}:\n{:#?}", filename, archive); // Try flush stdout. diff --git a/src/scripts/kirikiri/archive/xp3/pe.rs b/src/scripts/kirikiri/archive/xp3/pe.rs new file mode 100644 index 0000000..132ff12 --- /dev/null +++ b/src/scripts/kirikiri/archive/xp3/pe.rs @@ -0,0 +1,34 @@ +use super::consts::*; +use anyhow::Result; +use memchr::memmem::find; +use pelite::{PeFile, Wrap}; + +pub fn get_base_offset + ?Sized>(data: &D) -> Result { + let file = PeFile::from_bytes(data)?; + if let Some(rsrc) = file.section_headers().by_name(".rsrc") { + let bytes = file.get_section_bytes(rsrc)?; + if let Some(pos) = find(bytes, XP3_MAGIC) { + return Ok(rsrc.file_range().start as u64 + pos as u64); + } + } + let last_section_end = file + .section_headers() + .iter() + .map(|s| s.PointerToRawData + s.SizeOfRawData) + .max() + .unwrap_or_else(|| match file.optional_header() { + Wrap::T32(h) => h.SizeOfHeaders, + Wrap::T64(h) => h.SizeOfHeaders, + }); + let aligned_offset = ((last_section_end + 0xF) & !0xF) as usize; + let data = data.as_ref(); + if aligned_offset >= data.len() { + anyhow::bail!("No overlay for pe image."); + } + for i in (aligned_offset..(data.len() - 11)).step_by(0x10) { + if &data[i..i + 11] == XP3_MAGIC { + return Ok(i as u64); + } + } + anyhow::bail!("Failed to find xp3 file in pe file.") +} diff --git a/src/scripts/kirikiri/archive/xp3/read.rs b/src/scripts/kirikiri/archive/xp3/read.rs index 3b3eda4..2b1786a 100644 --- a/src/scripts/kirikiri/archive/xp3/read.rs +++ b/src/scripts/kirikiri/archive/xp3/read.rs @@ -12,6 +12,7 @@ impl<'a> Xp3Archive<'a> { stream: T, config: &ExtraConfig, filename: &str, + base_offset: u64, ) -> Result { #[allow(unused_mut)] let mut crypt: Box = @@ -25,7 +26,6 @@ impl<'a> Xp3Archive<'a> { Box::new(NoCrypt::new()) }; let mut stream = Box::new(stream); - let base_offset = 0; if base_offset != 0 { stream.seek(SeekFrom::Start(base_offset))?; }