Add CRXD support

This commit is contained in:
2025-08-08 23:18:16 +08:00
parent e9c2131372
commit df0e4fb6cf
7 changed files with 341 additions and 4 deletions

View File

@@ -282,5 +282,9 @@ fn detect_script_type(_buf: &[u8], _buf_len: usize, _filename: &str) -> Option<S
if _buf_len >= 4 && _buf.starts_with(b"CRXG") {
return Some(ScriptType::CircusCrx);
}
#[cfg(feature = "circus-img")]
if _buf_len >= 4 && _buf.starts_with(b"CRXD") {
return Some(ScriptType::CircusCrxd);
}
None
}

View File

@@ -7,6 +7,7 @@ use anyhow::Result;
use clap::ValueEnum;
use clap::builder::PossibleValue;
use msg_tool_macro::*;
use overf::wrapping;
use std::io::{Read, Seek, Write};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -135,12 +136,12 @@ impl ScriptBuilder for CrxImageBuilder {
#[derive(Clone, Debug, StructPack, StructUnpack)]
struct Clip {
field_0: u32,
img_width: u16,
img_height: u16,
clip_offset_x: u16,
clip_offset_y: u16,
clip_width: u16,
clip_height: u16,
field_8: u16,
field_a: u16,
width: u16,
height: u16,
}
#[derive(Clone, Debug, StructPack, StructUnpack)]
@@ -232,6 +233,43 @@ impl CrxImage {
})
}
pub fn draw_diff(&self, diff: &Self) -> Result<ImageData> {
let base_header = &self.header;
let diff_header = &diff.header;
let (img_width, img_height) =
if base_header.clips.is_empty() && diff_header.clips.is_empty() {
(
(base_header.width + base_header.inner_x)
.max(diff_header.width + diff_header.inner_x),
(base_header.height + base_header.inner_y)
.max(diff_header.height + diff_header.inner_y),
)
} else {
if base_header.clips.is_empty() {
let clip = &diff_header.clips[0];
(clip.img_width, clip.img_height)
} else {
let clip = &base_header.clips[0];
(clip.img_width, clip.img_height)
}
};
let base = self.export_image()?;
let mut nw = draw_on_canvas(
base,
img_width as u32,
img_height as u32,
base_header.inner_x as u32,
base_header.inner_y as u32,
)?;
draw_on_img(
&mut nw,
&diff.export_image()?,
diff_header.inner_x as u32,
diff_header.inner_y as u32,
)?;
Ok(nw)
}
fn decode_row0(
dst: &mut Vec<u8>,
mut dst_p: usize,
@@ -907,3 +945,55 @@ impl Script for CrxImage {
Ok(())
}
}
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];
wrapping! {
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];
wrapping! {
a -= diff_pixel[3];
}
base.data[base_index + 3] = a;
}
}
}
Ok(())
}

View File

@@ -0,0 +1,163 @@
use super::crx::CrxImage;
use crate::ext::io::*;
use crate::scripts::base::*;
use crate::types::*;
use anyhow::Result;
use std::io::{Read, Seek};
#[derive(Debug)]
pub struct CrxdImageBuilder {}
impl CrxdImageBuilder {
pub fn new() -> Self {
Self {}
}
}
impl ScriptBuilder for CrxdImageBuilder {
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(CrxdImage::new(
MemReader::new(data),
filename,
encoding,
config,
archive,
)?))
}
fn extensions(&self) -> &'static [&'static str] {
&["crx"]
}
fn script_type(&self) -> &'static ScriptType {
&ScriptType::CircusCrxd
}
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"CRXD") {
return Some(255);
}
None
}
}
#[derive(Debug)]
pub struct CrxdImage {
base: CrxImage,
diff: CrxImage,
}
impl CrxdImage {
pub fn new<T: Read + Seek>(
data: T,
filename: &str,
encoding: Encoding,
config: &ExtraConfig,
archive: Option<&Box<dyn Script>>,
) -> Result<Self> {
let mut reader = data;
let mut magic = [0; 4];
reader.read_exact(&mut magic)?;
if magic != *b"CRXD" {
return Err(anyhow::anyhow!("Invalid CRXD magic"));
}
reader.seek_relative(4)?;
let offset = reader.read_u32()?;
let name = reader.read_fstring(0x14, encoding, true)?;
let base = if let Some(archive) = archive {
CrxImage::new(
archive.open_file_by_offset(offset as u64)?.to_data()?,
config,
)?
} else {
let mut nf = std::path::PathBuf::from(filename);
nf.set_file_name(name);
let f = std::fs::File::open(nf)?;
CrxImage::new(std::io::BufReader::new(f), config)?
};
let mut typ = [0; 4];
reader.read_exact(&mut typ)?;
if typ == *b"CRXJ" {
reader.seek_relative(4)?;
let offset = reader.read_u32()?;
let diff = Self::read_diff(
archive
.ok_or(anyhow::anyhow!("No archive provided"))?
.open_file_by_offset(offset as u64)?
.to_data()?,
archive.clone(),
config,
)?;
return Ok(Self { base, diff });
} else if typ == *b"CRXG" {
let reader = StreamRegion::with_start_pos(reader, 0x20)?;
let diff = CrxImage::new(reader, config)?;
return Ok(Self { base, diff });
}
Err(anyhow::anyhow!("Unsupported diff CRXD type: {:?}", typ))
}
fn read_diff<T: Read + Seek>(
mut reader: T,
archive: Option<&Box<dyn Script>>,
config: &ExtraConfig,
) -> Result<CrxImage> {
let mut magic = [0; 4];
reader.read_exact(&mut magic)?;
if magic != *b"CRXD" {
return Err(anyhow::anyhow!("Invalid CRXD magic"));
}
reader.seek_relative(0x1C)?;
let mut typ = [0; 4];
reader.read_exact(&mut typ)?;
if typ == *b"CRXJ" {
reader.seek_relative(4)?;
let offset = reader.read_u32()?;
return Ok(CrxImage::new(
archive
.ok_or(anyhow::anyhow!("No archive provided"))?
.open_file_by_offset(offset as u64)?
.to_data()?,
config,
)?);
} else if typ == *b"CRXG" {
let reader = StreamRegion::with_start_pos(reader, 0x20)?;
return Ok(CrxImage::new(reader, config)?);
}
Err(anyhow::anyhow!("Unsupported diff CRXD type: {:?}", typ))
}
}
impl Script for CrxdImage {
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> {
self.base.draw_diff(&self.diff)
}
}

View File

@@ -1 +1,2 @@
pub mod crx;
pub mod crxd;

View File

@@ -96,6 +96,8 @@ lazy_static::lazy_static! {
Box::new(circus::archive::dat::DatArchiveBuilder::new()),
#[cfg(feature = "circus-arc")]
Box::new(circus::archive::crm::CrmArchiveBuilder::new()),
#[cfg(feature = "circus-img")]
Box::new(circus::image::crxd::CrxdImageBuilder::new()),
];
pub static ref ALL_EXTS: Vec<String> =
BUILDER.iter().flat_map(|b| b.extensions()).map(|s| s.to_string()).collect();