diff --git a/CMakeLists.txt b/CMakeLists.txt index 513c36a..46de9f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -261,6 +261,7 @@ add_library("Aaru.Compression.Native" SHARED library.c apple_rle.c apple_rle.h a stuffit/method13.c stuffit/method13_tables.h stuffit/method14.c + stuffit/shrinkwrap.c stuffit/arsenic.c stuffit/brimstone.c stuffit/cyanide.c diff --git a/library.c b/library.c index aaec963..2a21b05 100644 --- a/library.c +++ b/library.c @@ -629,10 +629,17 @@ AARU_EXPORT int AARU_CALL AARU_stuffit_arsenic_decode_buffer(uint8_t *dst_buffer const uint8_t *src_buffer, size_t src_size) { return stuffit_arsenic_decode_buffer(dst_buffer, dst_size, src_buffer, src_size); } +AARU_EXPORT int AARU_CALL AARU_stuffit_shrinkwrap_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, + const uint8_t *src_buffer, size_t src_size) +{ return stuffit_shrinkwrap_decode_buffer(dst_buffer, dst_size, src_buffer, src_size); } + AARU_EXPORT int AARU_CALL AARU_stuffitx_brimstone_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size, int max_order, int sub_alloc_size) -{ return stuffitx_brimstone_decode_buffer(dst_buffer, dst_size, src_buffer, src_size, max_order, sub_alloc_size, NULL); } +{ + return stuffitx_brimstone_decode_buffer(dst_buffer, dst_size, src_buffer, src_size, max_order, sub_alloc_size, + NULL); +} AARU_EXPORT int AARU_CALL AARU_stuffitx_cyanide_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size) diff --git a/library.h b/library.h index c9bb2e3..0cc4a1c 100644 --- a/library.h +++ b/library.h @@ -400,6 +400,10 @@ AARU_EXPORT int AARU_CALL AARU_stuffit_method14_decode_buffer(uint8_t *dst_buffe AARU_EXPORT int AARU_CALL AARU_stuffit_arsenic_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size); +// ShrinkWrap 3 SIT2 compression (NDIF chunk type 0xF0) +AARU_EXPORT int AARU_CALL AARU_stuffit_shrinkwrap_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, + const uint8_t *src_buffer, size_t src_size); + // StuffIt X method 0: Brimstone (PPMd Variant G) AARU_EXPORT int AARU_CALL AARU_stuffitx_brimstone_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size, diff --git a/stuffit/shrinkwrap.c b/stuffit/shrinkwrap.c new file mode 100644 index 0000000..59431b5 --- /dev/null +++ b/stuffit/shrinkwrap.c @@ -0,0 +1,322 @@ +/* + * This file is part of the Aaru Data Preservation Suite. + * Copyright (c) 2019-2026 Natalia Portillo. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +/* + * ShrinkWrap 3 "SIT2" decompressor + * + * Reverse-engineered from 68K 'Dcmp' resource ID 240 in ShrinkWrap 3.5.1. + * Used by NDIF disk images (chunk type 0xF0) created by ShrinkWrap 3. + * Component type 'Chdc', subtype 'SIT2', manufacturer 'Wrap'. + * + * Algorithm: Block Huffman LZSS, adapted from StuffIt method 14, with: + * - 276 literal/length symbols, 16 distance symbols + * - 20 length codes, 16 distance codes + * - 16-byte per-chunk header + */ + +#include +#include +#include "stuffit.h" +#include "stuffit_internal.h" + +#define SW_LITLEN 276 +#define SW_DIST 16 + +/* Quicksort for canonical code assignment (same as method 14) */ +static void sw_update(uint16_t first, uint16_t last, uint8_t *code, uint16_t *freq) +{ + while(last - first > 1) + { + uint16_t i = first, j = last; + do + { + while(++i < last && code[first] > code[i]); + while(--j > first && code[first] < code[j]); + if(j > i) + { + uint8_t tc = code[i]; + code[i] = code[j]; + code[j] = tc; + uint16_t tf = freq[i]; + freq[i] = freq[j]; + freq[j] = tf; + } + } while(j > i); + + if(first != j) + { + uint8_t tc = code[first]; + code[first] = code[j]; + code[j] = tc; + uint16_t tf = freq[first]; + freq[first] = freq[j]; + freq[j] = tf; + i = j + 1; + if(last - i <= j - first) + { + sw_update(i, last, code, freq); + last = j; + } + else + { + sw_update(first, j, code, freq); + first = i; + } + } + else + ++first; + } +} + +/* Read Huffman tree from bitstream (same structure as method 14) */ +static void sw_read_tree(StuffitBitReaderLE *br, uint16_t codesize, uint8_t *code, uint8_t *codecopy, uint16_t *freq, + uint32_t *buff, uint16_t *result) +{ + uint32_t k = stuffit_bits_le_read(br, 1); + uint32_t j = stuffit_bits_le_read(br, 2) + 2; + uint32_t o = stuffit_bits_le_read(br, 3) + 1; + uint32_t size = 1u << j; + uint32_t m = size - 1; + k = k ? m - 1 : (uint32_t)-1; + + if(stuffit_bits_le_read(br, 2) & 1) + { + uint16_t temp_result[64]; + uint8_t temp_code[32], temp_copy[32]; + uint16_t temp_freq[64]; + uint32_t temp_buff[32]; + memset(temp_result, 0, sizeof(temp_result)); + sw_read_tree(br, size, temp_code, temp_copy, temp_freq, temp_buff, temp_result); + + for(uint32_t i = 0; i < codesize;) + { + uint32_t l = 0, n; + do + { + l = temp_result[l + stuffit_bits_le_read(br, 1)]; + n = size << 1; + } while(n > l); + l -= n; + if(k != l) + { + if(l == m) + { + l = 0; + do + { + l = temp_result[l + stuffit_bits_le_read(br, 1)]; + n = size << 1; + } while(n > l); + l += 3 - n; + while(l-- && i < codesize) + { + code[i] = code[i - 1]; + i++; + } + } + else + code[i++] = l + o; + } + else + code[i++] = 0; + } + } + else + { + for(uint32_t i = 0; i < codesize;) + { + uint32_t l = stuffit_bits_le_read(br, j); + if(k != l) + { + if(l == m) + { + l = stuffit_bits_le_read(br, j) + 3; + while(l-- && i < codesize) + { + code[i] = code[i - 1]; + i++; + } + } + else + code[i++] = l + o; + } + else + code[i++] = 0; + } + } + + /* Sort + canonical code assignment + tree build */ + for(uint32_t i = 0; i < codesize; i++) + { + codecopy[i] = code[i]; + freq[i] = i; + } + sw_update(0, codesize, codecopy, freq); + + uint32_t i; + for(i = 0; i < codesize && !codecopy[i]; i++); + for(j = 0; i < codesize; i++, j++) + { + if(i) j <<= (codecopy[i] - codecopy[i - 1]); + k = codecopy[i]; + m = 0; + for(uint32_t l = j; k--; l >>= 1) m = (m << 1) | (l & 1); + buff[freq[i]] = m; + } + + for(i = 0; i < codesize * 2; i++) result[i] = 0; + j = 2; + for(i = 0; i < codesize; i++) + { + uint32_t l = 0; + m = buff[i]; + for(k = 0; k < code[i]; k++) + { + l += (m & 1); + if(code[i] - 1 <= k) + result[l] = codesize * 2 + i; + else + { + if(!result[l]) + { + result[l] = j; + j += 2; + } + l = result[l]; + } + m >>= 1; + } + } + + /* Byte-align after tree read (68K code length reader returns byte-aligned) */ + br->bits_left = 0; + br->buffer = 0; +} + +int stuffit_shrinkwrap_decode_buffer(uint8_t *dst, size_t *dst_size, const uint8_t *src, size_t src_size) +{ + if(src_size < 16) return -1; + + /* 16-byte header: skip4 | LE32 decomp_size | LE32 decomp_size | FFFFFFFF */ + uint32_t decomp_size = + (uint32_t)src[4] | ((uint32_t)src[5] << 8) | ((uint32_t)src[6] << 16) | ((uint32_t)src[7] << 24); + if(decomp_size > *dst_size) return -1; + + /* Build length/distance tables */ + uint8_t len_extra[20]; + uint16_t len_base[20]; + uint8_t dist_extra[16]; + uint16_t dist_base[16]; + + uint32_t i, k; + for(i = 0, k = 0; i < 20; i++) + { + len_base[i] = k + 4; + len_extra[i] = (i >= 2) ? ((i - 2) >> 1) : 0; + k += (1u << len_extra[i]); + } + for(i = 0, k = 1; i < 16; i++) + { + dist_base[i] = k; + dist_extra[i] = i; + k += (1u << i); + } + + /* Allocate workspace */ + uint8_t *code = calloc(SW_LITLEN + 64, 1); + uint8_t *codecopy = calloc(SW_LITLEN + 64, 1); + uint16_t *freq = calloc(SW_LITLEN + 64, sizeof(uint16_t)); + uint32_t *buff = calloc(SW_LITLEN + 64, sizeof(uint32_t)); + uint16_t *litlen_tree = calloc(SW_LITLEN * 8, sizeof(uint16_t)); + uint16_t *dist_tree = calloc(4096, sizeof(uint16_t)); + + if(!code || !codecopy || !freq || !buff || !litlen_tree || !dist_tree) goto fail; + + StuffitBitReaderLE br; + stuffit_bits_le_init(&br, src + 16, src_size - 16); + + /* Read litlen tree (276 symbols) */ + memset(litlen_tree, 0, SW_LITLEN * 8 * sizeof(uint16_t)); + sw_read_tree(&br, SW_LITLEN, code, codecopy, freq, buff, litlen_tree); + + /* Read dist tree (16 symbols) */ + memset(code, 0, SW_LITLEN + 64); + memset(dist_tree, 0, 4096 * sizeof(uint16_t)); + sw_read_tree(&br, SW_DIST, code, codecopy, freq, buff, dist_tree); + + /* Main decode loop */ + size_t di = 0; + uint32_t litlen_th = SW_LITLEN * 2; + uint32_t dist_th = SW_DIST * 2; + + while(di < decomp_size) + { + /* Decode literal/length symbol */ + i = 0; + while(i < litlen_th) i = litlen_tree[i + stuffit_bits_le_read(&br, 1)]; + i -= litlen_th; + + if(i < 256) { dst[di++] = (uint8_t)i; } + else + { + i -= 256; + if(i >= 20) goto fail; + + k = len_base[i]; + uint32_t extra = len_extra[i]; + if(extra) k += stuffit_bits_le_read(&br, extra); + + /* Decode distance */ + uint32_t d = 0; + while(d < dist_th) d = dist_tree[d + stuffit_bits_le_read(&br, 1)]; + d -= dist_th; + if(d >= 16) goto fail; + + uint32_t dist = dist_base[d]; + extra = dist_extra[d]; + if(extra) dist += stuffit_bits_le_read(&br, extra); + + if(dist > di) goto fail; + + /* Copy from back-reference */ + for(uint32_t j2 = 0; j2 < k && di < decomp_size; j2++) + { + dst[di] = dst[di - dist]; + di++; + } + } + } + + free(code); + free(codecopy); + free(freq); + free(buff); + free(litlen_tree); + free(dist_tree); + *dst_size = di; + return 0; + +fail: + free(code); + free(codecopy); + free(freq); + free(buff); + free(litlen_tree); + free(dist_tree); + return -1; +} diff --git a/stuffit/stuffit.h b/stuffit/stuffit.h index 3b97446..71a65d2 100644 --- a/stuffit/stuffit.h +++ b/stuffit/stuffit.h @@ -32,6 +32,9 @@ int stuffit_method13_decode_buffer(uint8_t *dst, size_t *dst_size, const uint8_t int stuffit_method14_decode_buffer(uint8_t *dst, size_t *dst_size, const uint8_t *src, size_t src_size); int stuffit_arsenic_decode_buffer(uint8_t *dst, size_t *dst_size, const uint8_t *src, size_t src_size); +/* ShrinkWrap 3 SIT2 compression (used by NDIF chunk type 0xF0) */ +int stuffit_shrinkwrap_decode_buffer(uint8_t *dst, size_t *dst_size, const uint8_t *src, size_t src_size); + /* StuffIt X compression methods */ int stuffitx_brimstone_decode_buffer(uint8_t *dst, size_t *dst_size, const uint8_t *src, size_t src_size, int max_order, int sub_alloc_size, size_t *src_consumed); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fa6e371..5fb17bf 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -180,6 +180,9 @@ file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/stuffit_method13_v5.bin file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/stuffit_arsenic.bin DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/stuffit_shrinkwrap.bin + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/stuffitx_brimstone.bin DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) diff --git a/tests/data/stuffit_shrinkwrap.bin b/tests/data/stuffit_shrinkwrap.bin new file mode 100644 index 0000000..8435d92 Binary files /dev/null and b/tests/data/stuffit_shrinkwrap.bin differ diff --git a/tests/stuffit/stuffit.cpp b/tests/stuffit/stuffit.cpp index 0851203..fa96036 100644 --- a/tests/stuffit/stuffit.cpp +++ b/tests/stuffit/stuffit.cpp @@ -391,3 +391,36 @@ TEST_F(StuffitxBlendFixture, stuffitx_blend) free(outBuf); EXPECT_EQ(crc, EXPECTED_CRC32); } + +/* ---- ShrinkWrap 3 SIT2 (NDIF chunk type 0xF0) ---- */ + +#define SHRINKWRAP_SIZE 27219 +#define SHRINKWRAP_ORIGSIZE 65536 +#define SHRINKWRAP_CRC32 0x93A5E340 + +class StuffitShrinkwrapFixture : public ::testing::Test +{ +protected: + uint8_t *buf = nullptr; + + void SetUp() { buf = load_test_file("stuffit_shrinkwrap.bin", SHRINKWRAP_SIZE); } + + void TearDown() { free(buf); } +}; + +TEST_F(StuffitShrinkwrapFixture, stuffit_shrinkwrap) +{ + ASSERT_NE(buf, nullptr); + + size_t destLen = SHRINKWRAP_ORIGSIZE; + auto *outBuf = (uint8_t *)malloc(SHRINKWRAP_ORIGSIZE); + + auto err = AARU_stuffit_shrinkwrap_decode_buffer(outBuf, &destLen, buf, SHRINKWRAP_SIZE); + + EXPECT_EQ(err, 0); + EXPECT_EQ(destLen, (size_t)SHRINKWRAP_ORIGSIZE); + + auto crc = crc32_data(outBuf, destLen); + free(outBuf); + EXPECT_EQ(crc, (uint32_t)SHRINKWRAP_CRC32); +}