diff --git a/README.md b/README.md index 49213be..1dd65a5 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,7 @@ msg-tool create -t | Image Type | Feature Name | Name | Export | Import | Export Multiple | Import Multiple | Create | Remarks | |---|---|---|---|---|---|---|---|---| | `softpal-pgd-ge`/`pgd-ge`/`pgd` | `softpal-img` | Softpal PGD Ge Image File (.pgd) | ✔️ | ✔️ | ❌ | ❌ | ✔️ | | +| `softpal-pgd3`/`softpal-pgd2`/`pgd3`/`pgd2` | `softpal-img` | Softpal PGD Differential Image File (.pgd) | ✔️ | ❌ | ❌ | ❌ | ❌ | | ### WillPlus / AdvHD | Script Type | Feature Name | Name | Export | Import | Custom Export | Custom Import | Create | Remarks | |---|---|---|---|---|---|---|---|---| diff --git a/src/scripts/mod.rs b/src/scripts/mod.rs index 44892ea..6fb335d 100644 --- a/src/scripts/mod.rs +++ b/src/scripts/mod.rs @@ -132,6 +132,8 @@ lazy_static::lazy_static! { Box::new(emote::psb::PsbBuilder::new()), #[cfg(feature = "softpal-img")] Box::new(softpal::img::pgd::ge::PgdGeBuilder::new()), + #[cfg(feature = "softpal-img")] + Box::new(softpal::img::pgd::pgd3::Pgd3Builder::new()), ]; /// A list of all script extensions. pub static ref ALL_EXTS: Vec = diff --git a/src/scripts/softpal/img/pgd/base.rs b/src/scripts/softpal/img/pgd/base.rs index a38652c..b75b748 100644 --- a/src/scripts/softpal/img/pgd/base.rs +++ b/src/scripts/softpal/img/pgd/base.rs @@ -1,6 +1,7 @@ use crate::ext::io::*; use crate::ext::vec::*; use crate::types::*; +use crate::utils::encoding::*; use crate::utils::img::*; use crate::utils::struct_pack::*; use anyhow::Result; @@ -20,12 +21,32 @@ pub struct PgdGeHeader { pub mode: u32, } +impl PgdGeHeader { + pub fn is_base_file(&self) -> bool { + self.offset_x == 0 + && self.offset_y == 0 + && self.width == self.canvas_width + && self.height == self.canvas_height + } +} + +#[derive(Clone, Debug, StructPack, StructUnpack)] +pub struct PgdDiffHeader { + pub offset_x: u16, + pub offset_y: u16, + pub width: u16, + pub height: u16, + pub bpp: u16, + #[fstring = 0x22] + pub base_name: String, +} + pub struct PgdReader { pub input: T, output: Vec, width: u32, height: u32, - _bpp: u8, + bpp: u8, method: u32, format: Option, } @@ -41,7 +62,7 @@ impl PgdReader { output, width: 0, height: 0, - _bpp: 0, + bpp: 0, method: 0, format: None, }) @@ -55,6 +76,14 @@ impl PgdReader { Ok(s) } + pub fn with_diff_header(input: T, header: &PgdDiffHeader) -> Result { + let mut s = Self::new(input, 0x30)?; + s.width = header.width as u32; + s.height = header.height as u32; + s.bpp = header.bpp as u8; + Ok(s) + } + pub fn unpack_ge(mut self) -> Result { self.unpack_ge_pre()?; let data = match self.method { @@ -103,6 +132,28 @@ impl PgdReader { Ok(()) } + pub fn unpack_overlay(&mut self) -> Result { + self.unpack_ge_pre()?; + let data = self.post_process_pal(&self.output, 0, self.bpp as usize / 8)?; + let color_type = match self.bpp { + 24 => ImageColorType::Bgr, + 32 => ImageColorType::Bgra, + _ => { + return Err(anyhow::anyhow!( + "Unsupported bpp for overlay PGD: {}", + self.bpp + )); + } + }; + Ok(ImageData { + width: self.width, + height: self.height, + color_type, + depth: 8, + data, + }) + } + fn post_process1(&mut self) -> Result> { self.format = Some(ImageColorType::Bgra); let input = &self.output; diff --git a/src/scripts/softpal/img/pgd/mod.rs b/src/scripts/softpal/img/pgd/mod.rs index eac82fc..6a7a3ca 100644 --- a/src/scripts/softpal/img/pgd/mod.rs +++ b/src/scripts/softpal/img/pgd/mod.rs @@ -1,2 +1,3 @@ mod base; pub mod ge; +pub mod pgd3; diff --git a/src/scripts/softpal/img/pgd/pgd3.rs b/src/scripts/softpal/img/pgd/pgd3.rs new file mode 100644 index 0000000..3586ed7 --- /dev/null +++ b/src/scripts/softpal/img/pgd/pgd3.rs @@ -0,0 +1,198 @@ +use super::base::*; +use crate::ext::io::*; +use crate::scripts::base::*; +use crate::types::*; +use crate::utils::img::*; +use crate::utils::struct_pack::*; +use anyhow::Result; +use std::io::{Read, Seek}; + +#[derive(Debug)] +pub struct Pgd3Builder {} + +impl Pgd3Builder { + pub fn new() -> Self { + Self {} + } +} + +impl ScriptBuilder for Pgd3Builder { + fn default_encoding(&self) -> Encoding { + Encoding::Cp932 + } + + fn build_script( + &self, + buf: Vec, + filename: &str, + encoding: Encoding, + _archive_encoding: Encoding, + config: &ExtraConfig, + archive: Option<&Box>, + ) -> Result> { + Ok(Box::new(Pgd3::new( + MemReader::new(buf), + filename, + encoding, + config, + archive, + )?)) + } + + fn extensions(&self) -> &'static [&'static str] { + &["pgd"] + } + + fn script_type(&self) -> &'static ScriptType { + &ScriptType::SoftpalPgd3 + } + + 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"PGD3") || buf.starts_with(b"PGD2")) { + return Some(20); + } + None + } +} + +#[derive(Debug)] +pub struct Pgd3 { + header: PgdDiffHeader, + base_header: PgdGeHeader, + base: ImageData, + diff: ImageData, +} + +impl Pgd3 { + pub fn new( + mut reader: R, + filename: &str, + encoding: Encoding, + _config: &ExtraConfig, + archive: Option<&Box>, + ) -> Result { + let mut sig = [0u8; 4]; + reader.read_exact(&mut sig)?; + if &sig != b"PGD3" && &sig != b"PGD2" { + return Err(anyhow::anyhow!("Not a valid PGD3/PGD2 file")); + } + let header = PgdDiffHeader::unpack(&mut reader, false, encoding)?; + let diff = PgdReader::with_diff_header(reader, &header)?.unpack_overlay()?; + let base: Vec = if let Some(archive) = archive { + let mut file = archive.open_file_by_name(&header.base_name, true)?; + file.data()? + } else { + let path = { + let mut pb = std::path::PathBuf::from(filename); + pb.set_file_name(&header.base_name); + pb + }; + crate::utils::files::read_file(&path).map_err(|e| { + anyhow::anyhow!("Failed to read base image file '{}': {}", path.display(), e) + })? + }; + let mut reader = MemReader::new(base); + reader.read_exact(&mut sig)?; + if &sig != b"GE \0" { + return Err(anyhow::anyhow!( + "Base image file '{}' is not a valid GE file", + header.base_name + )); + } + let base_header = PgdGeHeader::unpack(&mut reader, false, encoding)?; + let base = PgdReader::with_ge_header(reader, &base_header)?.unpack_ge()?; + Ok(Self { + header, + base_header, + base, + diff, + }) + } +} + +impl Script for Pgd3 { + 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 base = if self.base_header.is_base_file() { + self.base.clone() + } else { + draw_on_canvas( + self.base.clone(), + self.base_header.canvas_width, + self.base_header.canvas_height, + self.base_header.offset_x, + self.base_header.offset_y, + )? + }; + draw_on_img( + &mut base, + &self.diff, + self.header.offset_x as u32, + self.header.offset_y as u32, + )?; + Ok(base) + } +} + +fn draw_on_img(base: &mut ImageData, diff: &ImageData, left: u32, top: u32) -> Result<()> { + if base.color_type != diff.color_type { + return Err(anyhow::anyhow!( + "Color types do not match: {:?} vs {:?}", + base.color_type, + diff.color_type + )); + } + let bpp = base.color_type.bpp(1) as usize; + let base_stride = base.width as usize * bpp; + let diff_stride = diff.width as usize * bpp; + + for y in 0..diff.height { + let base_y = top + y; + if base_y >= base.height { + continue; // Skip if the base image is not tall enough + } + + for x in 0..diff.width { + let base_x = left + x; + if base_x >= base.width { + continue; // Skip if the base image is not wide enough + } + + let base_index = (base_y as usize * base_stride) + (base_x as usize * bpp); + let diff_index = (y as usize * diff_stride) + (x as usize * bpp); + + let diff_pixel = &diff.data[diff_index..diff_index + bpp]; + let base_pixel_orig = base.data[base_index..base_index + bpp].to_vec(); + let mut b = base_pixel_orig[0]; + let mut g = base_pixel_orig[1]; + let mut r = base_pixel_orig[2]; + b ^= diff_pixel[0]; + g ^= diff_pixel[1]; + r ^= diff_pixel[2]; + base.data[base_index] = b; + base.data[base_index + 1] = g; + base.data[base_index + 2] = r; + if bpp == 4 { + let mut a = base_pixel_orig[3]; + a ^= diff_pixel[3]; + base.data[base_index + 3] = a; + } + } + } + Ok(()) +} diff --git a/src/types.rs b/src/types.rs index ef946c0..25d2e7c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -610,6 +610,10 @@ pub enum ScriptType { #[value(alias = "pgd-ge", alias = "pgd")] /// Softpal Pgd Ge image SoftpalPgdGe, + #[cfg(feature = "softpal-img")] + #[value(alias = "softpal-pgd2", alias = "pgd3", alias = "pgd2")] + /// Softpal Pgd Differential image + SoftpalPgd3, #[cfg(feature = "will-plus")] #[value(alias("adv-hd-ws2"))] /// WillPlus ws2 script