From 16dd2ce90f08d5026de2d336f87b386923aaa99e Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Mon, 20 Apr 2026 14:43:39 +0100 Subject: [PATCH] Add ShrinkWrap 3 SIT2 decompression support and tests --- CMakeLists.txt | 1 + library.c | 9 +- library.h | 4 + stuffit/shrinkwrap.c | 322 ++++++++++++++++++++++++++++++ stuffit/stuffit.h | 3 + tests/CMakeLists.txt | 3 + tests/data/stuffit_shrinkwrap.bin | Bin 0 -> 27219 bytes tests/stuffit/stuffit.cpp | 33 +++ 8 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 stuffit/shrinkwrap.c create mode 100644 tests/data/stuffit_shrinkwrap.bin 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 0000000000000000000000000000000000000000..8435d92e1b9dab4a533184a1cceeb6f3629ad9e3 GIT binary patch literal 27219 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 literal 0 HcmV?d00001 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); +}