diff --git a/Cargo.toml b/Cargo.toml index e57b533..ecc3558 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" repository = "https://github.com/lifegpc/msg-tool" description = "A command-line tool for exporting, importing, packing, and unpacking script files." license = "GPL-3.0-or-later" -exclude = [".github", "*.py"] +exclude = [".github", "*.py", "AGENTS.md"] [dependencies] anyhow = "1" @@ -53,7 +53,7 @@ zstd = { version = "0.13", optional = true } default = ["all-fmt", "image-jpg", "image-jxl", "image-webp", "audio-flac", "jieba"] all-fmt = ["all-script", "all-img", "all-arc", "all-audio"] all-script = ["artemis", "artemis-panmimisoft", "bgi", "cat-system", "circus", "entis-gls", "escude", "ex-hibit", "favorite", "hexen-haus", "kirikiri", "silky", "softpal", "will-plus", "yaneurao", "yaneurao-itufuru"] -all-img = ["bgi-img", "cat-system-img", "circus-img", "emote-img", "kirikiri-img", "softpal-img"] +all-img = ["bgi-img", "cat-system-img", "circus-img", "emote-img", "hexen-haus-img", "kirikiri-img", "softpal-img"] all-arc = ["artemis-arc", "bgi-arc", "cat-system-arc", "circus-arc", "escude-arc", "ex-hibit-arc", "hexen-haus-arc", "softpal-arc"] all-audio = ["bgi-audio", "circus-audio"] artemis = ["stylua", "utils-escape"] @@ -79,6 +79,7 @@ ex-hibit-arc = ["ex-hibit"] favorite = [] 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-img = ["kirikiri", "image", "libtlg-rs"] silky = [] diff --git a/README.md b/README.md index 76fcf0c..da178cb 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,10 @@ msg-tool create -t |---|---|---|---|---|---| | `hexen-haus-arcc` | `hexen-haus-arc` | HexenHaus Arcc Archive File (.arc) | ✔️ | ❌ | | | `hexen-haus-wag` | `hexen-haus-arc` | HexenHaus Wag Archive File (.wag) | ✔️ | ❌ | | + +| Image Type | Feature Name | Name | Export | Import | Export Multiple | Import Multiple | Create | Remarks | +|---|---|---|---|---|---|---|---| +| `hexen-haus-png` | `hexen-haus-img` | HexenHaus PNG Image File (.png) | ✔️ | ❌ | ❌ | ❌ | ❌ | | ### Kirikiri | Script Type | Feature Name | Name | Export | Import | Export Multiple | Import Multiple | Custom Export | Custom Import | Create | Remarks | |---|---|---|---|---|---|---|---|---|---|---| diff --git a/src/scripts/hexen_haus/archive/mod.rs b/src/scripts/hexen_haus/archive/mod.rs index 0cd3e92..1b1352a 100644 --- a/src/scripts/hexen_haus/archive/mod.rs +++ b/src/scripts/hexen_haus/archive/mod.rs @@ -7,5 +7,9 @@ fn detect_script_type(_filename: &str, buf: &[u8]) -> Option { if buf.len() >= 4 && buf.starts_with(b"NORI") { return Some(ScriptType::HexenHaus); } + #[cfg(feature = "hexen-haus-img")] + if buf.len() >= 4 && buf.starts_with(b"IMGD") { + return Some(ScriptType::HexenHausPng); + } None } diff --git a/src/scripts/hexen_haus/img/mod.rs b/src/scripts/hexen_haus/img/mod.rs new file mode 100644 index 0000000..6717f3e --- /dev/null +++ b/src/scripts/hexen_haus/img/mod.rs @@ -0,0 +1 @@ +pub mod png; diff --git a/src/scripts/hexen_haus/img/png.rs b/src/scripts/hexen_haus/img/png.rs new file mode 100644 index 0000000..ae4ef9a --- /dev/null +++ b/src/scripts/hexen_haus/img/png.rs @@ -0,0 +1,120 @@ +//! HexenHaus PNG Image +use crate::ext::io::*; +use crate::scripts::base::*; +use crate::types::*; +use crate::utils::img::*; +use anyhow::Result; +use std::io::{Read, Seek, SeekFrom}; + +#[derive(Debug)] +/// HexenHaus PNG Image Builder +pub struct PngImageBuilder {} + +impl PngImageBuilder { + /// Creates a new instance of `PngImageBuilder` + pub fn new() -> Self { + PngImageBuilder {} + } +} + +impl ScriptBuilder for PngImageBuilder { + fn default_encoding(&self) -> Encoding { + Encoding::Cp932 + } + + fn build_script( + &self, + data: Vec, + _filename: &str, + _encoding: Encoding, + _archive_encoding: Encoding, + config: &ExtraConfig, + _archive: Option<&Box>, + ) -> Result> { + Ok(Box::new(PngImage::new(MemReader::new(data), config)?)) + } + + fn extensions(&self) -> &'static [&'static str] { + &["png"] + } + + fn script_type(&self) -> &'static ScriptType { + &ScriptType::HexenHausPng + } + + fn is_image(&self) -> bool { + true + } + + fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option { + if buf_len >= 4 && buf.starts_with(b"IMGD") { + return Some(10); + } + None + } +} + +#[derive(Debug)] +/// Extra information for PNG image +pub struct ExtraInfo { + /// x offset + pub offset_x: u32, + /// y offset + pub offset_y: u32, +} + +#[derive(Debug)] +pub struct PngImage { + reader: MemReader, + extra: Option, +} + +impl PngImage { + /// Creates a new instance of `PngImage` + pub fn new(mut reader: MemReader, _config: &ExtraConfig) -> Result { + let mut header = [0; 4]; + reader.read_exact(&mut header)?; + if &header != b"IMGD" { + return Err(anyhow::anyhow!("Not a valid HexenHaus PNG image")); + } + reader.seek(SeekFrom::End(-14))?; + let cnt = reader.read_exact_vec(12)?; + let extra = if cnt.starts_with(b"CNTR") { + let mut cnt_reader = MemReaderRef::new(&cnt[4..]); + let offset_x = cnt_reader.read_u32()?; + let offset_y = cnt_reader.read_u32()?; + Some(ExtraInfo { offset_x, offset_y }) + } else { + None + }; + Ok(PngImage { reader, extra }) + } +} + +impl Script for PngImage { + fn default_output_script_type(&self) -> OutputScriptType { + OutputScriptType::Json + } + + fn default_format_type(&self) -> FormatOptions { + FormatOptions::None + } + + fn is_image(&self) -> bool { + true + } + + fn export_image(&self) -> Result { + let mut reader = self.reader.to_ref(); + reader.pos = 0; + let reader = StreamRegion::with_start_pos(reader, 0x10)?; + let img = load_png(reader)?; + Ok(img) + } + + fn extra_info<'a>(&'a self) -> Option> { + self.extra + .as_ref() + .map(|e| Box::new(e) as Box) + } +} diff --git a/src/scripts/hexen_haus/mod.rs b/src/scripts/hexen_haus/mod.rs index ee2c8bd..2d80129 100644 --- a/src/scripts/hexen_haus/mod.rs +++ b/src/scripts/hexen_haus/mod.rs @@ -2,3 +2,5 @@ #[cfg(feature = "hexen-haus-arc")] pub mod archive; pub mod bin; +#[cfg(feature = "hexen-haus-img")] +pub mod img; diff --git a/src/scripts/mod.rs b/src/scripts/mod.rs index b5169fa..89a787a 100644 --- a/src/scripts/mod.rs +++ b/src/scripts/mod.rs @@ -146,6 +146,8 @@ lazy_static::lazy_static! { Box::new(artemis::archive::pf2::ArtemisPf2Builder::new()), #[cfg(feature = "hexen-haus-arc")] Box::new(hexen_haus::archive::wag::HexenHausWagArchiveBuilder::new()), + #[cfg(feature = "hexen-haus-img")] + Box::new(hexen_haus::img::png::PngImageBuilder::new()), ]; /// A list of all script extensions. pub static ref ALL_EXTS: Vec = diff --git a/src/types.rs b/src/types.rs index 30380bf..844ade7 100644 --- a/src/types.rs +++ b/src/types.rs @@ -605,6 +605,9 @@ pub enum ScriptType { #[cfg(feature = "hexen-haus-arc")] /// HexenHaus WAG archive HexenHausWag, + #[cfg(feature = "hexen-haus-img")] + /// HexenHaus PNG image + HexenHausPng, #[cfg(feature = "kirikiri")] #[value(alias("kr-scn"))] /// Kirikiri SCN script