mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-06 12:58:45 +08:00
Add support for export Softpal PGD Differential Image File (.pgd)
This commit is contained in:
@@ -187,6 +187,7 @@ msg-tool create -t <script-type> <input> <output>
|
||||
| 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 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
|
||||
@@ -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<String> =
|
||||
|
||||
@@ -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<T: Read + Seek> {
|
||||
pub input: T,
|
||||
output: Vec<u8>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
_bpp: u8,
|
||||
bpp: u8,
|
||||
method: u32,
|
||||
format: Option<ImageColorType>,
|
||||
}
|
||||
@@ -41,7 +62,7 @@ impl<T: Read + Seek> PgdReader<T> {
|
||||
output,
|
||||
width: 0,
|
||||
height: 0,
|
||||
_bpp: 0,
|
||||
bpp: 0,
|
||||
method: 0,
|
||||
format: None,
|
||||
})
|
||||
@@ -55,6 +76,14 @@ impl<T: Read + Seek> PgdReader<T> {
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub fn with_diff_header(input: T, header: &PgdDiffHeader) -> Result<Self> {
|
||||
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<ImageData> {
|
||||
self.unpack_ge_pre()?;
|
||||
let data = match self.method {
|
||||
@@ -103,6 +132,28 @@ impl<T: Read + Seek> PgdReader<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn unpack_overlay(&mut self) -> Result<ImageData> {
|
||||
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<Vec<u8>> {
|
||||
self.format = Some(ImageColorType::Bgra);
|
||||
let input = &self.output;
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
mod base;
|
||||
pub mod ge;
|
||||
pub mod pgd3;
|
||||
|
||||
198
src/scripts/softpal/img/pgd/pgd3.rs
Normal file
198
src/scripts/softpal/img/pgd/pgd3.rs
Normal file
@@ -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<u8>,
|
||||
filename: &str,
|
||||
encoding: Encoding,
|
||||
_archive_encoding: Encoding,
|
||||
config: &ExtraConfig,
|
||||
archive: Option<&Box<dyn Script>>,
|
||||
) -> Result<Box<dyn Script>> {
|
||||
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<u8> {
|
||||
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<R: Read + Seek>(
|
||||
mut reader: R,
|
||||
filename: &str,
|
||||
encoding: Encoding,
|
||||
_config: &ExtraConfig,
|
||||
archive: Option<&Box<dyn Script>>,
|
||||
) -> Result<Self> {
|
||||
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<u8> = 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<ImageData> {
|
||||
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(())
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user