From 9c95679887de1421bd74c8353481716818d3435a Mon Sep 17 00:00:00 2001 From: Brett Mayson Date: Wed, 22 Nov 2023 22:58:42 +0000 Subject: [PATCH] bringing over wasm --- .github/workflows/browser.yaml | 21 ++ Cargo.lock | 239 ++++++++++++- Cargo.toml | 2 + libs/lzo/Cargo.toml | 17 + libs/lzo/src/compress.rs | 591 +++++++++++++++++++++++++++++++++ libs/lzo/src/decompress.rs | 445 +++++++++++++++++++++++++ libs/lzo/src/lib.rs | 122 +++++++ libs/paa/Cargo.toml | 26 ++ libs/paa/src/lib.rs | 10 + libs/paa/src/mipmap.rs | 74 +++++ libs/paa/src/paa.rs | 165 +++++++++ libs/paa/src/pax.rs | 80 +++++ libs/paa/src/wasm.rs | 54 +++ libs/paa/tests/dxt1.paa | Bin 0 -> 17835 bytes libs/paa/tests/dxt5.paa | Bin 0 -> 5625 bytes libs/paa/tests/read.rs | 13 + 16 files changed, 1849 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/browser.yaml create mode 100644 libs/lzo/Cargo.toml create mode 100644 libs/lzo/src/compress.rs create mode 100644 libs/lzo/src/decompress.rs create mode 100644 libs/lzo/src/lib.rs create mode 100644 libs/paa/Cargo.toml create mode 100644 libs/paa/src/lib.rs create mode 100644 libs/paa/src/mipmap.rs create mode 100644 libs/paa/src/paa.rs create mode 100644 libs/paa/src/pax.rs create mode 100644 libs/paa/src/wasm.rs create mode 100644 libs/paa/tests/dxt1.paa create mode 100644 libs/paa/tests/dxt5.paa create mode 100644 libs/paa/tests/read.rs diff --git a/.github/workflows/browser.yaml b/.github/workflows/browser.yaml new file mode 100644 index 00000000..e0bec9c3 --- /dev/null +++ b/.github/workflows/browser.yaml @@ -0,0 +1,21 @@ +name: Browser + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build-wasm: + name: Build WASM + runs-on: ubuntu-latest + steps: + - name: Checkout the source code + uses: actions/checkout@master + - name: Install wasm-pack + run: cargo install wasm-pack + - name: Build + run: wasm-pack build libs/paa --target web + - name: tree + run: tree diff --git a/Cargo.lock b/Cargo.lock index c32f277d..b9fd06c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,6 +154,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -181,6 +187,12 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" + [[package]] name = "byteorder" version = "1.5.0" @@ -270,7 +282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" dependencies = [ "ciborium-io", - "half", + "half 1.8.2", ] [[package]] @@ -310,6 +322,12 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colorchoice" version = "1.0.0" @@ -591,12 +609,37 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "exr" +version = "1.71.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8" +dependencies = [ + "bit_field", + "flume", + "half 2.2.1", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "fdeflate" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d6dafc854908ff5da46ff3f8f473c6984119a2876a383a860246dd7841a868" +dependencies = [ + "simd-adler32", +] + [[package]] name = "flate2" version = "1.0.28" @@ -607,6 +650,15 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -630,9 +682,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -712,6 +764,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.28.0" @@ -764,6 +826,15 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + [[package]] name = "hashbrown" version = "0.14.2" @@ -837,6 +908,26 @@ dependencies = [ "vfs", ] +[[package]] +name = "hemtt-lzo" +version = "1.0.1" +dependencies = [ + "libc", +] + +[[package]] +name = "hemtt-paa" +version = "1.0.1" +dependencies = [ + "byteorder", + "hemtt-common", + "hemtt-lzo", + "image", + "js-sys", + "texpresso", + "wasm-bindgen", +] + [[package]] name = "hemtt-pbo" version = "1.0.1" @@ -965,14 +1056,33 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", ] +[[package]] +name = "image" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "exr", + "gif", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", + "qoi", + "tiff", +] + [[package]] name = "indexmap" version = "2.1.0" @@ -1042,6 +1152,15 @@ dependencies = [ "libc", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +dependencies = [ + "rayon", +] + [[package]] name = "js-sys" version = "0.3.65" @@ -1082,9 +1201,15 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ - "spin", + "spin 0.5.2", ] +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.150" @@ -1154,6 +1279,16 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.20" @@ -1201,6 +1336,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", + "simd-adler32", ] [[package]] @@ -1280,6 +1416,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -1417,9 +1564,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" @@ -1533,6 +1680,19 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "png" +version = "0.17.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1563,6 +1723,15 @@ dependencies = [ "cc", ] +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quote" version = "1.0.33" @@ -1989,6 +2158,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.9" @@ -2041,6 +2216,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spki" version = "0.7.2" @@ -2151,6 +2335,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "texpresso" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8277e703c934b9693d0773d5749faacc6366b3d81d012da556a4cfd4ab87f336" +dependencies = [ + "libm", +] + [[package]] name = "thiserror" version = "1.0.50" @@ -2181,6 +2374,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tiff" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.3.30" @@ -2441,9 +2645,9 @@ checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -2582,6 +2786,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + [[package]] name = "whoami" version = "1.4.1" @@ -2888,3 +3098,12 @@ dependencies = [ "cc", "pkg-config", ] + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/Cargo.toml b/Cargo.toml index 0a0d6099..447978a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,8 @@ members = [ "libs/common", "libs/config", + "libs/lzo", + "libs/paa", "libs/pbo", "libs/preprocessor", "libs/signing", diff --git a/libs/lzo/Cargo.toml b/libs/lzo/Cargo.toml new file mode 100644 index 00000000..b36c4325 --- /dev/null +++ b/libs/lzo/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "hemtt-lzo" +version = "1.0.1" +edition = "2021" +description = "An LZO library for hemtt" +license = "GPL-2.0" + +[lints] +workspace = true + +[dependencies] +libc = "0.2.150" + +[features] +default = ["compress", "decompress"] +compress = [] +decompress = [] diff --git a/libs/lzo/src/compress.rs b/libs/lzo/src/compress.rs new file mode 100644 index 00000000..27bf9f98 --- /dev/null +++ b/libs/lzo/src/compress.rs @@ -0,0 +1,591 @@ +#![allow(clippy::cast_sign_loss)] +#![allow(clippy::cast_possible_truncation)] +#![allow(clippy::cast_possible_wrap)] +#![allow(clippy::cast_ptr_alignment)] + +extern "C" { + fn memcpy( + __dest: *mut ::std::os::raw::c_void, + __src: *const ::std::os::raw::c_void, + __n: usize, + ) -> *mut ::std::os::raw::c_void; + fn memset( + __s: *mut ::std::os::raw::c_void, + __c: i32, + __n: usize, + ) -> *mut ::std::os::raw::c_void; +} + +const unsafe extern "C" fn get_unaligned_le32(p: *const ::std::os::raw::c_void) -> u32 { + let input: *const u8 = p.cast::(); + (*input.offset(0isize) as i32 + | *input.offset(1isize) as (i32) << 8i32 + | *input.offset(2isize) as (i32) << 16i32 + | *input.offset(3isize) as (i32) << 24i32) as u32 +} + +unsafe extern "C" fn put_unaligned(mut v: u32, p: *mut ::std::os::raw::c_void) { + memcpy( + p, + std::ptr::addr_of_mut!(v) as *const ::std::os::raw::c_void, + ::std::mem::size_of::(), + ); +} + +unsafe extern "C" fn get_unaligned(p: *const ::std::os::raw::c_void) -> u32 { + let mut ret: u32 = 0u32; + memcpy( + std::ptr::addr_of_mut!(ret).cast::<::std::os::raw::c_void>(), + p, + ::std::mem::size_of::(), + ); + ret +} + +#[allow(clippy::too_many_lines)] +#[allow(clippy::cognitive_complexity)] +#[allow(clippy::similar_names)] +unsafe extern "C" fn lzo1x_1_do_compress( + in_: *const u8, + in_len: usize, + out: *mut u8, + out_len: *mut usize, + mut ti: usize, + wrkmem: *mut ::std::os::raw::c_void, +) -> usize { + let mut current_block; + let mut ip: *const u8; + let mut op: *mut u8; + let in_end: *const u8 = in_.add(in_len); + let ip_end: *const u8 = in_.add(in_len).offset(-20isize); + let mut ii: *const u8; + let dict: *mut u16 = wrkmem.cast::(); + op = out; + ip = in_; + ii = ip; + ip = ip.add(if ti < 4usize { + 4usize.wrapping_sub(ti) + } else { + 0usize + }); + let mut m_pos: *const u8; + let mut t: usize; + let mut m_len: usize; + let mut m_off: usize; + let mut dv: u32; + 'loop2: loop { + ip = ip.offset( + 1isize + + (((ip as isize).wrapping_sub(ii as isize) + / ::std::mem::size_of::() as isize) + >> 5i32), + ); + loop { + if ip >= ip_end { + break 'loop2; + } + dv = get_unaligned_le32(ip.cast::<::std::os::raw::c_void>()); + t = (dv.wrapping_mul(0x1824_429d_u32) >> (32i32 - 13i32) + & (1u32 << 13i32).wrapping_sub(1u32)) as usize; + m_pos = in_.offset(*dict.add(t) as isize); + *dict.add(t) = ((ip as isize).wrapping_sub(in_ as isize) + / ::std::mem::size_of::() as isize) as u16; + if dv != get_unaligned_le32(m_pos.cast::<::std::os::raw::c_void>()) { + break; + } + ii = ii.offset(-(ti as isize)); + ti = 0usize; + t = ((ip as isize).wrapping_sub(ii as isize) / ::std::mem::size_of::() as isize) + as usize; + if t != 0usize { + if t <= 3usize { + { + let rhs = t; + let lhs = &mut *op.offset(-2isize); + *lhs = (*lhs as usize | rhs) as u8; + } + put_unaligned( + get_unaligned(ii.cast::().cast::<::std::os::raw::c_void>()), + op.cast::().cast::<::std::os::raw::c_void>(), + ); + op = op.add(t); + } else if t <= 16usize { + *{ + let old = op; + op = op.offset(1isize); + old + } = t.wrapping_sub(3usize) as u8; + put_unaligned( + get_unaligned(ii.cast::().cast::<::std::os::raw::c_void>()), + op.cast::().cast::<::std::os::raw::c_void>(), + ); + put_unaligned( + get_unaligned( + ii.offset(4isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ), + op.offset(4isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ); + put_unaligned( + get_unaligned( + ii.offset(8isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ), + op.offset(8isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ); + put_unaligned( + get_unaligned( + ii.offset(8isize) + .offset(4isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ), + op.offset(8isize) + .offset(4isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ); + op = op.add(t); + } else { + if t <= 18usize { + *{ + let old = op; + op = op.offset(1isize); + old + } = t.wrapping_sub(3usize) as u8; + } else { + let mut tt: usize = t.wrapping_sub(18usize); + *{ + let old = op; + op = op.offset(1isize); + old + } = 0u8; + loop { + if tt <= 255usize { + break; + } + tt = tt.wrapping_sub(255usize); + *{ + let old = op; + op = op.offset(1isize); + old + } = 0u8; + } + *{ + let old = op; + op = op.offset(1isize); + old + } = tt as u8; + } + loop { + put_unaligned( + get_unaligned(ii.cast::().cast::<::std::os::raw::c_void>()), + op.cast::().cast::<::std::os::raw::c_void>(), + ); + put_unaligned( + get_unaligned( + ii.offset(4isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ), + op.offset(4isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ); + put_unaligned( + get_unaligned( + ii.offset(8isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ), + op.offset(8isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ); + put_unaligned( + get_unaligned( + ii.offset(8isize) + .offset(4isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ), + op.offset(8isize) + .offset(4isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ); + op = op.offset(16isize); + ii = ii.offset(16isize); + t = t.wrapping_sub(16usize); + if t < 16usize { + break; + } + } + if t > 0usize { + loop { + *{ + let old = op; + op = op.offset(1isize); + old + } = *{ + let old = ii; + ii = ii.offset(1isize); + old + }; + t -= 1; + if t == 0 { + break; + } + } + } + } + } + m_len = 4usize; + if i32::from(*ip.add(m_len)) == i32::from(*m_pos.add(m_len)) { + current_block = 22; + } else { + current_block = 31; + } + loop { + if current_block == 22 { + m_len = m_len.wrapping_add(1usize); + if i32::from(*ip.add(m_len)) != i32::from(*m_pos.add(m_len)) { + current_block = 31; + continue; + } + m_len = m_len.wrapping_add(1usize); + if i32::from(*ip.add(m_len)) != i32::from(*m_pos.add(m_len)) { + current_block = 31; + continue; + } + m_len = m_len.wrapping_add(1usize); + if i32::from(*ip.add(m_len)) != i32::from(*m_pos.add(m_len)) { + current_block = 31; + continue; + } + m_len = m_len.wrapping_add(1usize); + if i32::from(*ip.add(m_len)) != i32::from(*m_pos.add(m_len)) { + current_block = 31; + continue; + } + m_len = m_len.wrapping_add(1usize); + if i32::from(*ip.add(m_len)) != i32::from(*m_pos.add(m_len)) { + current_block = 31; + continue; + } + m_len = m_len.wrapping_add(1usize); + if i32::from(*ip.add(m_len)) != i32::from(*m_pos.add(m_len)) { + current_block = 31; + continue; + } + m_len = m_len.wrapping_add(1usize); + if i32::from(*ip.add(m_len)) != i32::from(*m_pos.add(m_len)) { + current_block = 31; + continue; + } + m_len = m_len.wrapping_add(1usize); + if ip.add(m_len) >= ip_end { + current_block = 31; + continue; + } + if i32::from(*ip.add(m_len)) == i32::from(*m_pos.add(m_len)) { + current_block = 22; + } else { + current_block = 31; + } + } else { + m_off = ((ip as isize).wrapping_sub(m_pos as isize) + / ::std::mem::size_of::() as isize) + as usize; + ip = ip.add(m_len); + ii = ip; + if m_len <= 8usize && (m_off <= 0x800usize) { + current_block = 47; + } else { + current_block = 32; + } + break; + } + } + if current_block == 32 { + if m_off <= 0x4000usize { + m_off = m_off.wrapping_sub(1usize); + if m_len <= 33usize { + *{ + let old = op; + op = op.offset(1isize); + old + } = (32usize | m_len.wrapping_sub(2usize)) as u8; + } else { + m_len = m_len.wrapping_sub(33usize); + *{ + let old = op; + op = op.offset(1isize); + old + } = 32i32 as u8; + loop { + if m_len <= 255usize { + break; + } + m_len = m_len.wrapping_sub(255usize); + *{ + let old = op; + op = op.offset(1isize); + old + } = 0u8; + } + *{ + let old = op; + op = op.offset(1isize); + old + } = m_len as u8; + } + } else { + m_off = m_off.wrapping_sub(0x4000usize); + if m_len <= 9usize { + *{ + let old = op; + op = op.offset(1isize); + old + } = (16usize | m_off >> 11i32 & 8usize | m_len.wrapping_sub(2usize)) as u8; + } else { + m_len = m_len.wrapping_sub(9usize); + *{ + let old = op; + op = op.offset(1isize); + old + } = (16usize | m_off >> 11i32 & 8usize) as u8; + loop { + if m_len <= 255usize { + break; + } + m_len = m_len.wrapping_sub(255usize); + *{ + let old = op; + op = op.offset(1isize); + old + } = 0u8; + } + *{ + let old = op; + op = op.offset(1isize); + old + } = m_len as u8; + } + } + *{ + let old = op; + op = op.offset(1isize); + old + } = (m_off << 2i32) as u8; + *{ + let old = op; + op = op.offset(1isize); + old + } = (m_off >> 6i32) as u8; + } else { + m_off = m_off.wrapping_sub(1usize); + *{ + let old = op; + op = op.offset(1isize); + old + } = (m_len.wrapping_sub(1usize) << 5i32 | (m_off & 7usize) << 2i32) as u8; + *{ + let old = op; + op = op.offset(1isize); + old + } = (m_off >> 3i32) as u8; + } + } + } + *out_len = + ((op as isize).wrapping_sub(out as isize) / ::std::mem::size_of::() as isize) as usize; + ((in_end as isize).wrapping_sub(ii.offset(-(ti as isize)) as isize) + / ::std::mem::size_of::() as isize) as usize +} + +#[no_mangle] +#[allow(clippy::too_many_lines)] +#[allow(clippy::cognitive_complexity)] +#[allow(clippy::similar_names)] +#[allow(clippy::module_name_repetitions)] +pub unsafe extern "C" fn lzo1x_1_compress( + in_: *const u8, + in_len: usize, + out: *mut u8, + out_len: *mut usize, + wrkmem: *mut ::std::os::raw::c_void, +) -> i32 { + let mut current_block; + let mut ip: *const u8 = in_; + let mut op: *mut u8 = out; + let mut l: usize = in_len; + let mut t: usize = 0usize; + loop { + if l <= 20usize { + break; + } + let ll: usize = if l <= (0xbfffi32 + 1i32) as usize { + l + } else { + (0xbfffi32 + 1i32) as usize + }; + let ll_end: usize = (ip as usize).wrapping_add(ll); + if ll_end.wrapping_add(t.wrapping_add(ll) >> 5i32) <= ll_end { + break; + } + memset( + wrkmem, + 0i32, + ((1u32 << 13i32) as usize).wrapping_mul(::std::mem::size_of::()), + ); + t = lzo1x_1_do_compress(ip, ll, op, out_len, t, wrkmem); + ip = ip.add(ll); + op = op.add(*out_len); + l = l.wrapping_sub(ll); + } + t = t.wrapping_add(l); + if t > 0usize { + let mut ii: *const u8 = in_.add(in_len).offset(-(t as isize)); + if op == out && (t <= 238usize) { + *{ + let old = op; + op = op.offset(1isize); + old + } = 17usize.wrapping_add(t) as u8; + } else if t <= 3usize { + let rhs = t; + let lhs = &mut *op.offset(-2isize); + *lhs = (*lhs as usize | rhs) as u8; + } else if t <= 18usize { + *{ + let old = op; + op = op.offset(1isize); + old + } = t.wrapping_sub(3usize) as u8; + } else { + let mut tt: usize = t.wrapping_sub(18usize); + *{ + let old = op; + op = op.offset(1isize); + old + } = 0u8; + loop { + if tt <= 255usize { + break; + } + tt = tt.wrapping_sub(255usize); + *{ + let old = op; + op = op.offset(1isize); + old + } = 0u8; + } + *{ + let old = op; + op = op.offset(1isize); + old + } = tt as u8; + } + if t >= 16usize { + current_block = 16; + } else { + current_block = 18; + } + loop { + if current_block == 16 { + put_unaligned( + get_unaligned(ii.cast::().cast::<::std::os::raw::c_void>()), + op.cast::().cast::<::std::os::raw::c_void>(), + ); + put_unaligned( + get_unaligned( + ii.offset(4isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ), + op.offset(4isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ); + put_unaligned( + get_unaligned( + ii.offset(8isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ), + op.offset(8isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ); + put_unaligned( + get_unaligned( + ii.offset(8isize) + .offset(4isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ), + op.offset(8isize) + .offset(4isize) + .cast::() + .cast::<::std::os::raw::c_void>(), + ); + op = op.offset(16isize); + ii = ii.offset(16isize); + t = t.wrapping_sub(16usize); + if t >= 16usize { + current_block = 16; + } else { + current_block = 18; + } + } else if t > 0usize { + current_block = 19; + break; + } else { + current_block = 21; + break; + } + } + if current_block != 21 { + loop { + *{ + let old = op; + op = op.offset(1isize); + old + } = *{ + let old = ii; + ii = ii.offset(1isize); + old + }; + t -= 1; + if t == 0 { + break; + } + } + } + } + *{ + let old = op; + op = op.offset(1isize); + old + } = (16i32 | 1i32) as u8; + *{ + let old = op; + op = op.offset(1isize); + old + } = 0u8; + *{ + let old = op; + op = op.offset(1isize); + old + } = 0u8; + *out_len = + ((op as isize).wrapping_sub(out as isize) / ::std::mem::size_of::() as isize) as usize; + 0i32 +} diff --git a/libs/lzo/src/decompress.rs b/libs/lzo/src/decompress.rs new file mode 100644 index 00000000..a1a44916 --- /dev/null +++ b/libs/lzo/src/decompress.rs @@ -0,0 +1,445 @@ +#![allow(clippy::cast_sign_loss)] +#![allow(clippy::cast_possible_truncation)] +#![allow(clippy::cast_possible_wrap)] +#![allow(clippy::cast_ptr_alignment)] + +const unsafe extern "C" fn get_unaligned_le16(p: *const ::std::os::raw::c_void) -> u16 { + let input: *const u8 = p.cast::(); + (*input.offset(0isize) as i32 | *input.offset(1isize) as (i32) << 8i32) as u16 +} + +#[no_mangle] +#[allow(clippy::too_many_lines)] +#[allow(clippy::cognitive_complexity)] +#[allow(clippy::similar_names)] +pub unsafe extern "C" fn lzo1x_decompress_safe( + in_: *const u8, + in_len: usize, + out: *mut u8, + out_len: *mut usize, +) -> i32 { + let mut current_block; + let mut op: *mut u8; + let mut ip: *const u8; + let mut t: usize; + let mut next: usize; + let mut state: usize = 0usize; + let mut m_pos: *const u8; + let ip_end: *const u8 = in_.add(in_len); + let op_end: *mut u8 = out.add(*out_len); + op = out; + ip = in_; + if in_len >= 3usize { + if i32::from(*ip) > 17i32 { + t = (i32::from(*{ + let old = ip; + ip = ip.offset(1isize); + old + }) - 17i32) as usize; + if t < 4usize { + next = t; + state = next; + t = next; + if (((ip_end as isize).wrapping_sub(ip as isize) + / ::std::mem::size_of::() as isize) as usize) + < t.wrapping_add(3usize) + { + current_block = 64; + } else if (((op_end as isize).wrapping_sub(op as isize) + / ::std::mem::size_of::() as isize) as usize) + < t + { + current_block = 63; + } else { + loop { + if t == 0usize { + break; + } + *{ + let old = op; + op = op.offset(1isize); + old + } = *{ + let old = ip; + ip = ip.offset(1isize); + old + }; + t = t.wrapping_sub(1usize); + } + current_block = 11; + } + } else if (((op_end as isize).wrapping_sub(op as isize) + / ::std::mem::size_of::() as isize) as usize) + < t + { + current_block = 63; + } else if (((ip_end as isize).wrapping_sub(ip as isize) + / ::std::mem::size_of::() as isize) as usize) + < t.wrapping_add(3usize) + { + current_block = 64; + } else { + loop { + *{ + let old = op; + op = op.offset(1isize); + old + } = *{ + let old = ip; + ip = ip.offset(1isize); + old + }; + t -= 1; + if t == 0 { + break; + } + } + state = 4usize; + current_block = 11; + } + } else { + current_block = 11; + } + if current_block == 64 { + } else { + 'loop11: loop { + if current_block == 11 { + t = *{ + let old = ip; + ip = ip.offset(1isize); + old + } as usize; + if t < 16usize { + if state == 0usize { + if t == 0usize { + let mut offset: usize; + let ip_last: *const u8 = ip; + loop { + if i32::from(*ip) != 0i32 { + break; + } + ip = ip.offset(1isize); + if (((ip_end as isize).wrapping_sub(ip as isize) + / ::std::mem::size_of::() as isize) + as usize) + < 1usize + { + current_block = 64; + break 'loop11; + } + } + offset = ((ip as isize).wrapping_sub(ip_last as isize) + / ::std::mem::size_of::() as isize) + as usize; + if offset + > (!0i32 as usize).wrapping_div(255usize).wrapping_sub(2usize) + { + current_block = 60; + break; + } + offset = (offset << 8i32).wrapping_sub(offset); + t = t.wrapping_add(offset.wrapping_add(15usize).wrapping_add(*{ + let old = ip; + ip = ip.offset(1isize); + old + } + as usize)); + } + t = t.wrapping_add(3usize); + if (((op_end as isize).wrapping_sub(op as isize) + / ::std::mem::size_of::() as isize) + as usize) + < t + { + current_block = 63; + continue; + } + if (((ip_end as isize).wrapping_sub(ip as isize) + / ::std::mem::size_of::() as isize) + as usize) + < t.wrapping_add(3usize) + { + current_block = 64; + break; + } + loop { + *{ + let old = op; + op = op.offset(1isize); + old + } = *{ + let old = ip; + ip = ip.offset(1isize); + old + }; + t -= 1; + if t == 0 { + break; + } + } + state = 4usize; + current_block = 11; + continue; + } else if state != 4usize { + next = t & 3usize; + m_pos = op.offset(-1isize).cast_const(); + m_pos = m_pos.offset(-((t >> 2i32) as isize)); + m_pos = m_pos.offset( + -((i32::from(*{ + let old = ip; + ip = ip.offset(1isize); + old + }) << 2i32) as isize), + ); + if m_pos < out.cast_const() { + current_block = 48; + break; + } + if (((op_end as isize).wrapping_sub(op as isize) + / ::std::mem::size_of::() as isize) + as usize) + < 2usize + { + current_block = 63; + continue; + } + *op.offset(0isize) = *m_pos.offset(0isize); + *op.offset(1isize) = *m_pos.offset(1isize); + op = op.offset(2isize); + current_block = 44; + } else { + next = t & 3usize; + m_pos = op.offset(-((1i32 + 0x800i32) as isize)).cast_const(); + m_pos = m_pos.offset(-((t >> 2i32) as isize)); + m_pos = m_pos.offset( + -((i32::from(*{ + let old = ip; + ip = ip.offset(1isize); + old + }) << 2i32) as isize), + ); + t = 3usize; + current_block = 36; + } + } else { + if t >= 64usize { + next = t & 3usize; + m_pos = op.offset(-1isize).cast_const(); + m_pos = m_pos.offset(-((t >> 2i32 & 7usize) as isize)); + m_pos = m_pos.offset( + -((i32::from(*{ + let old = ip; + ip = ip.offset(1isize); + old + }) << 3i32) as isize), + ); + t = (t >> 5i32) + .wrapping_sub(1usize) + .wrapping_add((3i32 - 1i32) as usize); + } else if t >= 32usize { + t = (t & 31usize).wrapping_add((3i32 - 1i32) as usize); + if t == 2usize { + let mut offset: usize; + let ip_last: *const u8 = ip; + loop { + if i32::from(*ip) != 0i32 { + break; + } + ip = ip.offset(1isize); + if (((ip_end as isize).wrapping_sub(ip as isize) + / ::std::mem::size_of::() as isize) + as usize) + < 1usize + { + current_block = 64; + break 'loop11; + } + } + offset = ((ip as isize).wrapping_sub(ip_last as isize) + / ::std::mem::size_of::() as isize) + as usize; + if offset + > (!0i32 as usize).wrapping_div(255usize).wrapping_sub(2usize) + { + current_block = 30; + break; + } + offset = (offset << 8i32).wrapping_sub(offset); + t = t.wrapping_add(offset.wrapping_add(31usize).wrapping_add(*{ + let old = ip; + ip = ip.offset(1isize); + old + } + as usize)); + if (((ip_end as isize).wrapping_sub(ip as isize) + / ::std::mem::size_of::() as isize) + as usize) + < 2usize + { + current_block = 64; + break; + } + } + m_pos = op.offset(-1isize).cast_const(); + next = get_unaligned_le16(ip.cast::<::std::os::raw::c_void>()) as usize; + ip = ip.offset(2isize); + m_pos = m_pos.offset(-((next >> 2i32) as isize)); + next &= 3usize; + } else { + m_pos = op.cast_const(); + m_pos = m_pos.offset(-(((t & 8usize) << 11i32) as isize)); + t = (t & 7usize).wrapping_add((3i32 - 1i32) as usize); + if t == 2usize { + let mut offset: usize; + let ip_last: *const u8 = ip; + loop { + if i32::from(*ip) != 0i32 { + break; + } + ip = ip.offset(1isize); + if (((ip_end as isize).wrapping_sub(ip as isize) + / ::std::mem::size_of::() as isize) + as usize) + < 1usize + { + current_block = 64; + break 'loop11; + } + } + offset = ((ip as isize).wrapping_sub(ip_last as isize) + / ::std::mem::size_of::() as isize) + as usize; + if offset + > (!0i32 as usize).wrapping_div(255usize).wrapping_sub(2usize) + { + current_block = 22; + break; + } + offset = (offset << 8i32).wrapping_sub(offset); + t = t.wrapping_add(offset.wrapping_add(7usize).wrapping_add(*{ + let old = ip; + ip = ip.offset(1isize); + old + } + as usize)); + if (((ip_end as isize).wrapping_sub(ip as isize) + / ::std::mem::size_of::() as isize) + as usize) + < 2usize + { + current_block = 64; + break; + } + } + next = get_unaligned_le16(ip.cast::<::std::os::raw::c_void>()) as usize; + ip = ip.offset(2isize); + m_pos = m_pos.offset(-((next >> 2i32) as isize)); + next &= 3usize; + if m_pos == op.cast_const() { + current_block = 21; + break; + } + m_pos = m_pos.offset(-0x4000isize); + } + current_block = 36; + } + if current_block == 36 { + if m_pos < out.cast_const() { + current_block = 48; + break; + } + let oe: *mut u8 = op.add(t); + if (((op_end as isize).wrapping_sub(op as isize) + / ::std::mem::size_of::() as isize) + as usize) + < t + { + current_block = 63; + continue; + } + *op.offset(0isize) = *m_pos.offset(0isize); + *op.offset(1isize) = *m_pos.offset(1isize); + op = op.offset(2isize); + m_pos = m_pos.offset(2isize); + loop { + *{ + let old = op; + op = op.offset(1isize); + old + } = *{ + let old = m_pos; + m_pos = m_pos.offset(1isize); + old + }; + if op >= oe { + break; + } + } + } + state = next; + t = next; + if (((ip_end as isize).wrapping_sub(ip as isize) + / ::std::mem::size_of::() as isize) as usize) + < t.wrapping_add(3usize) + { + current_block = 64; + break; + } + if (((op_end as isize).wrapping_sub(op as isize) + / ::std::mem::size_of::() as isize) as usize) + < t + { + current_block = 63; + continue; + } + loop { + if t == 0usize { + current_block = 11; + break; + } + *{ + let old = op; + op = op.offset(1isize); + old + } = *{ + let old = ip; + ip = ip.offset(1isize); + old + }; + t = t.wrapping_sub(1usize); + } + } else { + *out_len = ((op as isize).wrapping_sub(out as isize) + / ::std::mem::size_of::() as isize) + as usize; + return -5i32; + } + } + if current_block == 64 { + } else if current_block == 21 { + *out_len = ((op as isize).wrapping_sub(out as isize) + / ::std::mem::size_of::() as isize) as usize; + return if t != 3usize { + -1i32 + } else if ip == ip_end { + 0i32 + } else if ip < ip_end { + -8i32 + } else { + -4i32 + }; + } else if current_block == 22 || current_block == 30 { + return -1i32; + } else if current_block == 48 { + *out_len = ((op as isize).wrapping_sub(out as isize) + / ::std::mem::size_of::() as isize) as usize; + return -6i32; + } else { + return -1i32; + } + } + } + *out_len = + ((op as isize).wrapping_sub(out as isize) / ::std::mem::size_of::() as isize) as usize; + -4i32 +} diff --git a/libs/lzo/src/lib.rs b/libs/lzo/src/lib.rs new file mode 100644 index 00000000..26795388 --- /dev/null +++ b/libs/lzo/src/lib.rs @@ -0,0 +1,122 @@ +mod compress; +mod decompress; +use std::mem; +use std::slice; + +#[must_use] +/// for a given `size` computes the worst case size that the compresed result can be +pub const fn worst_compress(size: usize) -> usize { + (size) + ((size) / 16) + 64 + 3 +} + +#[cfg(feature = "compress")] +const LZO1X_1_MEM_COMPRESS: usize = 8192 * 16; +#[cfg(feature = "compress")] +const LZO1X_MEM_COMPRESS: usize = LZO1X_1_MEM_COMPRESS; + +#[repr(i32)] +#[derive(Eq, PartialEq)] +pub enum LZOError { + Ok = 0, + Error = -1, + OutOfMemory = -2, + NotCompressible = -3, + InputOverrun = -4, + OutputOverrun = -5, + LookbehindOverrun = -6, + EofNotFound = -7, + InputNotConsumed = -8, + NotYetImplemented = -9, + InvalidArgument = -10, +} + +#[cfg(feature = "compress")] +/// compress `input` into `output` +/// returns an error if the Vec is not large enough +pub fn compress(input: &[u8], output: &mut Vec) -> LZOError { + unsafe { + let wrkmem = libc::malloc(LZO1X_MEM_COMPRESS); + let mut out_len = output.capacity(); + let err = compress::lzo1x_1_compress( + input.as_ptr(), + input.len(), + output.as_mut_ptr(), + &mut out_len, + wrkmem, + ); + + output.set_len(out_len); + libc::free(wrkmem); + mem::transmute::(err) + } +} + +#[cfg(feature = "compress")] +/// returns a slice containing the compressed data +pub fn compress_to_slice<'a>(in_: &[u8], out: &'a mut [u8]) -> (&'a mut [u8], LZOError) { + unsafe { + let wrkmem = libc::malloc(LZO1X_MEM_COMPRESS); + let mut out_len = out.len(); + let err = compress::lzo1x_1_compress( + in_.as_ptr(), + in_.len(), + out.as_mut_ptr(), + &mut out_len, + wrkmem, + ); + libc::free(wrkmem); + ( + slice::from_raw_parts_mut(out.as_mut_ptr(), out_len), + mem::transmute::(err), + ) + } +} + +#[cfg(feature = "decompress")] +/// returns a slice containing the decompressed data +pub fn decompress_to_slice<'a>(in_: &[u8], out: &'a mut [u8]) -> (&'a mut [u8], LZOError) { + unsafe { + let mut out_len = out.len(); + let err = decompress::lzo1x_decompress_safe( + in_.as_ptr(), + in_.len(), + out.as_mut_ptr(), + &mut out_len, + ); + ( + slice::from_raw_parts_mut(out.as_mut_ptr(), out_len), + mem::transmute::(err), + ) + } +} + +#[cfg(test)] +#[test] +fn compress_and_back() { + unsafe { + let data = [ + 0u8, 2, 3, 4, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 4, + 2, 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 4, 2, 2, 4, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 4, 2, 2, 4, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 4, + ]; + let dst_len: usize = worst_compress(mem::size_of_val(&data)); + let mut v = Vec::with_capacity(dst_len); + let dst = libc::malloc(dst_len); + let dst = slice::from_raw_parts_mut(dst.cast::(), dst_len); + let (dst, err) = compress_to_slice(&data, dst); + assert!(err == LZOError::Ok); + let err = compress(&data, &mut v); + assert!(err == LZOError::Ok); + println!("{}", dst.len()); + + let dec_dst = libc::malloc(mem::size_of_val(&data)); + let result_len = mem::size_of_val(&data); + let dec_dst = slice::from_raw_parts_mut(dec_dst.cast::(), result_len); + let (result, err) = decompress_to_slice(dst, dec_dst); + assert!(err == LZOError::Ok); + println!("{}", result.len()); + assert!(result.len() == mem::size_of_val(&data)); + assert!(&data[..] == result); + } +} diff --git a/libs/paa/Cargo.toml b/libs/paa/Cargo.toml new file mode 100644 index 00000000..19b3a34a --- /dev/null +++ b/libs/paa/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "hemtt-paa" +version = "1.0.1" +edition = "2021" +description = "An PAA library for hemtt" +license = "GPL-2.0" + +[lints] +workspace = true + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +hemtt-common = { path = "../common", version = "1.0.0" } +hemtt-lzo = { path = "../lzo", version = "1.0.0", features = ["decompress"], default-features = false } + +js-sys = { version = "0.3.65", optional = true } +wasm-bindgen = { version = "0.2.88", optional = true } + +byteorder = { workspace = true } +texpresso = "2.0.1" +image = "0.24.7" + +[features] +wasm = ["wasm-bindgen", "js-sys"] diff --git a/libs/paa/src/lib.rs b/libs/paa/src/lib.rs new file mode 100644 index 00000000..1a133b02 --- /dev/null +++ b/libs/paa/src/lib.rs @@ -0,0 +1,10 @@ +mod mipmap; +mod paa; +mod pax; + +pub use self::mipmap::MipMap; +pub use self::paa::Paa; +pub use self::pax::PaXType; + +#[cfg(feature = "wasm")] +mod wasm; diff --git a/libs/paa/src/mipmap.rs b/libs/paa/src/mipmap.rs new file mode 100644 index 00000000..94aecf77 --- /dev/null +++ b/libs/paa/src/mipmap.rs @@ -0,0 +1,74 @@ +use std::io::Read; + +use byteorder::{LittleEndian, ReadBytesExt}; +use texpresso::Format; + +#[derive(Debug)] +pub struct MipMap { + pub width: u16, + pub height: u16, + pub data: Vec, + format: Format, +} + +impl MipMap { + /// Read the `MipMap` from the given input + /// + /// # Errors + /// [`std::io::Error`] if the input is not readable, or the `MipMap` is invalid + pub fn from_stream(format: Format, stream: &mut I) -> Result { + let width = stream.read_u16::()?; + let height = stream.read_u16::()?; + let length = stream.read_u24::()?; + let mut buffer: Box<[u8]> = vec![0; length as usize].into_boxed_slice(); + stream.read_exact(&mut buffer)?; + Ok(Self { + format, + width, + height, + data: buffer.to_vec(), + }) + } + + #[must_use] + /// Get the image from the `MipMap` + /// + /// # Panics + /// Panics if the `MipMap` is invalid + pub fn get_image(&self) -> image::DynamicImage { + let data = &*self.data; + let mut width_2 = self.width; + let compress = if width_2 % 32768 == width_2 { + false + } else { + width_2 -= 32768; + true + }; + let mut img_size: u32 = u32::from(width_2) * u32::from(self.height); + if self.format == Format::Bc1 { + img_size /= 2; + } + let mut buffer: Box<[u8]> = vec![0; img_size as usize].into_boxed_slice(); + let mut out_buffer = vec![0u8; 4 * (width_2 as usize) * (self.height as usize)]; + if compress { + hemtt_lzo::decompress_to_slice(data, &mut buffer); + self.format.decompress( + &buffer, + usize::from(width_2), + usize::from(self.height), + &mut out_buffer, + ); + } else { + self.format.decompress( + data, + usize::from(width_2), + usize::from(self.height), + &mut out_buffer, + ); + }; + image::DynamicImage::ImageRgba8( + image::RgbaImage::from_raw(u32::from(width_2), u32::from(self.height), out_buffer) + .unwrap(), + ) + } +} diff --git a/libs/paa/src/paa.rs b/libs/paa/src/paa.rs new file mode 100644 index 00000000..f442cbcb --- /dev/null +++ b/libs/paa/src/paa.rs @@ -0,0 +1,165 @@ +#![allow(clippy::cast_possible_truncation)] + +use std::{ + io::{Error, Read, Seek, SeekFrom, Write}, + mem::size_of, +}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use image::{EncodableLayout, RgbaImage}; +use texpresso::{Format, Params}; + +use crate::{MipMap, PaXType}; + +use std::collections::HashMap; + +#[derive(Debug)] +pub struct Paa { + pub format: PaXType, + pub taggs: HashMap>, + pub maps: Vec, +} + +impl Paa { + #[must_use] + pub fn new(format: PaXType) -> Self { + Self { + format, + taggs: HashMap::new(), + maps: Vec::new(), + } + } + + /// Read the Paa from the given input + /// + /// # Errors + /// [`std::io::Error`] if the input is not readable, or the Paa is invalid + pub fn read(mut input: I) -> Result { + if let Some(pax) = PaXType::from_stream(&mut input) { + let mut paa = Self::new(pax); + // Read Taggs + while { + let mut tagg_sig = [0; 4]; + input.read_exact(&mut tagg_sig)?; + std::str::from_utf8(&tagg_sig).map_or(false, |ts| ts == "GGAT") + } { + let name = { + let mut bytes = [0; 4]; + input.read_exact(&mut bytes)?; + std::str::from_utf8(&bytes) + .map_err(|_| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + "invalid tagg name", + ) + })? + .to_string() + }; + paa.taggs.insert(name, { + let mut buffer: Box<[u8]> = + vec![0; (input.read_u32::()?) as usize].into_boxed_slice(); + input.read_exact(&mut buffer)?; + buffer.to_vec() + }); + } + // Read MipMaps + if let Some(offs) = paa.taggs.get("SFFO") { + for i in 0..(offs.len() / 4) { + let mut seek: [u8; 4] = [0; 4]; + let p = i * 4; + seek.clone_from_slice(&offs[p..p + 4]); + let seek = u32::from_le_bytes(seek); + if seek != 0 { + input.seek(SeekFrom::Start(u64::from(seek)))?; + paa.maps + .push(MipMap::from_stream(paa.format.clone().into(), &mut input)?); + } + } + } + Ok(paa) + } else { + Err(Error::new( + std::io::ErrorKind::InvalidData, + "invalid paa format", + )) + } + } + + /// Write the Paa to the given output + /// + /// # Errors + /// [`std::io::Error`] if the output is not writable + pub fn write(image: &RgbaImage, output: &mut impl Write) -> Result<(), Error> { + let algo: Format = PaXType::DXT5.clone().into(); + output.write_all(&PaXType::DXT5.as_bytes())?; // 2 + + // Average Color + output.write_all(b"GGATAVGC")?; // 8 + output.write_u32::(size_of::() as u32)?; // 4 + let avg_color = image + .pixels() + .map(|p| { + [ + u32::from(p.0[0]), + u32::from(p.0[1]), + u32::from(p.0[2]), + u32::from(p.0[3]), + ] + }) + .fold([0, 0, 0, 0], |mut acc, p| { + acc[0] += p[0]; + acc[1] += p[1]; + acc[2] += p[2]; + acc[3] += p[3]; + acc + }); + output.write_u32::(u32::from_le_bytes([ + (avg_color[0] / (image.width() * image.height())) as u8, + (avg_color[1] / (image.width() * image.height())) as u8, + (avg_color[2] / (image.width() * image.height())) as u8, + (avg_color[3] / (image.width() * image.height())) as u8, + ]))?; // 4 + + // Max Color + output.write_all(b"GGATCXAM")?; // 8 + output.write_u32::(size_of::() as u32)?; // 4 + let max_color = image + .pixels() + .map(|p| p.0) + .fold([0, 0, 0, 0], |mut acc, p| { + acc[0] = acc[0].max(p[0]); + acc[1] = acc[1].max(p[1]); + acc[2] = acc[2].max(p[2]); + acc[3] = acc[3].max(p[3]); + acc + }); + output.write_u32::(u32::from_le_bytes(max_color))?; // 4 + + // Offset Table + output.write_all(b"GGATSFFO")?; // 8 + output.write_u32::(16 * size_of::() as u32)?; // 4 + let pos = 2 + 8 + 4 + 4 + 8 + 4 + 4 + 8 + 4 + (16 * 4); + output.write_u32::(pos)?; // 4 + for _ in 0..15 { + // 15 * 4 + output.write_u32::(0)?; + } + + // Write main image + output.write_u16::(image.width() as u16)?; + output.write_u16::(image.height() as u16)?; + let size = algo.compressed_size(image.width() as usize, image.height() as usize); + output.write_u24::(size as u32)?; + let mut buffer = vec![0; size]; + algo.compress( + image.as_bytes(), + image.width() as usize, + image.height() as usize, + Params::default(), + &mut buffer, + ); + output.write_all(&buffer)?; + + Ok(()) + } +} diff --git a/libs/paa/src/pax.rs b/libs/paa/src/pax.rs new file mode 100644 index 00000000..f0659301 --- /dev/null +++ b/libs/paa/src/pax.rs @@ -0,0 +1,80 @@ +use std::io::Read; + +use texpresso::Format; + +#[derive(Debug, Clone)] +pub enum PaXType { + DXT1, + DXT2, + DXT3, + DXT4, + DXT5, + RGBA4, + RGBA5, + RGBA8, + GRAYA, +} + +impl PaXType { + pub fn from_stream(stream: &mut I) -> Option { + let mut bytes = [0; 2]; + if stream.read_exact(&mut bytes).is_ok() { + Self::from_bytes(bytes) + } else { + None + } + } + + #[must_use] + pub const fn from_bytes(bytes: [u8; 2]) -> Option { + match bytes { + [1, 255] => Some(Self::DXT1), // 0x01FF + [2, 255] => Some(Self::DXT2), // 0x02FF + [3, 255] => Some(Self::DXT3), // 0x03FF + [4, 255] => Some(Self::DXT4), // 0x04FF + [5, 255] => Some(Self::DXT5), // 0x05FF + [68, 68] => Some(Self::RGBA4), // 0x4444 + [21, 85] => Some(Self::RGBA5), // 0x1555 + [136, 136] => Some(Self::RGBA8), // 0x8888 + [128, 128] => Some(Self::GRAYA), // 0x8080 + _ => None, + } + } + + #[must_use] + pub const fn as_bytes(&self) -> [u8; 2] { + match self { + Self::DXT1 => [1, 255], + Self::DXT2 => [2, 255], + Self::DXT3 => [3, 255], + Self::DXT4 => [4, 255], + Self::DXT5 => [5, 255], + Self::RGBA4 => [68, 68], + Self::RGBA5 => [21, 85], + Self::RGBA8 => [136, 136], + Self::GRAYA => [128, 128], + } + } +} + +impl From for Format { + fn from(pax: PaXType) -> Self { + match pax { + PaXType::DXT1 => Self::Bc1, + PaXType::DXT3 => Self::Bc2, + PaXType::DXT5 => Self::Bc3, + _ => unimplemented!(), + } + } +} + +impl From for PaXType { + fn from(pax: Format) -> Self { + match pax { + Format::Bc1 => Self::DXT1, + Format::Bc2 => Self::DXT3, + Format::Bc3 => Self::DXT5, + _ => unimplemented!(), + } + } +} diff --git a/libs/paa/src/wasm.rs b/libs/paa/src/wasm.rs new file mode 100644 index 00000000..c841cb6c --- /dev/null +++ b/libs/paa/src/wasm.rs @@ -0,0 +1,54 @@ +use std::io::Cursor; + +use js_sys::Uint8Array; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + // Use `js_namespace` here to bind `console.log(..)` instead of just + // `log(..)` + #[wasm_bindgen(js_namespace = console)] + fn log(s: &str); + + // The `console.log` is quite polymorphic, so we can bind it with multiple + // signatures. Note that we need to use `js_name` to ensure we always call + // `log` in JS. + #[wasm_bindgen(js_namespace = console, js_name = log)] + fn log_u32(a: u32); + + // Multiple arguments too! + #[wasm_bindgen(js_namespace = console, js_name = log)] + fn log_many(a: &str, b: &str); +} + +#[wasm_bindgen] +pub struct ImageResult { + data: std::rc::Rc>>, +} + +#[wasm_bindgen] +impl ImageResult { + #[wasm_bindgen(constructor)] + pub fn new(s: &Uint8Array) -> Self { + let bytes = s.to_vec(); + let paa = crate::Paa::read(Cursor::new(bytes)).unwrap(); + let mut buffer = Cursor::new(Vec::new()); + paa.maps[0] + .get_image() + .write_to(&mut buffer, image::ImageFormat::Png) + .unwrap(); + + Self { + data: std::rc::Rc::new(std::cell::RefCell::from(buffer.into_inner())), + } + } + + pub fn data_ptr(&self) -> *const u8 { + self.data.clone().borrow().as_ptr() + } + + #[allow(clippy::cast_possible_truncation)] + pub fn data_len(&self) -> u32 { + self.data.clone().borrow().len() as u32 + } +} diff --git a/libs/paa/tests/dxt1.paa b/libs/paa/tests/dxt1.paa new file mode 100644 index 0000000000000000000000000000000000000000..acbb0241fafd1c05a84e93c2c76d1eb352257d91 GIT binary patch literal 17835 zcmeHv4S17Pmhic6lBQ`%la{uGnwI!Yc_&3FoYK2rt z-h`sa2qQSQz_LkN5wsMm%#K0DylKz{b#Mh&OI%Hx0FGkES!I-2N7H2Q3s(2x`&hyC zo5y`V(x*MSf9KwF?!D)pbMCoCiJNY!uDkIjeYFq(2Htt&V%k_epHC;qhc90;ckV(R zNjH<bPvh|bn<7Spz(vZ0IZfmuPkFrEz~$#xlw?+ znVm;!6BN?}Wj+=x*7L%H%*ImFhEA72T8hV2P^co*88QM@$cDaXpOa!qLyxX#07K>R zT~a6O{Y^K+LC*jbMz(#;RCt7mX}O^EK&Z$J&H<{wBmk8mKX9oNEmAD6M#EC{8)ruE z2S@3fGz?>uidjeYKD^#9_GC5dClIyH#Li$Ynd!Vog;}Um4I}m zjWg;f)XM2OyC?>fjZ5F6v)*TR85oz%E)p>Y@|TzRAryy{Ux-<)_CA- znr!J&3Hr*Cyj&qg`iWs0uckn5x3w|Mu7+%?pWz;xI0(=?u#vK$w&ZGbJ`H7h)Ug}< ztMGKY7aVw7AMnVQ7rW1OT=-t%imN*6i*0 z#awBNdodXQLG7neA3VRu4g*_;rLfZh&kaD2W{rv&8i0gf!NOmk@h8Q^F&M8@dtmM2lyLtMy8wFS*v1vc)VG2oUzw)@(k)JkD0a(LrE`m_1!2cuB(!Hv#Pb)QXw}Pe zzXCMFGzJBmjV60aeXQU9i=_Ti31Lvi7F2E$kuT5h(LrDrB|&joeZvj*-wN8~lt3cI z@Jvj$%-M8Df$6?#$hO>?l@)ekRqm*1mifkJ=}QYZS9j+}jE`c!@TG7IyYV9?8J_N! zqa()Nq0W1%lh?ZML)FR5S*@2goYhpR8AEhngE`(7JLzw;+f|^aJ+s~J^>#$Lg4UvC z{$5o~jru!EwT`t2r&MIo?@sf;d6O9FO~zVPcn;EPOS3RM8<+L;IFA|h`ax$$N1&>^ z8|T|oMLUled|o2Nal|ddV-v#c-X^U@Yj6yGTQQ z9R`Iz4bO8=QPcI{#XMN7U*1>SW|tX&T*5LR!+v_!U_!CN)S);(aE*7M&r_h4bH5rQq^z? zA>wAcMbOi;v@`O7YIu!h0GMJTazoSSUJuT+)L+(EfX&soe?{ljdm}2-on%X~j>^!a zip|6DmB}xtGc_fx7;FyquH8< zo|>Ww?3zPs+byf|($&SJ81@bM>KHJ7D(fD&->lMy0I&_Iq2?(7g}( z|Ku66XomMK)ePRwGVYK2?*DDO1m^^#A7byMtR_v&GnZ<2Tcw)eJI`ypH!+E6N>Ifu zOuv?Upl?RSW<-PqOjm-SmD??6x>(dE$B)Mx7%-{lUtSMQ?9E1#YH(ZZEwif@A9NJ; z#MXMjWl@{=<_@<-{P&5LU1zI3 zd7{A3`4m5B***PKzJH&J+qk!L&D3^;;T^XUzd-t`7idLV?L8|r6`M5i;Qsyi>6k2T zFIrjPImT#??sF|oqE@Y&;6H}X*mq%gH!;o{bXk^g-DUEe} z>R;nB!VTvlI`8hR4i)>zp@2%alP~xKsrJWB5*-jRh(UEjt&k1Ze>cg0Re=zazvwcMqs1i2sF8zC_3-Z)W*^W^b@IAmrE7%H(X^H9VOykH5}36-LVK6x4j%S%B`-f-6@7{FWX;4n|$=Z8~gBShtQzT z{cCgj>~Si~4M6RAQslDQxM!9kg2(+*=IL=+Srfz=Y{;r3d;*viTc~oEWFSkSd6?4L z-@{9kAE|~H>U8j>Q|0+n>BU-a#v5K$Bqf5uxbx+J+bMq$2qOSS#bqlO7skh}4P-(s zpYi6ftci!GVzNoGaT&{%M6?1Nk5x??9>?-u--#_O8kR-ePLj~X$lvWT8TmRfwiUx8 zvE#*Yr-+;pppKCxv6B#-sFF%~txLrdy@@QGasOnsHs{BV3oAoU$|VSqGBUjhhtr6l zE;ovjI@r)7HAp}ay#e5rm@M;jpG&8D{+ysrZ(zP`XPq5c8Q3L%IeHw!E5j)#GH{Ic zhjUU!K|_Z1Q!^A{+JdAj{V8>j{>BRiFHjvh)?9pxDUNT9RJmNiiQ%fz(TE&iC#v0P zcce#0u_uiNIjVm8;Vf6W2q4?X7_F|HU{SqjKTcJ~%n_|2g_BV7)S(xq`RY?Z5U43Yjh62 z1&i${2O85=Z|aNX)GOUZg1R=!cSd0hW`f#G)!9>!M5-;tPsU{F{zN_}G>?v!;3vWh zF&v7?s81V%x?uLHqjip!tn@u!7gkI}%~qW&R-dtAom3=}E399LY}_GBB z!^LWvs>fZrCD+00^pO&eYM`W_9xQoug(^lk2XHtpD>SIRTa9&c!TQcpT|yDE^6mcU zFIgJ57Ua(QNPfRRj$W0sA(ayIEeN-@1HT{2IdeiJLHU~B=d{VPwD@;!eh7aVmT^1d za_aPvLxI~UWDX_Iz%To&ND0|G*{_`6c_dG_wZL+mEdH*F1doj(GR(bj?1#@zVx3y) zb@(&zkyd9I-S$ac5ZKGW2dR=92pmV<{O-n3K#>f06r2HKsa!!Blva0S?@f<1oglw?e&SdV+$nITchxyC=Z2HiG<5tD#=pZS5BSDz7N^Mza@HGru z>zIt2GgOu@wtM_+&Z$SW__Ti<23<^+yL^UD{u-{p_Qa#akjglNtR{mZdHs-=?kp*i zYVnJ)w=gsh%M9xH>n3B);TNT2_iW0D676=9Ql8`b#Ib*x$svHC(XgOJ6ZR@kzu&L5 zueT(z1rR%diA9{WTft1vHuBm}92 z$d=(MY<3~t`q)C&B`8wO#0S>lZz}Kv)3c+WST14zW;GW0WuvJjzEqYfO_irdr)E$k zy5Jb^jbI)fe2ZWy#;}c-riDmv)Ip!Y!1SvcCi@shGdLqvizBRXNxRBxVYC4{YGD5s zwSZ97e~z_`gcS6oWYnDgS(x=$3S#R~O2>%}oy%P|?v96{4{uN8eN9exMWJS(*cG_WrT0Dn z?^8+bZl!K(M%#U@j2yYzICY2joBjePTGEabQx7*}Uw7s$lkq7hIkNi2iRlMHaVQ$# z#qcVk@#)<47Lz498>u;~#6D1OHN>rdZt`z%7z)i$wt9WXz=J7%>b4~1sRLaPr!Y*9 z`_A;6o#9_2V44;W|GMS2d(2cjPRfPHueZSFrEUq$Rkn?;e!}3D4>2CN3zAf;7Z^u< zQIcl?$P9MfpKBNI#od7?`l-%r9|fLox^c1HnjT{nRPKI6N^I3I`zCxhZ3d$-DC0=KDUIQy@x8xegz`_1CDI zi2uPyb{;9p1+GX5GffbNvE}XpcGRC6g;afEia{xJ;|XpHy^_oLA$rcK7|x(Yk-840UyDkLE-z zr0%19-4wE^;g!_BzC;3GYCA>rnJ?ROqh31CMHa~^rHdR-r$DYU-&c3>04Hr=FfMCx z2b_;>QRg1$&w(ns{3O{g7gZ<6ZqqFIxZ0i&cQCt-vLU^mXl)U4f{^m{fIbobv@WN6 zm5_XhbVK|s+VX>D2d_>X4vUjQ$6H8;I&-KF09h?eOFKX-!$|t~0Q56Vkfo?1A%)n` zZp9p-WO^R?Ru6?qDAa2fvQqS%BgIMlDJ#v;pN4LqLJZh6$?XNi?~{gvw3C=8a|ThH z!Vi7{X*g=umpOxz+AH5vZVEa^&-^`1AUQfbJQOGH>v+8ClddiD6QU*cY!+SGJn192;k z88haBgNG{1CHnUiiHVq_kGlaLV3-o`j4r*m%txic;&u^b)XkWwM0kvn_XI6zZ{0CZ zTfm;IVY+mWu%LHE=2~1YFk4e~$jJcZGtYX(v)y7rF01g6eKG3LN+phE`wHyHo@_Mf ztLWskm&R+Of^8--9$S^%?$)`h z1$RuzCyQq$2W6%z6n?io(^1up8|_KqlT;HM+P-($7{kT_b)|+%LLXv{S3@V?_HTjc z5aa)gz`#_7&M5BKU3t7-=o!sBRpr(YqeMy! zl@l>BQHF>`z*dR0#JWn5w7Y`Nov&93=ORX5YsJgm(;CvD%M<`gibs=Kt5Q|eA#Evf zt&rNz+x2y=>P~}JE5YVmbs@J%kHDmA&=R#5%au1G8z-5ytSY!RAWhHW<<+*>7@>_j zx49W0Kh}uTtk9OlJj+Ey;zbT{de@Ik!az_@+PiQ7#)a$Wv z_C<(zx|*HwtWqt$Is7n&yJBL;Z)$aYjw-3Hov$OdzdhG!wb05(MF!#uGl%LA*1t_! z2MJY5T5F=|fmaPgi7bCnfESvc#$f3ir)!SUo~Iefur_U2@hrZD*ENkXG3~I`%&tn+ zwG!RTq-Q$Jq1$OGe2opJ7s!Gd5HC>MJ?l8t)e!vrXm3II2l`tYW>@Q6`0JU@Wj zoxdfzp6nZdyXag_$<0Lg&hWqBhm|6g`y-@$*VL@xCJAt>^MsB2Q00wS%j`30ML_a|KM1H{F-2@w~aeJVvQb{_@>AqMswhvq0hHN(FwRL3?0B^X3X0m!c~h zU-8$69_pdH;uMF0VFDW&M%S#K`+g5I-k&H_LN*5W1;!Vc@1vZ_CpHvmO`T+?opVa+ z>P)nA)tmydz|>?rJRYt>M&jyFjhjHU0u22Lacy(+c*k*KSCgH5`)k^2vg7Ta@%!vu zqDk8$D&=$}m6Dy_5EGO0-bH>$*@YXXo}Qj-kleIg_zzOx3DMn7GK`~HGh2##4cK5w>>$Q?5O`*|lMfjzC9wm|-%ZM|lz!r4my=)= zpC$670t~}FHXn&q?WZox8$+TL3?$8ver(*+<>xOz;<&s3Gqwz0fc<+embbhA>v%@S z_4k6gWWN+AB_&-c-6Qob<>TLLygzk6=14)!`0-^IBZjYH{(tImT#C=&t4;<;&b~DN zYST8Zu!bzRvdgvO*S~^%M&|WO;pOoCRQbI8%AWB3KK-$H`F%L1?DBYakaES&ZQHJt z?vZ*wc050~cD&r$Mm{5ST`Byz(eY!)lT8lUZF0H%O6eY{_hb9}pSH7c{AKU=`}te_ zVaUeux0WEfTKr1`{Qj>PKSI})!mI7)e_G$b5qTKYk?1ixdB+2@;J^kOM`D5*1JPfC z%?HSNTb*3G?eplj{dE6Of4n|G#68iwHz>yEi=|ge_ei}T!vAwG=k-fhP-kUB7$oHS zZG^5Xg+F)terS9w(TA6Mv-5fTi`Rp%l=x0Nj z9RwCxLV6E@IyUsH6$F<4Af2II#E=1^&XQ>93lEy7@b1TknIE66-m|B>&&Iu8ZY3Eu z?!<=wo4d`%oqy(H`KOH*Qg8TV&EnHLcz0p($|wm%{?-J9Hm-}Dzr2~@`!C1Oh#0z3$VRSj?;O_|xg7mhif5#rjoZ2oWYX{4pCkDr zkPp2EeB!%u8&|ZfD{9sKRQ-?ibEVMWdt?@2zw4*(pU?@$ZUZM>NtT(*K;GW~$b^@U zYxtv@nw~>BIX`{>SElBgL;un^b9~Zw*B6(L3&mvwE5ijb}simm#7? zUE1$R`f~kUns4CtFCDclNcIQM@7}Zgz9Mn@4K{88AJR^)0r-4NDnB1V;t;QlyxCZp zb9ugBZU2{!=ZDfW0_p8JyLRmw>F06~lTbkT@mAF3(Eh#izrTef(U%v*XNw!QJO9Jr zBLbC7KN});BY9z94lQXX;qM7H)b55t~xSI{I z{$yC^_du{C$cDc1@w+sc{9xY38_OStwQT4;elm(|T$!&o2+jO#T1n)#5QL;6xdG(SJrGB>|4H=YpE)zxKTxF~l4VhRm$e(wA{!~#5sU(3r|D8m06 z;MtN)$o5AF*=-=?5K zgp6MgSazM#uq56?Q%7X!DVOE48HL+b_{vy18-Y?H?arRSJ0Q7?wiyA?%BcUytUS^Wm3wZPSbPWq+`a-}*^{pDVSYV3( zwbh)K?X$AWRR`D$e1+5DeyTJfAI7(iQq3IB_cpkjf8P+yQ9hHu*~>8>pLdB~Yuua0 z_!7}<9%-`aEwX)7X~~C7Ux?ai$7Q{7WtcL)uy}d6684t@wkd3XqkL^j5~U}&{S84! zz*B$7!!hSG9CxFCM91eSd-pVaOZyqa$-Mu1@6pArUupYofi}|oAM?s(Q`eP;(I3&D z=~cmqYc>QI2du;FMJ(NdbCf!dXBFq;3ZyD9OwX|!^^hcqW2O&{!Jc~QN|**GIR~DM z@UNSB>DYo6ueu!f!(t;Oo)pxi7i^522kEZ$nE4&XBl}eC{1}7@s%$S^Rm0AsK}X1Y zp?Ni`m%@1j`!`f9;r22-Fl4VA{57M#FT}rh1O|PTORZLp-N8YhUZ0rAG4g%A;v^IX zs3swTz6I`^l0kF+1L4^JL zHc>&oWt^|>jgQXmK>x-4#iE3U6Ic;}{5DiBiQ1o|4EmU(N9;v@&3*NmL-r2(e(+aR za-6CRTmW~7`g21tRnhCRn)&y>pgRa(ujCl@uvo3um-{F+(VLSve`7TSH!vCbTP(Nd z7t8)p<{vfj+Wtz0dERwBp?-|VoG-G*c%0JauPlo0Z>;^X82M+yhI0E6+ibSK^&Sv{Z(C0u7tzC(?*KNhQ~XRb0Q#H=Lo9)vk;G z)Zg!%>B#gLi6AKDLcN#ojAm(1mQ!Hdb)Z<=IzSu4waBukp5EOh4-m83-cO}A93 zTHJa})Azsd_mN@wVAlIZ0V6SDQN$Yn_S?h#hU0c^wtvvs#M1O4K_Ky8%b9pBVrdPM zVx|wy*pZM~OLOpI8!|i>NqdrbJHF#;VZ6NT8Ew2ym-H)K+sy9BH3l@hrj-S(EcMh! z(;P3uX^5?0#A{03p~GLkw5v9am*tSPqGhNNx?$3reQOz_o7xUFSe{S2MqpVzI8=ss zh-cV(DQ$7nCuZ@qg7%z{3e9_ZX)ksD-WUnH;^dBFu8Zk!X=RcqN~3RG&oDJ`V@>FE z8(WW+0g*I}WseQg44)s<&^B*rDaQ?vma}#@HxFT(;=kH0XLLc00@pBHA8ujMGKKTK zLJIawgs{lcE~MGD*Jv9(UB2Ij=>gM(ihq&`PYZm0T)(keKaFFaCJw7u77jvLIOzMh z=$(Qj7t{zx>@2K;+|jbv2(zNzI6F2YJcxyuG?%q;#Za1qu>G()f6YN~31%PIo7Vw@ zib^ACju=P>4US2iJT`~fFN!cNYex9Q`871O-o3`j_*0Fcc6CMkfUc(X@ALB-I*2{0U`)W0}g=0xoZ4GuCiX+ueIh<;vGjzs|VDj%^#; z1}IlD_S2-%U~($kmZ56N2&*991d_mh7JVD~WS1!Dt#D-$GA_&CpQ6{spjqfTQ@lMp s)a>Q0+U_Pw$$L$q#%epmP=ibH5K%$TwC%Egj=Jm&P6JoNxiAvi9|BT1u>b%7 literal 0 HcmV?d00001 diff --git a/libs/paa/tests/read.rs b/libs/paa/tests/read.rs new file mode 100644 index 00000000..f3be34ae --- /dev/null +++ b/libs/paa/tests/read.rs @@ -0,0 +1,13 @@ +#[test] +fn read_dxt1() { + let file = std::fs::File::open("tests/dxt1.paa").unwrap(); + let paa = hemtt_paa::Paa::read(file).unwrap(); + let _ = paa.maps[0].get_image(); +} + +#[test] +fn read_dxt5() { + let file = std::fs::File::open("tests/dxt5.paa").unwrap(); + let paa = hemtt_paa::Paa::read(file).unwrap(); + let _ = paa.maps[0].get_image(); +}