From 20b3c1480713ec53a9f5e5e267d60953b3ce6ef2 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Fri, 10 Apr 2026 16:55:53 +0800 Subject: [PATCH] =?UTF-8?q?Add=20support=20to=20old=20pimg=20file=20which?= =?UTF-8?q?=20don't=20have=20diff=5Fid=20(example=20game:=20=E3=82=B5?= =?UTF-8?q?=E3=83=8E=E3=83=90=E3=82=A6=E3=82=A3=E3=83=83=E3=83=81=20evimag?= =?UTF-8?q?e)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/args.rs | 5 +++ src/main.rs | 2 + src/scripts/emote/pimg.rs | 77 +++++++++++++++++++++++++++++++++++++-- src/types.rs | 4 ++ 4 files changed, 85 insertions(+), 3 deletions(-) diff --git a/src/args.rs b/src/args.rs index bfd55a9..f77eb65 100644 --- a/src/args.rs +++ b/src/args.rs @@ -737,6 +737,11 @@ pub struct Arg { /// Force decrypt files in Kirikiri xp3 archive even when flags are not set. /// Some encrypted files in Kirikiri XP3 archive may not set encryption flag, but still encrypted. Enable this to force decrypt these files. pub xp3_force_decrypt: bool, + #[cfg(feature = "emote-img")] + #[arg(long, global = true)] + /// Disable diff handle when exporting Emote PIMG images to PSD files. + /// If enabled, no group layers will be crated if both layer don't have diff_id and group_layer_id attribute. + pub emote_pimg_psd_no_diff: bool, #[command(subcommand)] /// Command pub command: Command, diff --git a/src/main.rs b/src/main.rs index f3f6c31..4f0bb73 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3399,6 +3399,8 @@ fn main() { xp3_force_extract: arg.xp3_force_extract, #[cfg(feature = "kirikiri-arc")] xp3_force_decrypt: arg.xp3_force_decrypt, + #[cfg(feature = "emote-img")] + emote_pimg_psd_no_diff: arg.emote_pimg_psd_no_diff, }); match &arg.command { args::Command::Export { input, output } => { diff --git a/src/scripts/emote/pimg.rs b/src/scripts/emote/pimg.rs index 9adf04e..d55f41d 100644 --- a/src/scripts/emote/pimg.rs +++ b/src/scripts/emote/pimg.rs @@ -358,6 +358,7 @@ pub struct PImg { psd: bool, psd_compress: bool, zlib_compression_level: u32, + psd_no_diff: bool, } impl PImg { @@ -374,6 +375,7 @@ impl PImg { psd: config.emote_pimg_psd, psd_compress: config.psd_compress, zlib_compression_level: config.zlib_compression_level, + psd_no_diff: config.emote_pimg_psd_no_diff, }) } @@ -448,6 +450,10 @@ impl Script for PImg { return Ok(Box::new(std::iter::empty())); } let mut bases = HashMap::new(); + let is_all_non_diff = psb["layers"] + .members() + .all(|layer| layer["diff_id"].is_none()); + let mut base_id = None; for i in psb["layers"].members() { if !i["diff_id"].is_none() { continue; // Skip layers with diff_id @@ -457,10 +463,22 @@ impl Script for PImg { .ok_or(anyhow::anyhow!("missing layer_id"))?; let top = i["top"].as_u32().ok_or(anyhow::anyhow!("missing top"))?; let left = i["left"].as_u32().ok_or(anyhow::anyhow!("missing left"))?; + if is_all_non_diff { + let width = i["width"] + .as_u32() + .ok_or(anyhow::anyhow!("missing width for non-diff layer"))?; + let height = i["height"] + .as_u32() + .ok_or(anyhow::anyhow!("missing height for non-diff layer"))?; + if !(top == 0 && left == 0 && width == width && height == height) { + continue; + } + } let opacity = i["opacity"] .as_u8() .ok_or_else(|| anyhow::anyhow!("Layer does not have a valid opacity"))?; bases.insert(layer_id, (self.load_img(layer_id)?, top, left, opacity)); + base_id = Some(layer_id); } Ok(Box::new(PImgIter { pimg: self, @@ -468,6 +486,8 @@ impl Script for PImg { height, layers: psb["layers"].members(), bases, + base_id: base_id.ok_or(anyhow::anyhow!("No valid base layer found"))?, + all_non_diff: is_all_non_diff, })) } @@ -489,6 +509,50 @@ impl Script for PImg { depth: 8, data: vec![0u8; (width * height * 4) as usize], }; + if !self.psd_no_diff { + let is_no_group = psb["layers"] + .members() + .all(|layer| layer["group_layer_id"].is_none()); + let is_all_non_diff = psb["layers"] + .members() + .all(|layer| layer["diff_id"].is_none()); + if is_all_non_diff && is_no_group { + let mut psb = psb.clone(); + let diff_id = { + let base_layer = psb["layers"] + .members() + .rev() + .find(|layer| { + layer["diff_id"].is_none() + && layer["top"].as_i64() == Some(0) + && layer["left"].as_i64() == Some(0) + && layer["width"].as_u32() == Some(width) + && layer["height"].as_u32() == Some(height) + }) + .ok_or(anyhow::anyhow!( + "No valid base layer found for overlay mode" + ))?; + base_layer["layer_id"] + .as_i64() + .ok_or(anyhow::anyhow!("missing layer_id for base layer"))? + }; + for layer in psb["layers"].members_mut() { + let layer_id = layer["layer_id"].as_i64().unwrap_or(-1); + if layer_id != diff_id { + layer["diff_id"] = diff_id.into(); + } + } + let layers = PImgLayerRoot::new(&psb["layers"])?; + if layers.len() != psb["layers"].len() { + return Err(anyhow::anyhow!("Layer hierarchy is invalid")); + } + layers.save_to_psd(self, &mut psd, &mut base)?; + let file = std::fs::File::create(filename)?; + let mut writer = std::io::BufWriter::new(file); + psd.save(base, &mut writer)?; + return Ok(()); + } + } let layers = PImgLayerRoot::new(&psb["layers"])?; if layers.len() != psb["layers"].len() { return Err(anyhow::anyhow!("Layer hierarchy is invalid")); @@ -507,6 +571,8 @@ struct PImgIter<'a> { height: u32, layers: ListIter<'a>, bases: HashMap, + base_id: i64, + all_non_diff: bool, } impl<'a> Iterator for PImgIter<'a> { @@ -549,7 +615,9 @@ impl<'a> Iterator for PImgIter<'a> { .as_u8() .ok_or_else(|| { anyhow::anyhow!("Layer does not have a valid opacity") }) ); - if layer["diff_id"].is_none() { + if (layer["diff_id"].is_none() && !self.all_non_diff) + || (self.all_non_diff && layer_id == self.base_id) + { let base = &try_option!(self.bases.get(&layer_id).ok_or(anyhow::anyhow!( "Base image for layer_id {} not found", layer_id @@ -578,10 +646,13 @@ impl<'a> Iterator for PImgIter<'a> { data, })); } else { - let diff_id = + let diff_id = if self.all_non_diff { + self.base_id + } else { try_option!(layer["diff_id"].as_i64().ok_or_else(|| { anyhow::anyhow!("Layer does not have a valid diff_id") - })); + })) + }; let (base, base_top, base_left, base_opacity) = try_option!( self.bases .get(&diff_id) diff --git a/src/types.rs b/src/types.rs index e0efa82..a053323 100644 --- a/src/types.rs +++ b/src/types.rs @@ -649,6 +649,10 @@ pub struct ExtraConfig { /// Force decrypt files in Kirikiri xp3 archive even when flags are not set. /// Some encrypted files in Kirikiri XP3 archive may not set encryption flag, but still encrypted. Enable this to force decrypt these files. pub xp3_force_decrypt: bool, + #[cfg(feature = "emote-img")] + /// Disable diff handle when exporting Emote PIMG images to PSD files. + /// If enabled, no group layers will be crated if both layer don't have diff_id and group_layer_id attribute. + pub emote_pimg_psd_no_diff: bool, } #[cfg(feature = "artemis")]