Add support for HexenHaus PNG Image File (.png)

This commit is contained in:
2025-09-28 10:09:28 +08:00
parent 91bd43826d
commit 3ae55b03c0
8 changed files with 139 additions and 2 deletions

View File

@@ -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 = []

View File

@@ -171,6 +171,10 @@ msg-tool create -t <script-type> <input> <output>
|---|---|---|---|---|---|
| `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 |
|---|---|---|---|---|---|---|---|---|---|---|

View File

@@ -7,5 +7,9 @@ fn detect_script_type(_filename: &str, buf: &[u8]) -> Option<ScriptType> {
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
}

View File

@@ -0,0 +1 @@
pub mod png;

View File

@@ -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<u8>,
_filename: &str,
_encoding: Encoding,
_archive_encoding: Encoding,
config: &ExtraConfig,
_archive: Option<&Box<dyn Script>>,
) -> Result<Box<dyn Script>> {
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<u8> {
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<ExtraInfo>,
}
impl PngImage {
/// Creates a new instance of `PngImage`
pub fn new(mut reader: MemReader, _config: &ExtraConfig) -> Result<Self> {
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<ImageData> {
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<Box<dyn AnyDebug + 'a>> {
self.extra
.as_ref()
.map(|e| Box::new(e) as Box<dyn AnyDebug>)
}
}

View File

@@ -2,3 +2,5 @@
#[cfg(feature = "hexen-haus-arc")]
pub mod archive;
pub mod bin;
#[cfg(feature = "hexen-haus-img")]
pub mod img;

View File

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

View File

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