Add ShrinkWrap 3 SIT2 decompression support and tests

This commit is contained in:
2026-04-20 14:43:39 +01:00
parent 5cfdbe485c
commit 16dd2ce90f
8 changed files with 374 additions and 1 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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,

322
stuffit/shrinkwrap.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
/*
* 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 <stdlib.h>
#include <string.h>
#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;
}

View File

@@ -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);

View File

@@ -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/)

Binary file not shown.

View File

@@ -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);
}