Add decode support for crx

This commit is contained in:
2025-07-29 14:58:30 +08:00
parent 71d19cfd81
commit 70943f4a3e
9 changed files with 666 additions and 29 deletions

62
Cargo.lock generated
View File

@@ -130,6 +130,17 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.2.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7"
dependencies = [
"jobserver",
"libc",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -570,6 +581,16 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jobserver"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
dependencies = [
"getrandom",
"libc",
]
[[package]]
name = "json"
version = "0.12.4"
@@ -648,6 +669,7 @@ dependencies = [
"url",
"utf16string",
"windows-sys",
"zstd",
]
[[package]]
@@ -681,6 +703,12 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "png"
version = "0.17.16"
@@ -843,6 +871,12 @@ dependencies = [
"digest",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "simd-adler32"
version = "0.3.7"
@@ -1149,3 +1183,31 @@ dependencies = [
"quote",
"syn",
]
[[package]]
name = "zstd"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.15+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
dependencies = [
"cc",
"pkg-config",
]

View File

@@ -27,9 +27,10 @@ sha1 = { version = "0.10", optional = true }
unicode-segmentation = "1.12"
url = { version = "2.5", optional = true }
utf16string = "0.2"
zstd = { version = "0.13", optional = true }
[features]
default = ["artemis", "artemis-arc", "bgi", "bgi-arc", "bgi-img", "cat-system", "cat-system-arc", "cat-system-img", "circus", "escude", "escude-arc", "hexen-haus", "kirikiri", "kirikiri-img", "will-plus", "yaneurao", "yaneurao-itufuru"]
default = ["artemis", "artemis-arc", "bgi", "bgi-arc", "bgi-img", "cat-system", "cat-system-arc", "cat-system-img", "circus", "circus-img", "escude", "escude-arc", "hexen-haus", "kirikiri", "kirikiri-img", "will-plus", "yaneurao", "yaneurao-itufuru"]
artemis = ["utils-escape"]
artemis-arc = ["artemis", "msg_tool_macro/artemis-arc", "sha1"]
bgi = []
@@ -39,6 +40,7 @@ cat-system = ["fancy-regex", "flate2", "int-enum"]
cat-system-arc = ["cat-system", "blowfish", "utils-crc32"]
cat-system-img = ["cat-system", "flate2", "image", "utils-bit-stream"]
circus = []
circus-img = ["circus", "image", "flate2", "zstd"]
escude = ["int-enum"]
escude-arc = ["escude", "rand", "utils-bit-stream"]
hexen-haus = ["memchr", "utils-str"]

View File

@@ -74,7 +74,10 @@ pub fn struct_unpack_impl_for_num(item: TokenStream) -> TokenStream {
/// * `fvec = <len>` attribute can be used to specify a fixed vector length for Vec<_> fields.
/// * `pstring(<len_type>)` attribute can be used to specify a packed string length for String fields, where `<len_type>` can be `u8`, `u16`, `u32`, or `u64`.
/// Length is read as a prefix before the string data.
#[proc_macro_derive(StructPack, attributes(skip_pack, fstring, fstring_pad, fvec, pstring))]
/// * `pvec(<len_type>)` attribute can be used to specify a packed vector length for Vec<_> fields, where `<len_type>` can be `u8`, `u16`, `u32`, or `u64`.
/// Length is read as a prefix before the vector data.
/// * `skip_pack_if(<expr>)` attribute can be used to skip packing a field if the expression evaluates to true. The expression must be a valid Rust expression that evaluates to a boolean.
#[proc_macro_derive(StructPack, attributes(skip_pack, fstring, fstring_pad, fvec, pstring, pvec, skip_pack_if))]
pub fn struct_pack_derive(input: TokenStream) -> TokenStream {
let a = syn::parse_macro_input!(input as PackStruct);
match a {
@@ -87,6 +90,9 @@ pub fn struct_pack_derive(input: TokenStream) -> TokenStream {
let mut fixed_vec: Option<usize> = None;
let mut fstring_pad = 0u8; // Default padding byte
let mut pstring_type: Option<syn::Ident> = None;
let mut pvec_type: Option<syn::Ident> = None;
let mut cur = None;
let mut skip_if = None;
for attr in &field.attrs {
let path = attr.path();
if path.is_ident("skip_pack") {
@@ -132,6 +138,27 @@ pub fn struct_pack_derive(input: TokenStream) -> TokenStream {
Ok(())
}).unwrap();
}
} else if path.is_ident("pvec") {
if let syn::Meta::List(list) = &attr.meta {
list.parse_nested_meta(|meta| {
if meta.path.is_ident("u8") {
pvec_type = Some(syn::Ident::new("u8", meta.path.span()));
} else if meta.path.is_ident("u16") {
pvec_type = Some(syn::Ident::new("u16", meta.path.span()));
} else if meta.path.is_ident("u32") {
pvec_type = Some(syn::Ident::new("u32", meta.path.span()));
} else if meta.path.is_ident("u64") {
pvec_type = Some(syn::Ident::new("u64", meta.path.span()));
} else {
return Err(meta.error("Expected u8, u16, or u32 for pvec"));
}
Ok(())
}).unwrap();
}
} else if path.is_ident("skip_pack_if") {
if let syn::Meta::List(list) = &attr.meta {
skip_if = Some(list.parse_args::<syn::Expr>().unwrap());
}
}
}
if skipped {
@@ -150,7 +177,7 @@ pub fn struct_pack_derive(input: TokenStream) -> TokenStream {
if let Some(segment) = type_path.path.segments.last() {
if segment.ident == "String" {
if let Some(fixed_string) = fixed_string {
return quote::quote! {
cur = Some(quote::quote! {
let s = encode_string(encoding, &self.#field_name, true)?;
let mut slen = s.len();
if slen > #fixed_string {
@@ -164,35 +191,54 @@ pub fn struct_pack_derive(input: TokenStream) -> TokenStream {
for _ in slen..#fixed_string {
writer.write_all(&[#fstring_pad])?;
}
};
}
if let Some(pstring_type) = pstring_type {
});
} else if let Some(pstring_type) = pstring_type {
let write_fn = syn::Ident::new(format!("write_{}", pstring_type).as_str(), pstring_type.span());
return quote::quote! {
cur = Some(quote::quote! {
let encoded = crate::utils::encoding::encode_string(encoding, &self.#field_name, true)?;
writer.#write_fn(encoded.len() as #pstring_type)?;
writer.write_all(&encoded)?;
};
});
}
}
}
if let Some(segment) = type_path.path.segments.first() {
if segment.ident == "Vec" {
if let Some(fixed_vec) = fixed_vec {
return quote::quote! {
cur = Some(quote::quote! {
if self.#field_name.len() != #fixed_vec {
return Err(anyhow::anyhow!("Vector length was not equal to {}", #fixed_vec));
}
for item in &self.#field_name {
item.pack(writer, big, encoding)?;
}
};
});
} else if let Some(pvec_type) = pvec_type {
let write_fn = syn::Ident::new(format!("write_{}", pvec_type).as_str(), pvec_type.span());
cur = Some(quote::quote! {
let len = self.#field_name.len() as #pvec_type;
writer.#write_fn(len)?;
for item in &self.#field_name {
item.pack(writer, big, encoding)?;
}
});
}
}
}
}
quote::quote! {
self.#field_name.pack(writer, big, encoding)?;
let p = cur.unwrap_or_else(|| {
quote::quote! {
self.#field_name.pack(writer, big, encoding)?;
}
});
if let Some(skip_if) = skip_if {
quote::quote! {
if !(#skip_if) {
#p
}
}
} else {
p
}
});
let output = quote::quote! {
@@ -226,6 +272,10 @@ pub fn struct_pack_derive(input: TokenStream) -> TokenStream {
let mut fixed_string: Option<usize> = None;
let mut fixed_vec: Option<usize> = None;
let mut fstring_pad = 0u8; // Default padding byte
let mut pstring_type: Option<syn::Ident> = None;
let mut pvec_type: Option<syn::Ident> = None;
let mut cur = None;
let mut skip_if = None;
for attr in &field.attrs {
let path = attr.path();
if path.is_ident("skip_pack") {
@@ -254,6 +304,44 @@ pub fn struct_pack_derive(input: TokenStream) -> TokenStream {
}
}
}
} else if path.is_ident("pstring") {
if let syn::Meta::List(list) = &attr.meta {
list.parse_nested_meta(|meta| {
if meta.path.is_ident("u8") {
pstring_type = Some(syn::Ident::new("u8", meta.path.span()));
} else if meta.path.is_ident("u16") {
pstring_type = Some(syn::Ident::new("u16", meta.path.span()));
} else if meta.path.is_ident("u32") {
pstring_type = Some(syn::Ident::new("u32", meta.path.span()));
} else if meta.path.is_ident("u64") {
pstring_type = Some(syn::Ident::new("u64", meta.path.span()));
} else {
return Err(meta.error("Expected u8, u16, or u32 for pstring"));
}
Ok(())
}).unwrap();
}
} else if path.is_ident("pvec") {
if let syn::Meta::List(list) = &attr.meta {
list.parse_nested_meta(|meta| {
if meta.path.is_ident("u8") {
pvec_type = Some(syn::Ident::new("u8", meta.path.span()));
} else if meta.path.is_ident("u16") {
pvec_type = Some(syn::Ident::new("u16", meta.path.span()));
} else if meta.path.is_ident("u32") {
pvec_type = Some(syn::Ident::new("u32", meta.path.span()));
} else if meta.path.is_ident("u64") {
pvec_type = Some(syn::Ident::new("u64", meta.path.span()));
} else {
return Err(meta.error("Expected u8, u16, or u32 for pvec"));
}
Ok(())
}).unwrap();
}
} else if path.is_ident("skip_pack_if") {
if let syn::Meta::List(list) = &attr.meta {
skip_if = Some(list.parse_args::<syn::Expr>().unwrap());
}
}
}
if skipped {
@@ -273,7 +361,7 @@ pub fn struct_pack_derive(input: TokenStream) -> TokenStream {
if let Some(segment) = type_path.path.segments.last() {
if segment.ident == "String" {
if let Some(fixed_string) = fixed_string {
return quote::quote! {
cur = Some(quote::quote! {
let s = encode_string(encoding, &#field_name, true)?;
let mut slen = s.len();
if slen > #fixed_string {
@@ -287,27 +375,54 @@ pub fn struct_pack_derive(input: TokenStream) -> TokenStream {
for _ in slen..#fixed_string {
writer.write_all(&[#fstring_pad])?;
}
};
});
} else if let Some(pstring_type) = pstring_type {
let write_fn = syn::Ident::new(format!("write_{}", pstring_type).as_str(), pstring_type.span());
cur = Some(quote::quote! {
let encoded = crate::utils::encoding::encode_string(encoding, &#field_name, true)?;
writer.#write_fn(encoded.len() as #pstring_type)?;
writer.write_all(&encoded)?;
});
}
}
}
if let Some(segment) = type_path.path.segments.first() {
if segment.ident == "Vec" {
if let Some(fixed_vec) = fixed_vec {
return quote::quote! {
cur = Some(quote::quote! {
if #field_name.len() != #fixed_vec {
return Err(anyhow::anyhow!("Vector length was not equal to {}", #fixed_vec));
}
for item in &#field_name {
item.pack(writer, big, encoding)?;
}
};
});
} else if let Some(pvec_type) = pvec_type {
let write_fn = syn::Ident::new(format!("write_{}", pvec_type).as_str(), pvec_type.span());
cur = Some(quote::quote! {
let len = #field_name.len() as #pvec_type;
writer.#write_fn(len)?;
for item in &#field_name {
item.pack(writer, big, encoding)?;
}
});
}
}
}
}
quote::quote! {
#field_name.pack(writer, big, encoding)?;
let p = cur.unwrap_or_else(|| {
quote::quote! {
#field_name.pack(writer, big, encoding)?;
}
});
if let Some(skip_if) = skip_if {
quote::quote! {
if !(#skip_if) {
#p
}
}
} else {
p
}
}).collect();
let idents = if is_struct_like {
@@ -351,7 +466,10 @@ pub fn struct_pack_derive(input: TokenStream) -> TokenStream {
/// * `fvec = <len>` attribute can be used to specify a fixed vector length for Vec<_> fields.
/// * `pstring(<number_type>)` attribute can be used to specify a packed string length for String fields, where `<number_type>` can be `u8`, `u16`, `u32` or `u64`.
/// length is read as a prefix before the string data.
#[proc_macro_derive(StructUnpack, attributes(skip_unpack, fstring, fstring_no_trim, fvec, pstring))]
/// * `pvec(<number_type>)` attribute can be used to specify a packed vector length for Vec<_> fields, where `<number_type>` can be `u8`, `u16`, `u32` or `u64`.
/// length is read as a prefix before the vector data.
/// * `skip_unpack_if(<expr>)` attribute can be used to skip unpacking a field if the expression evaluates to true. The expression must be a valid Rust expression that evaluates to a boolean.
#[proc_macro_derive(StructUnpack, attributes(skip_unpack, fstring, fstring_no_trim, fvec, pstring, pvec, skip_unpack_if))]
pub fn struct_unpack_derive(input: TokenStream) -> TokenStream {
let sut = syn::parse_macro_input!(input as syn::ItemStruct);
let name = sut.ident;
@@ -364,6 +482,9 @@ pub fn struct_unpack_derive(input: TokenStream) -> TokenStream {
let mut fstring_no_trim = false;
let mut fixed_vec: Option<usize> = None;
let mut pstring_type: Option<syn::Ident> = None;
let mut pvec_type: Option<syn::Ident> = None;
let mut cur = None;
let mut skip_if: Option<syn::Expr> = None;
for attr in &field.attrs {
let path = attr.path();
if path.is_ident("skip_unpack") {
@@ -403,6 +524,27 @@ pub fn struct_unpack_derive(input: TokenStream) -> TokenStream {
Ok(())
}).unwrap();
}
} else if path.is_ident("pvec") {
if let syn::Meta::List(list) = &attr.meta {
list.parse_nested_meta(|meta| {
if meta.path.is_ident("u8") {
pvec_type = Some(syn::Ident::new("u8", meta.path.span()));
} else if meta.path.is_ident("u16") {
pvec_type = Some(syn::Ident::new("u16", meta.path.span()));
} else if meta.path.is_ident("u32") {
pvec_type = Some(syn::Ident::new("u32", meta.path.span()));
} else if meta.path.is_ident("u64") {
pvec_type = Some(syn::Ident::new("u64", meta.path.span()));
} else {
return Err(meta.error("Expected u8, u16, or u32 for pvec"));
}
Ok(())
}).unwrap();
}
} else if path.is_ident("skip_unpack_if") {
if let syn::Meta::List(list) = &attr.meta {
skip_if = Some(list.parse_args::<syn::Expr>().unwrap());
}
}
}
let field_name = match &field.ident {
@@ -426,32 +568,51 @@ pub fn struct_unpack_derive(input: TokenStream) -> TokenStream {
if segment.ident == "String" {
if let Some(fixed_string) = fixed_string {
let trim = syn::LitBool::new(!fstring_no_trim, field.span());
return quote::quote! {
cur = Some(quote::quote! {
let #field_name = reader.read_fstring(#fixed_string, encoding, #trim)?;
};
}
if let Some(pstring_type) = pstring_type {
});
} else if let Some(pstring_type) = pstring_type {
let read_fn = syn::Ident::new(format!("read_{}", pstring_type).as_str(), pstring_type.span());
return quote::quote! {
cur = Some(quote::quote! {
let len = reader.#read_fn()? as usize;
let #field_name = reader.read_exact_vec(len)?;
let #field_name = crate::utils::encoding::decode_to_string(encoding, &#field_name, true)?;
}
});
}
}
}
if let Some(segment) = type_path.path.segments.first() {
if segment.ident == "Vec" {
if let Some(fixed_vec) = fixed_vec {
return quote::quote! {
cur = Some(quote::quote! {
let #field_name = reader.read_struct_vec(#fixed_vec, big, encoding)?;
};
});
} else if let Some(pvec_type) = pvec_type {
let read_fn = syn::Ident::new(format!("read_{}", pvec_type).as_str(), pvec_type.span());
cur = Some(quote::quote! {
let len = reader.#read_fn()? as usize;
let #field_name = reader.read_struct_vec(len, big, encoding)?;
});
}
}
}
}
quote::quote! {
let #field_name = #field_type::unpack(&mut reader, big, encoding)?;
let p = cur.unwrap_or_else(|| {
quote::quote! {
let #field_name = #field_type::unpack(&mut reader, big, encoding)?;
}
});
if let Some(skip_if) = skip_if {
quote::quote! {
let #field_name = if !(#skip_if) {
#p
#field_name
} else {
Default::default()
};
}
} else {
p
}
}).collect();
let fields = if is_tuple_struct {

View File

@@ -922,6 +922,19 @@ impl<T: Write + Seek> WriteAt for T {
}
}
pub trait SeekExt {
fn stream_length(&mut self) -> Result<u64>;
}
impl<T: Seek> SeekExt for T{
fn stream_length(&mut self) -> Result<u64> {
let current_pos = self.stream_position()?;
let length = self.seek(SeekFrom::End(0))?;
self.seek(SeekFrom::Start(current_pos))?;
Ok(length)
}
}
pub struct MemReader {
pub data: Vec<u8>,
pub pos: usize,

View File

@@ -0,0 +1,391 @@
use crate::ext::io::*;
use crate::scripts::base::*;
use crate::types::*;
use crate::utils::struct_pack::*;
use anyhow::Result;
use msg_tool_macro::*;
use std::io::{Read, Seek, Write};
#[derive(Debug)]
pub struct CrxImageBuilder {}
impl CrxImageBuilder {
pub const fn new() -> Self {
CrxImageBuilder {}
}
}
impl ScriptBuilder for CrxImageBuilder {
fn default_encoding(&self) -> Encoding {
Encoding::Cp932
}
fn build_script(
&self,
data: Vec<u8>,
_filename: &str,
_encoding: Encoding,
_archive_encoding: Encoding,
config: &ExtraConfig,
) -> Result<Box<dyn Script>> {
Ok(Box::new(CrxImage::new(MemReader::new(data), config)?))
}
fn extensions(&self) -> &'static [&'static str] {
&["crx"]
}
fn script_type(&self) -> &'static ScriptType {
&ScriptType::CircusCrx
}
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"CRXG") {
return Some(255);
}
None
}
}
#[derive(Clone, Debug, StructPack, StructUnpack)]
struct Clip {
field_0: u32,
clip_width: u16,
clip_height: u16,
field_8: u16,
field_a: u16,
width: u16,
height: u16,
}
#[derive(Clone, Debug, StructPack, StructUnpack)]
struct Header {
inner_x: u16,
inner_y: u16,
width: u16,
height: u16,
version: u16,
flags: u16,
bpp: u16,
mode: u16,
#[skip_pack_if(self.version != 3)]
#[skip_unpack_if(version != 3)]
#[pvec(u32)]
clips: Vec<Clip>,
}
pub struct CrxImage {
header: Header,
color_type: ImageColorType,
data: Vec<u8>,
}
impl std::fmt::Debug for CrxImage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CrxImage")
.field("header", &self.header)
.field("color_type", &self.color_type)
.field("data_length", &self.data.len())
.finish()
}
}
impl CrxImage {
pub fn new<T: Read + Seek>(data: T, _config: &ExtraConfig) -> Result<Self> {
let mut reader = data;
let mut magic = [0; 4];
reader.read_exact(&mut magic)?;
if magic != *b"CRXG" {
return Err(anyhow::anyhow!("Invalid CRX image magic"));
}
let header: Header = reader.read_struct(false, Encoding::Utf8)?;
if header.version < 2 || header.version > 3 {
return Err(anyhow::anyhow!(
"Unsupported CRX version: {}",
header.version
));
}
let color_type = if header.bpp == 0 {
ImageColorType::Bgr
} else if header.bpp == 1 {
ImageColorType::Bgra
} else {
return Err(anyhow::anyhow!("Unsupported CRX bpp: {}", header.bpp));
};
let compressed_size = if (header.flags & 0x10) == 0 {
let len = reader.stream_length()?;
(len - reader.stream_position()?) as u32
} else {
reader.read_u32()?
};
let compressed_data = reader.read_exact_vec(compressed_size as usize)?;
let uncompessed = if compressed_data.starts_with(&[0x28, 0xb5, 0x2f, 0xfd]) {
let mut decoder = zstd::Decoder::new(MemReaderRef::new(&compressed_data))?;
let mut decompressed_data = Vec::new();
decoder.read_to_end(&mut decompressed_data)?;
decompressed_data
} else {
let mut decompressed_data = Vec::new();
flate2::read::ZlibDecoder::new(MemReaderRef::new(&compressed_data))
.read_to_end(&mut decompressed_data)?;
decompressed_data
};
Ok(CrxImage {
header,
color_type,
data: uncompessed,
})
}
fn decode_row0(
dst: &mut Vec<u8>,
mut dst_p: usize,
src: &[u8],
mut src_p: usize,
width: u16,
pixel_size: u8,
) -> Result<usize> {
let mut prev_p = dst_p;
for _ in 0..pixel_size {
dst[dst_p] = src[src_p];
dst_p += 1;
src_p += 1;
}
let remaining = width - 1;
for _ in 0..remaining {
for _ in 0..pixel_size {
dst[dst_p] = src[src_p].overflowing_add(dst[prev_p]).0;
dst_p += 1;
src_p += 1;
prev_p += 1;
}
}
Ok(src_p)
}
fn decode_row1(
dst: &mut Vec<u8>,
mut dst_p: usize,
src: &[u8],
mut src_p: usize,
width: u16,
pixel_size: u8,
mut prev_row_p: usize,
) -> Result<usize> {
for _ in 0..width {
for _ in 0..pixel_size {
dst[dst_p] = src[src_p].overflowing_add(dst[prev_row_p]).0;
dst_p += 1;
src_p += 1;
prev_row_p += 1;
}
}
Ok(src_p)
}
fn decode_row2(
dst: &mut Vec<u8>,
mut dst_p: usize,
src: &[u8],
mut src_p: usize,
width: u16,
pixel_size: u8,
mut prev_row_p: usize,
) -> Result<usize> {
for _ in 0..pixel_size {
dst[dst_p] = src[src_p];
dst_p += 1;
src_p += 1;
}
let remaining = width - 1;
for _ in 0..remaining {
for _ in 0..pixel_size {
dst[dst_p] = src[src_p].overflowing_add(dst[prev_row_p]).0;
dst_p += 1;
src_p += 1;
prev_row_p += 1;
}
}
Ok(src_p)
}
fn decode_row3(
dst: &mut Vec<u8>,
mut dst_p: usize,
src: &[u8],
mut src_p: usize,
width: u16,
pixel_size: u8,
mut prev_row_p: usize,
) -> Result<usize> {
let count = width - 1;
prev_row_p += pixel_size as usize;
for _ in 0..count {
for _ in 0..pixel_size {
dst[dst_p] = src[src_p].overflowing_add(dst[prev_row_p]).0;
dst_p += 1;
src_p += 1;
prev_row_p += 1;
}
}
for _ in 0..pixel_size {
dst[dst_p] = src[src_p];
dst_p += 1;
src_p += 1;
}
Ok(src_p)
}
fn decode_row4(
dst: &mut Vec<u8>,
dst_p: usize,
src: &[u8],
mut src_p: usize,
width: u16,
pixel_size: u8,
) -> Result<usize> {
for offset in 0..pixel_size {
let mut dst_c = dst_p + offset as usize;
let mut remaining = width;
let value = src[src_p];
src_p += 1;
dst[dst_c] = value;
dst_c += pixel_size as usize;
remaining -= 1;
if remaining == 0 {
continue;
}
if value == src[src_p] {
src_p += 1;
let count = src[src_p] as u16;
src_p += 1;
remaining -= count;
for _ in 0..count {
dst[dst_c] = value;
dst_c += pixel_size as usize;
}
}
while remaining > 0 {
let value = src[src_p];
src_p += 1;
dst[dst_c] = value;
dst_c += pixel_size as usize;
remaining -= 1;
if remaining == 0 {
break;
}
if value == src[src_p] {
src_p += 1;
let count = src[src_p] as u16;
src_p += 1;
remaining -= count;
for _ in 0..count {
dst[dst_c] = value;
dst_c += pixel_size as usize;
}
}
}
}
Ok(src_p)
}
fn decode_image(
dst: &mut Vec<u8>,
src: &[u8],
width: u16,
height: u16,
pixel_size: u8,
encode_type: &mut Vec<u8>,
) -> Result<()> {
let mut src_p = 0;
let mut dst_p = 0;
let mut prev_row_p = 0;
for _ in 0..height {
let data = src[src_p];
encode_type.push(data);
src_p += 1;
match data {
0 => {
src_p = Self::decode_row0(dst, dst_p, src, src_p, width, pixel_size)?;
}
1 => {
src_p =
Self::decode_row1(dst, dst_p, src, src_p, width, pixel_size, prev_row_p)?;
}
2 => {
src_p =
Self::decode_row2(dst, dst_p, src, src_p, width, pixel_size, prev_row_p)?;
}
3 => {
src_p =
Self::decode_row3(dst, dst_p, src, src_p, width, pixel_size, prev_row_p)?;
}
4 => {
src_p = Self::decode_row4(dst, dst_p, src, src_p, width, pixel_size)?;
}
_ => {
return Err(anyhow::anyhow!("Invalid row type: {}", data));
}
}
prev_row_p = dst_p;
dst_p += pixel_size as usize * width as usize;
}
Ok(())
}
}
impl Script for CrxImage {
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 data_size = self.color_type.bpp(1) as usize * self.header.width as usize * self.header.height as usize;
let mut data = vec![0; data_size];
let mut encode_type = Vec::new();
Self::decode_image(
&mut data,
&self.data,
self.header.width,
self.header.height,
self.color_type.bpp(1) as u8,
&mut encode_type,
)?;
if self.color_type.bpp(1) == 4 && self.header.mode != 1 {
let alpha_flip = if self.header.mode == 2 {
0
} else {
0xFF
};
for i in (0..data_size).step_by(4) {
let a = data[i];
let b = data[i + 1];
let g = data[i + 2];
let r = data[i + 3];
data[i] = b;
data[i + 1] = g;
data[i + 2] = r;
data[i + 3] = a ^ alpha_flip;
}
}
Ok(ImageData {
width: self.header.width as u32,
height: self.header.height as u32,
depth: 8,
color_type: self.color_type,
data,
})
}
}

View File

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

View File

@@ -1,2 +1,4 @@
#[cfg(feature = "circus-img")]
pub mod image;
mod info;
pub mod script;

View File

@@ -80,6 +80,8 @@ lazy_static::lazy_static! {
Box::new(artemis::asb::ArtemisAsbBuilder::new()),
#[cfg(feature = "hexen-haus")]
Box::new(hexen_haus::bin::BinScriptBuilder::new()),
#[cfg(feature = "circus-img")]
Box::new(circus::image::crx::CrxImageBuilder::new()),
];
pub static ref ALL_EXTS: Vec<String> =
BUILDER.iter().flat_map(|b| b.extensions()).map(|s| s.to_string()).collect();

View File

@@ -292,6 +292,9 @@ pub enum ScriptType {
#[cfg(feature = "circus")]
/// Circus MES script
Circus,
#[cfg(feature = "circus-img")]
/// Circus CRX Image
CircusCrx,
#[cfg(feature = "escude-arc")]
/// Escude bin archive
EscudeArc,