diff --git a/CMakeLists.txt b/CMakeLists.txt index f065111..e45df87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -238,7 +238,9 @@ add_library("Aaru.Compression.Native" SHARED library.c apple_rle.c apple_rle.h a ppmd/VariantH.c ppmd/VariantH.h cpt/cpt.c - cpt/cpt.h) + cpt/cpt.h + dd/dd.c + dd/dd.h) include(3rdparty/bzip2.cmake) include(3rdparty/flac.cmake) diff --git a/dd/dd.c b/dd/dd.c new file mode 100644 index 0000000..24edfca --- /dev/null +++ b/dd/dd.c @@ -0,0 +1,1032 @@ +/* + * dd.c - DiskDoubler archive decompression + * + * 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 . + */ + +#include "dd.h" + +#include +#include + +#include "../cpt/cpt.h" +#include "../pak/bitstream.h" +#include "../pak/prefixcode.h" + +/* ================================================================ + * ADn decompressor (methods 6 and 9): + * Block-based LZSS with 8 KB output blocks. + * + * Each block has a 12-byte header: + * bytes 0-1: compressed block size (BE u16) + * bytes 2-3: uncompressed block size (BE u16, max 0x2000) + * bytes 4-7: reserved (XOR accumulator) + * byte 8: data XOR checksum + * byte 9: flags (bit 0: 1=uncompressed) + * byte 10: padding + * byte 11: header XOR checksum + * + * LZSS encoding (MSB-first bits): + * bit=0: literal (8 bits) + * bit=1: match + * far flag (1 bit): 1 = 12-bit offset, 0 = 8-bit offset + * offset (8 or 12 bits) + * length: variable encoding: + * 0 -> 2 + * 10 -> 3 or 4 (1 more bit) + * 11 -> read 4 bits + 5 + * ================================================================ */ + +#define ADN_BLOCK_SIZE 0x2000 + +/** Read a big-endian uint16 from raw data. */ +static uint16_t read_be16(const uint8_t *p) { return (uint16_t)((p[0] << 8) | p[1]); } + +/** Read a big-endian uint32 from raw data. */ +static uint32_t read_be32(const uint8_t *p) +{ return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3]; } + +/** Copy bytes allowing overlapping source/destination (byte-repeat). */ +static void copy_bytes_repeat(uint8_t *dst, const uint8_t *src, int length) +{ + for(int i = 0; i < length; i++) dst[i] = src[i]; +} + +int dd_adn_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size) +{ + if(!dst_buffer || !dst_size || !src_buffer) return -1; + + const size_t dst_cap = *dst_size; + size_t out_pos = 0; + size_t in_pos = 0; + + while(in_pos < src_size && out_pos < dst_cap) + { + /* Need at least 12 bytes for the block header. */ + if(in_pos + 12 > src_size) return -1; + + /* Parse block header. */ + uint8_t headxor = 0; + + uint16_t compsize = read_be16(&src_buffer[in_pos]); + headxor ^= (uint8_t)(compsize >> 8) ^ (uint8_t)compsize; + + uint16_t uncompsize = read_be16(&src_buffer[in_pos + 2]); + if(uncompsize > ADN_BLOCK_SIZE) return -1; + headxor ^= (uint8_t)(uncompsize >> 8) ^ (uint8_t)uncompsize; + + /* 4 reserved bytes contribute to header XOR. */ + headxor ^= src_buffer[in_pos + 4]; + headxor ^= src_buffer[in_pos + 5]; + headxor ^= src_buffer[in_pos + 6]; + headxor ^= src_buffer[in_pos + 7]; + + uint8_t datacorrectxor = src_buffer[in_pos + 8]; + headxor ^= datacorrectxor; + + uint8_t flags = src_buffer[in_pos + 9]; + headxor ^= flags; + + headxor ^= src_buffer[in_pos + 10]; /* padding */ + + uint8_t headcorrectxor = src_buffer[in_pos + 11]; + if(headxor != headcorrectxor) return -1; + + size_t block_data_start = in_pos + 12; + size_t next_block = block_data_start + compsize; + + if(next_block > src_size) return -1; + if(out_pos + uncompsize > dst_cap) return -1; + + uint8_t *outbuf = &dst_buffer[out_pos]; + + if(flags & 1) + { + /* Uncompressed block: copy raw bytes. */ + memcpy(outbuf, &src_buffer[block_data_start], uncompsize); + } + else + { + /* Compressed block: LZSS decode. */ + BitStream bs; + bitstream_init(&bs, &src_buffer[block_data_start], compsize); + + int currpos = 0; + while(currpos < uncompsize) + { + uint32_t ismatch = bitstream_read_bit(&bs); + + if(!ismatch) { outbuf[currpos++] = (uint8_t)bitstream_read_bits(&bs, 8); } + else + { + uint32_t isfar = bitstream_read_bit(&bs); + int offset = (int)bitstream_read_bits(&bs, isfar ? 12 : 8); + + if(offset > currpos) return -1; + + int length; + if(bitstream_read_bit(&bs) == 0) { length = 2; } + else + { + if(bitstream_read_bit(&bs) == 0) + { + if(bitstream_read_bit(&bs) == 0) + length = 3; + else + length = 4; + } + else + { + length = (int)bitstream_read_bits(&bs, 4) + 5; + } + } + + if(currpos + length > uncompsize) length = uncompsize - currpos; + if(length > offset) return -1; + + copy_bytes_repeat(&outbuf[currpos], &outbuf[currpos - offset], length); + currpos += length; + } + } + } + + out_pos += uncompsize; + in_pos = next_block; + } + + *dst_size = out_pos; + return 0; +} + +/* ================================================================ + * DDn decompressor (method 10): + * Block-based Huffman LZ77 with 64 KB sliding window. + * + * Each block has a 22-byte header followed by Huffman tables and + * compressed data. The block is organized in three sections: + * 1. Offset codes (Huffman-coded, decoded to uint16 offsets) + * 2. Literal bytes (raw or Huffman-coded) + * 3. Length codes (Huffman-coded, interleaved decode) + * + * Block header (22 bytes): + * bytes 0-3: uncompressed block size (BE u32) + * bytes 4-5: number of literals (BE u16) + * bytes 6-7: number of match offsets (BE u16) + * bytes 8-9: length code compressed size (BE u16) + * bytes 10-11: literal compressed size (BE u16) + * bytes 12-13: offset code compressed size (BE u16) + * byte 14: flags (bit 6: uncompressed block, bit 7: literals Huffman) + * byte 15: padding + * bytes 16-18: data XOR checksum bytes + * byte 19: uncompressed data XOR checksum + * byte 20: padding + * byte 21: header XOR checksum + * + * Huffman code table header (4-byte packed word): + * bits 31-24: numcodes - 1 + * bits 23-13: compressed size in bytes + * bits 12-8: maximum code length + * bits 7-3: bits per code length field + * bit 2: uses zero coding (sparse) + * bits 1-0: reserved + * + * Offset decoding: + * slot < 4: offset = slot + 1 + * slot >= 4: bits = slot/2 - 1; base = ((2 + (slot & 1)) << bits) + 1; + * offset = base + read_bits(bits) + * + * Length code interpretation: + * code == 0: output one literal + * 1 <= code < 128: LZ77 match, length = code + 2, offset from offset table + * code >= 128: run of (1 << (code - 128)) literals, first one output + * immediately, rest queued + * ================================================================ */ + +#define DDN_WINDOW_SIZE 65536 +#define DDN_WINDOW_MASK (DDN_WINDOW_SIZE - 1) +#define DDN_BUFFER_SIZE 0x10000 + +/** + * Read a canonical Huffman code table from the DDn bitstream. + * + * The 4-byte header packs: numcodes, compressed byte size, max code length, + * bits-per-length, and a zero-coding flag. + * + * Returns an allocated PrefixCode on success, NULL on error. + * The caller must free the returned code with prefix_code_free(). + */ +static PrefixCode *ddn_read_code(BitStream *bs) +{ + uint32_t head = bitstream_read_bits(bs, 32); + + int numcodes = ((head >> 24) & 0xff) + 1; + int numbytes = (head >> 13) & 0x7ff; + int maxlength = (head >> 8) & 0x1f; + int numbits = (head >> 3) & 0x1f; + + if(numcodes <= 0 || maxlength <= 0 || numbits <= 0) return NULL; + + /* Remember where we started reading code data so we can skip to the end. */ + /* Compute byte offset: pos is the next byte, bitcount is bits remaining. */ + size_t byte_start = bs->pos - (size_t)((bs->bitcount + 7) / 8); + /* But we also need to account for bits already consumed within the + * bitstream up to this point. We track the logical end position. */ + size_t end_byte_pos = byte_start + (size_t)numbytes; + + int *codelengths = (int *)calloc((size_t)numcodes, sizeof(int)); + if(!codelengths) return NULL; + + if(head & 0x04) + { + /* Zero coding: each entry has a 1-bit flag, only present symbols + * have a numbits-wide code length following the flag. */ + for(int i = 0; i < numcodes; i++) + { + if(bitstream_read_bit(bs)) + { + codelengths[i] = (int)bitstream_read_bits(bs, numbits); + if(codelengths[i] > maxlength) + { + free(codelengths); + return NULL; + } + } + else + { + codelengths[i] = 0; + } + } + } + else + { + /* Dense coding: each entry is numbits wide. */ + for(int i = 0; i < numcodes; i++) + { + codelengths[i] = (int)bitstream_read_bits(bs, numbits); + if(codelengths[i] > maxlength) + { + free(codelengths); + return NULL; + } + } + } + + /* Seek past the code table data to the byte-aligned end position. */ + /* Reset bitstream to end_byte_pos. */ + bs->pos = end_byte_pos; + bs->bitbuffer = 0; + bs->bitcount = 0; + + PrefixCode *code = prefix_code_alloc_with_lengths(codelengths, numcodes, maxlength, true); + free(codelengths); + return code; +} + +int dd_ddn_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size) +{ + if(!dst_buffer || !dst_size || !src_buffer) return -1; + + const size_t dst_cap = *dst_size; + size_t out_pos = 0; + + /* 64 KB sliding window for LZ77 match references. */ + uint8_t *window = (uint8_t *)calloc(DDN_WINDOW_SIZE, 1); + if(!window) return -1; + size_t win_pos = 0; + + /* Temporary buffer for interleaved literal/offset data. */ + uint8_t *buffer = (uint8_t *)malloc(DDN_BUFFER_SIZE); + if(!buffer) + { + free(window); + return -1; + } + + /* Block iterator: tracks input position via a raw pointer. */ + size_t next_block = 0; + + int result = 0; + + while(next_block < src_size && out_pos < dst_cap) + { + /* Need at least 22 bytes for the block header. */ + if(next_block + 22 > src_size) + { + result = -1; + break; + } + + const uint8_t *hdr = &src_buffer[next_block]; + uint8_t headxor = 0; + + uint32_t uncompsize = read_be32(&hdr[0]); + headxor ^= (uint8_t)(uncompsize >> 24) ^ (uint8_t)(uncompsize >> 16) ^ (uint8_t)(uncompsize >> 8) ^ + (uint8_t)uncompsize; + + uint16_t numliterals = read_be16(&hdr[4]); + headxor ^= (uint8_t)(numliterals >> 8) ^ (uint8_t)numliterals; + + uint16_t numoffsets = read_be16(&hdr[6]); + headxor ^= (uint8_t)(numoffsets >> 8) ^ (uint8_t)numoffsets; + + uint16_t lengthcompsize = read_be16(&hdr[8]); + headxor ^= (uint8_t)(lengthcompsize >> 8) ^ (uint8_t)lengthcompsize; + + uint16_t literalcompsize = read_be16(&hdr[10]); + headxor ^= (uint8_t)(literalcompsize >> 8) ^ (uint8_t)literalcompsize; + + uint16_t offsetcompsize = read_be16(&hdr[12]); + headxor ^= (uint8_t)(offsetcompsize >> 8) ^ (uint8_t)offsetcompsize; + + uint8_t flags = hdr[14]; + headxor ^= flags; + + headxor ^= hdr[15]; /* padding */ + + uint8_t datacorrectxor1 = hdr[16]; + headxor ^= datacorrectxor1; + uint8_t datacorrectxor2 = hdr[17]; + headxor ^= datacorrectxor2; + uint8_t datacorrectxor3 = hdr[18]; + headxor ^= datacorrectxor3; + + uint8_t uncompcorrectxor = hdr[19]; + headxor ^= uncompcorrectxor; + + headxor ^= hdr[20]; /* padding */ + + uint8_t headcorrectxor = hdr[21]; + if(headxor != headcorrectxor) + { + result = -1; + break; + } + + if(out_pos + uncompsize > dst_cap) + { + result = -1; + break; + } + + size_t block_data_start = next_block + 22; + + if(flags & 0x40) + { + /* Uncompressed block. */ + BitStream bs; + bitstream_init(&bs, &src_buffer[block_data_start], src_size - block_data_start); + + for(uint32_t i = 0; i < uncompsize; i++) + { + uint8_t byte = (uint8_t)bitstream_read_bits(&bs, 8); + dst_buffer[out_pos] = byte; + window[win_pos] = byte; + win_pos = (win_pos + 1) & DDN_WINDOW_MASK; + out_pos++; + } + + /* Advance past the raw data. */ + next_block = block_data_start + uncompsize; + continue; + } + + /* Validate buffer sizes. */ + if((size_t)numliterals + (size_t)numoffsets * 2 > DDN_BUFFER_SIZE) + { + result = -1; + break; + } + + /* Set up pointers into the temporary buffer. */ + uint8_t *literalptr = buffer; + uint16_t *offsetptr = (uint16_t *)&buffer[numliterals]; + + /* Compute section boundaries. + * After header: offset codes, then literals, then length codes. */ + size_t literal_start = block_data_start + offsetcompsize; + size_t length_start = literal_start + literalcompsize; + next_block = length_start + lengthcompsize; + + if(next_block > src_size) + { + result = -1; + break; + } + + /* -- Phase 1: Decode offset table -- */ + BitStream bs; + bitstream_init(&bs, &src_buffer[block_data_start], offsetcompsize); + + PrefixCode *offsetcode = ddn_read_code(&bs); + if(!offsetcode) + { + result = -1; + break; + } + + for(int i = 0; i < numoffsets; i++) + { + int slot = prefix_code_read_symbol(&bs, offsetcode); + if(slot < 0) + { + prefix_code_free(offsetcode); + result = -1; + goto done; + } + + if(slot < 4) { offsetptr[i] = (uint16_t)(slot + 1); } + else + { + int bits = slot / 2 - 1; + int start = ((2 + (slot & 1)) << bits) + 1; + offsetptr[i] = (uint16_t)(start + (int)bitstream_read_bits(&bs, bits)); + } + } + + prefix_code_free(offsetcode); + offsetcode = NULL; + + /* -- Phase 2: Decode literal data -- */ + BitStream lbs; + bitstream_init(&lbs, &src_buffer[literal_start], literalcompsize); + + if(flags & 0x80) + { + /* Huffman-compressed literals. */ + PrefixCode *literalcode = ddn_read_code(&lbs); + if(!literalcode) + { + result = -1; + break; + } + + for(int i = 0; i < numliterals; i++) + { + int sym = prefix_code_read_symbol(&lbs, literalcode); + if(sym < 0) + { + prefix_code_free(literalcode); + result = -1; + goto done; + } + literalptr[i] = (uint8_t)sym; + } + + prefix_code_free(literalcode); + } + else + { + /* Uncompressed literals: read raw bytes. */ + for(int i = 0; i < numliterals; i++) literalptr[i] = (uint8_t)bitstream_read_bits(&lbs, 8); + } + + /* -- Phase 3: Decode length codes and produce output -- */ + BitStream lenbs; + bitstream_init(&lenbs, &src_buffer[length_start], lengthcompsize); + + PrefixCode *lengthcode = ddn_read_code(&lenbs); + if(!lengthcode) + { + result = -1; + break; + } + + uint8_t *lit_cursor = literalptr; + uint16_t *off_cursor = offsetptr; + int lits_left = 0; + + size_t block_end = out_pos + uncompsize; + + while(out_pos < block_end) + { + if(lits_left > 0) + { + /* Pending run of literals from a previous code >= 128. */ + uint8_t byte = *lit_cursor++; + dst_buffer[out_pos] = byte; + window[win_pos] = byte; + win_pos = (win_pos + 1) & DDN_WINDOW_MASK; + out_pos++; + lits_left--; + continue; + } + + int code = prefix_code_read_symbol(&lenbs, lengthcode); + if(code < 0) + { + prefix_code_free(lengthcode); + result = -1; + goto done; + } + + if(code == 0) + { + /* Single literal byte. */ + uint8_t byte = *lit_cursor++; + dst_buffer[out_pos] = byte; + window[win_pos] = byte; + win_pos = (win_pos + 1) & DDN_WINDOW_MASK; + out_pos++; + } + else if(code < 128) + { + /* LZ77 match: length = code + 2, offset from table. */ + int length = code + 2; + int offset = *off_cursor++; + + if((size_t)offset > win_pos + out_pos) + { + prefix_code_free(lengthcode); + result = -1; + goto done; + } + + /* Cap length at block boundary. */ + if(out_pos + (size_t)length > block_end) length = (int)(block_end - out_pos); + + for(int i = 0; i < length; i++) + { + uint8_t byte = window[(win_pos - (size_t)offset) & DDN_WINDOW_MASK]; + dst_buffer[out_pos] = byte; + window[win_pos] = byte; + win_pos = (win_pos + 1) & DDN_WINDOW_MASK; + out_pos++; + } + } + else + { + /* Run of consecutive literals: 1 << (code - 128) total. + * Output the first one now, queue the rest. */ + int run_length = 1 << (code - 128); + + /* Cap at block boundary. */ + if(out_pos + (size_t)run_length > block_end) run_length = (int)(block_end - out_pos); + + uint8_t byte = *lit_cursor++; + dst_buffer[out_pos] = byte; + window[win_pos] = byte; + win_pos = (win_pos + 1) & DDN_WINDOW_MASK; + out_pos++; + lits_left = run_length - 1; + } + } + + prefix_code_free(lengthcode); + lengthcode = NULL; + } + +done: + free(window); + free(buffer); + *dst_size = out_pos; + return result; +} + +/* ================================================================ + * Method 2 decompressor (also used by method 5): + * Adaptive Huffman coding using splay trees. + * + * The decoder maintains an array of binary trees (one per tree index). + * Each tree has 256 internal nodes (indices 0-255) and 256 leaf nodes + * (indices 256-511, representing byte values 0-255). + * + * To decode a byte, walk from node 1 (root) using input bits: + * bit 0 -> left child, bit 1 -> right child. + * When a leaf (node >= 256) is reached, the byte value is (node - 256). + * + * After each decoded byte, the tree is restructured ("splayed") to move + * the decoded symbol closer to the root. The next tree is selected as + * (decoded_byte % num_trees). + * + * Initial state: each internal node j has left child = 2*j and right + * child = 2*j+1, with parent pointers set symmetrically. + * ================================================================ */ + +typedef struct +{ + uint8_t parents[512]; + uint16_t leftchildren[256]; + uint16_t rightchildren[256]; +} SplayTree; + +static void splay_tree_init(SplayTree *tree) +{ + for(int j = 0; j < 256; j++) + { + tree->parents[2 * j] = (uint8_t)j; + tree->parents[2 * j + 1] = (uint8_t)j; + tree->leftchildren[j] = (uint16_t)(j * 2); + tree->rightchildren[j] = (uint16_t)(j * 2 + 1); + } +} + +static void splay_tree_update(SplayTree *tree, int byte) +{ + uint8_t *parents = tree->parents; + uint16_t *leftchildren = tree->leftchildren; + uint16_t *rightchildren = tree->rightchildren; + + int node = byte + 0x100; + for(;;) + { + int parentnode = parents[node]; + if(parentnode == 1) break; + + int grandparent = parents[parentnode]; + + int uncle = leftchildren[grandparent]; + if(uncle == parentnode) + { + uncle = rightchildren[grandparent]; + rightchildren[grandparent] = (uint16_t)node; + } + else + { + leftchildren[grandparent] = (uint16_t)node; + } + + if(leftchildren[parentnode] != node) + rightchildren[parentnode] = (uint16_t)uncle; + else + leftchildren[parentnode] = (uint16_t)uncle; + + parents[node] = (uint8_t)grandparent; + parents[uncle] = (uint8_t)parentnode; + + node = grandparent; + if(node == 1) break; + } +} + +int dd_method2_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size, + int num_trees) +{ + if(!dst_buffer || !dst_size || !src_buffer) return -1; + if(num_trees < 1 || num_trees > 256) return -1; + + const size_t dst_cap = *dst_size; + + SplayTree *trees = (SplayTree *)malloc((size_t)num_trees * sizeof(SplayTree)); + if(!trees) return -1; + + for(int i = 0; i < num_trees; i++) splay_tree_init(&trees[i]); + + BitStream bs; + bitstream_init(&bs, src_buffer, src_size); + + int currtree = 0; + size_t out_pos = 0; + + while(out_pos < dst_cap && !bitstream_eof(&bs)) + { + /* Decode one byte by walking the current tree from root (node 1). */ + int node = 1; + for(;;) + { + uint32_t bit = bitstream_read_bit(&bs); + + if(bit) + node = trees[currtree].rightchildren[node]; + else + node = trees[currtree].leftchildren[node]; + + if(node >= 0x100) + { + int byte = node - 0x100; + + dst_buffer[out_pos++] = (uint8_t)byte; + + /* Splay the tree to move this symbol closer to root. */ + splay_tree_update(&trees[currtree], byte); + + /* Select next tree based on the decoded byte. */ + currtree = byte % num_trees; + break; + } + } + } + + free(trees); + *dst_size = out_pos; + return 0; +} + +/* ================================================================ + * Stac LZS decompressor (method 7): + * LZSS with 2 KB sliding window. Match lengths are unbounded. + * + * Encoding (MSB-first bits): + * bit=0: literal (8 bits) + * bit=1: match + * bit=1: 7-bit offset (short, 1-127) + * bit=0: 7-bit high offset + 4-bit low offset = 11-bit offset + * if high == 0: end of stream marker + * Length codes (fixed Huffman): + * 00 -> 2, 01 -> 3, 10 -> 4 + * 1100 -> 5, 1101 -> 6, 1110 -> 7, 1111 -> 8 + * If length == 8, read extension nibbles: add each 4-bit value + * until a value != 15 is read. + * ================================================================ */ + +#define STAC_WINDOW_SIZE 2048 +#define STAC_WINDOW_MASK (STAC_WINDOW_SIZE - 1) + +/** + * Decode a match length from the bitstream using the fixed Huffman table. + * + * 00 -> 2, 01 -> 3, 10 -> 4, 1100 -> 5, 1101 -> 6, 1110 -> 7, 1111 -> 8 + * If the result is 8, extension nibbles are read until one is < 15. + */ +static int stac_read_length(BitStream *bs) +{ + int length; + + uint32_t b0 = bitstream_read_bit(bs); + if(b0 == 0) + { + /* 0x -> 2 or 3 */ + length = (int)bitstream_read_bit(bs) + 2; + } + else + { + uint32_t b1 = bitstream_read_bit(bs); + if(b1 == 0) + { + /* 10 -> 4 */ + length = 4; + } + else + { + /* 11xx -> 5-8 */ + length = (int)bitstream_read_bits(bs, 2) + 5; + } + } + + if(length == 8) + { + /* Extended length: keep reading 4-bit nibbles. */ + for(;;) + { + int code = (int)bitstream_read_bits(bs, 4); + length += code; + if(code != 15) break; + } + } + + return length; +} + +int dd_stac_lzs_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size) +{ + if(!dst_buffer || !dst_size || !src_buffer) return -1; + + const size_t dst_cap = *dst_size; + + uint8_t window[STAC_WINDOW_SIZE]; + memset(window, 0, sizeof(window)); + size_t win_pos = 0; + size_t out_pos = 0; + + BitStream bs; + bitstream_init(&bs, src_buffer, src_size); + + while(out_pos < dst_cap && !bitstream_eof(&bs)) + { + if(bitstream_read_bit(&bs) == 0) + { + /* Literal byte. */ + uint8_t byte = (uint8_t)bitstream_read_bits(&bs, 8); + dst_buffer[out_pos++] = byte; + window[win_pos] = byte; + win_pos = (win_pos + 1) & STAC_WINDOW_MASK; + } + else + { + /* Match. */ + int offset; + + if(bitstream_read_bit(&bs) == 1) + { + /* Short offset: 7 bits. */ + offset = (int)bitstream_read_bits(&bs, 7); + } + else + { + /* Long offset: 7-bit high + 4-bit low = 11 bits. */ + int offset_high = (int)bitstream_read_bits(&bs, 7); + if(offset_high == 0) + { + /* End of stream marker. */ + break; + } + offset = (offset_high << 4) | (int)bitstream_read_bits(&bs, 4); + } + + if((size_t)offset > out_pos) return -1; + + int length = stac_read_length(&bs); + + /* Copy from window, handling lengths > window size by chunking. */ + for(int i = 0; i < length; i++) + { + if(out_pos >= dst_cap) break; + + uint8_t byte = window[(win_pos - (size_t)offset) & STAC_WINDOW_MASK]; + dst_buffer[out_pos++] = byte; + window[win_pos] = byte; + win_pos = (win_pos + 1) & STAC_WINDOW_MASK; + } + } + } + + *dst_size = out_pos; + return 0; +} + +/* ================================================================ + * Compact Pro wrapper (method 8): + * Reads a 16-byte header. If the sum of those bytes is zero, the data + * is LZH + RLE compressed; otherwise it is RLE-only. + * ================================================================ */ + +int dd_cpt_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size) +{ + if(!dst_buffer || !dst_size || !src_buffer) return -1; + if(src_size < 16) return -1; + + int sum = 0; + for(int i = 0; i < 16; i++) sum += src_buffer[i]; + + const uint8_t *payload = src_buffer + 16; + size_t payload_size = src_size - 16; + + if(sum == 0) + return cpt_lzh_rle_decode_buffer(dst_buffer, dst_size, payload, payload_size); + else + return cpt_rle_decode_buffer(dst_buffer, dst_size, payload, payload_size); +} + +/* ================================================================ + * LZW decompressor (method 1, Unix compress variant): + * Variable-width LZW with LSB-first bit reading. + * + * flags byte: bits 0-4 = max code bits (9-16) + * bit 7 = block mode (code 256 triggers table clear) + * + * Dictionary starts with 256 single-byte entries (+ 1 reserved if + * block mode). Code width begins at 9 and grows when the dictionary + * size reaches a power of two. In block mode, code 256 clears the + * table and resets code width to 9, with padding to the next code- + * width-aligned boundary. + * ================================================================ */ + +#define LZW_INITIAL_BITS 9 + +typedef struct +{ + uint8_t byte; + int parent; +} LzwNode; + +int dd_lzw_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size, int flags) +{ + if(!dst_buffer || !dst_size || !src_buffer) return -1; + + int maxbits = flags & 0x1f; + int blockmode = (flags & 0x80) != 0; + int maxsymbols = 1 << maxbits; + + if(maxbits < LZW_INITIAL_BITS || maxbits > 16) return -1; + + const size_t dst_cap = *dst_size; + + /* Allocate dictionary. */ + LzwNode *nodes = (LzwNode *)malloc((size_t)maxsymbols * sizeof(LzwNode)); + if(!nodes) return -1; + + /* Temporary output stack for reversing LZW chains. */ + uint8_t *stack = (uint8_t *)malloc((size_t)maxsymbols); + if(!stack) + { + free(nodes); + return -1; + } + + /* Initialize 256 single-byte entries. */ + for(int i = 0; i < 256; i++) + { + nodes[i].byte = (uint8_t)i; + nodes[i].parent = -1; + } + + int reserved = blockmode ? 1 : 0; /* code 256 reserved for clear */ + int numsymbols = 256 + reserved; + int symbolsize = LZW_INITIAL_BITS; + int prevsymbol = -1; + int symcount = 0; /* counter for block-mode alignment */ + + BitStream bs; + bitstream_init(&bs, src_buffer, src_size); + + size_t out_pos = 0; + int result = 0; + + while(out_pos < dst_cap && !bitstream_eof(&bs)) + { + int symbol = (int)bitstream_read_bits_le(&bs, symbolsize); + symcount++; + + /* Block mode: code 256 clears the dictionary. */ + if(symbol == 256 && blockmode) + { + /* Skip remaining bits to next symbolsize-aligned boundary. */ + int rem = symcount % 8; + if(rem) + { + for(int i = 0; i < 8 - rem; i++) bitstream_read_bits_le(&bs, symbolsize); + } + + numsymbols = 256 + reserved; + symbolsize = LZW_INITIAL_BITS; + prevsymbol = -1; + symcount = 0; + continue; + } + + /* Resolve the symbol to output bytes. */ + int firstbyte; + + if(symbol < numsymbols) + { + /* Known symbol: find its first byte for new entry. */ + int s = symbol; + while(nodes[s].parent >= 0) s = nodes[s].parent; + firstbyte = nodes[s].byte; + } + else if(symbol == numsymbols && prevsymbol >= 0) + { + /* KwKwK case: first byte of previous symbol. */ + int s = prevsymbol; + while(nodes[s].parent >= 0) s = nodes[s].parent; + firstbyte = nodes[s].byte; + } + else + { + result = -1; + break; + } + + /* Add new dictionary entry. */ + if(prevsymbol >= 0 && numsymbols < maxsymbols) + { + nodes[numsymbols].parent = prevsymbol; + nodes[numsymbols].byte = (uint8_t)firstbyte; + numsymbols++; + + /* Grow code width when numsymbols reaches next power of two. */ + if(numsymbols < maxsymbols && (numsymbols & (numsymbols - 1)) == 0) symbolsize++; + } + + prevsymbol = symbol; + + /* Output the symbol's bytes (reverse chain, then emit). */ + int n = 0; + int s = symbol; + while(s >= 0) + { + if(n >= maxsymbols) + { + result = -1; + goto done; + } + stack[n++] = nodes[s].byte; + s = nodes[s].parent; + } + + for(int i = n - 1; i >= 0; i--) + { + if(out_pos >= dst_cap) break; + dst_buffer[out_pos++] = stack[i]; + } + } + +done: + free(stack); + free(nodes); + *dst_size = out_pos; + return result; +} diff --git a/dd/dd.h b/dd/dd.h new file mode 100644 index 0000000..076bb50 --- /dev/null +++ b/dd/dd.h @@ -0,0 +1,140 @@ +/* + * dd.h - DiskDoubler archive decompression + * + * 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 . + */ + +#ifndef AARU_DD_H +#define AARU_DD_H + +#include +#include + +/** + * Decompress DiskDoubler ADn block-based LZSS data (methods 6 and 9). + * + * The compressed stream consists of sequential blocks, each with a 12-byte + * header followed by compressed or uncompressed block data. Each block + * decompresses to at most 8192 bytes. + * + * The LZSS encoding uses MSB-first bits: bit=0 means literal (8 bits), + * bit=1 means match. Matches encode a near/far flag, offset (8 or 12 bits), + * and variable-length match length (2-20 bytes). + * + * @param dst_buffer Output buffer for decompressed data. + * @param dst_size On entry, capacity of output buffer. + * On return, number of bytes actually written. + * @param src_buffer Input compressed data. + * @param src_size Size of input data in bytes. + * @return 0 on success, -1 on error. + */ +int dd_adn_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size); + +/** + * Decompress DiskDoubler DDn block-based Huffman LZ77 data (method 10). + * + * The compressed stream consists of sequential blocks, each with a 22-byte + * header followed by Huffman code tables and compressed data. Uses a 64 KB + * sliding window. Each block contains separately coded offset tables, + * literal data, and length codes. + * + * @param dst_buffer Output buffer for decompressed data. + * @param dst_size On entry, capacity of output buffer. + * On return, number of bytes actually written. + * @param src_buffer Input compressed data. + * @param src_size Size of input data in bytes. + * @return 0 on success, -1 on error. + */ +int dd_ddn_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size); + +/** + * Decompress DiskDoubler Method 2 adaptive Huffman data. + * + * Uses a set of splay trees (one per byte value modulo numtrees) that + * adapt during decompression. Each output byte is decoded by walking a + * binary tree using input bits, then the tree is restructured to move + * frequently-used symbols closer to the root. + * + * Method 5 is a variant that reads the number of trees from the first + * byte of the stream (0 means 256). Method 2 always uses 256 trees. + * + * @param dst_buffer Output buffer for decompressed data. + * @param dst_size On entry, capacity of output buffer. + * On return, number of bytes actually written. + * @param src_buffer Input compressed data. + * @param src_size Size of input data in bytes. + * @param num_trees Number of splay trees (1-256, typically 256). + * @return 0 on success, -1 on error. + */ +int dd_method2_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size, + int num_trees); + +/** + * Decompress Stac LZS data (DiskDoubler method 7). + * + * LZSS with a 2 KB sliding window. Match lengths are unbounded and may + * exceed the window size. Uses MSB-first bits. Offsets are either 7-bit + * (short) or 11-bit (long). Lengths use a fixed Huffman table for values + * 2-8, with extension nibbles for longer matches. A zero high-offset byte + * signals end of stream. + * + * @param dst_buffer Output buffer for decompressed data. + * @param dst_size On entry, capacity of output buffer. + * On return, number of bytes actually written. + * @param src_buffer Input compressed data. + * @param src_size Size of input data in bytes. + * @return 0 on success, -1 on error. + */ +int dd_stac_lzs_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size); + +/** + * Decompress DiskDoubler Compact Pro data (method 8). + * + * Reads a 16-byte header; if the byte sum is zero, applies LZH + RLE + * decompression, otherwise applies RLE only. Delegates to the existing + * Compact Pro decompressor functions. + * + * @param dst_buffer Output buffer for decompressed data. + * @param dst_size On entry, capacity of output buffer. + * On return, number of bytes actually written. + * @param src_buffer Input compressed data (including 16-byte header). + * @param src_size Size of input data in bytes. + * @return 0 on success, -1 on error. + */ +int dd_cpt_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size); + +/** + * Decompress DiskDoubler LZW data (method 1, Unix compress variant). + * + * Variable-width LZW with LSB-first bit reading, 9 to maxbits code width. + * The flags byte encodes: bits 0-4 = max code bits (9-16), + * bit 7 = block mode (code 256 triggers table clear). + * + * The caller is responsible for stripping the 3-byte header (m1, m2, flags) + * and any XOR decryption before calling this function. + * + * @param dst_buffer Output buffer for decompressed data. + * @param dst_size On entry, capacity of output buffer. + * On return, number of bytes actually written. + * @param src_buffer Input LZW-compressed data (after the 3-byte header). + * @param src_size Size of input data in bytes. + * @param flags The flags byte from the 3-byte header. + * @return 0 on success, -1 on error. + */ +int dd_lzw_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size, int flags); + +#endif /* AARU_DD_H */ diff --git a/library.c b/library.c index c559c11..da20d01 100644 --- a/library.c +++ b/library.c @@ -49,6 +49,7 @@ #include "3rdparty/zstd/lib/zstd.h" #include "ace/ace.h" #include "cpt/cpt.h" +#include "dd/dd.h" #include "zip/zip.h" AARU_EXPORT int32_t AARU_CALL AARU_bzip2_decode_buffer(uint8_t *dst_buffer, uint32_t *dst_size, @@ -566,4 +567,30 @@ AARU_EXPORT int AARU_CALL AARU_cpt_lzh_rle_decode_buffer(uint8_t *dst_buffer, si const uint8_t *src_buffer, size_t src_size) { return cpt_lzh_rle_decode_buffer(dst_buffer, dst_size, src_buffer, src_size); } +/* ============== DiskDoubler Wrappers ============== */ + +AARU_EXPORT int AARU_CALL AARU_dd_adn_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, + size_t src_size) +{ return dd_adn_decode_buffer(dst_buffer, dst_size, src_buffer, src_size); } + +AARU_EXPORT int AARU_CALL AARU_dd_ddn_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, + size_t src_size) +{ return dd_ddn_decode_buffer(dst_buffer, dst_size, src_buffer, src_size); } + +AARU_EXPORT int AARU_CALL AARU_dd_method2_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, + const uint8_t *src_buffer, size_t src_size, int num_trees) +{ return dd_method2_decode_buffer(dst_buffer, dst_size, src_buffer, src_size, num_trees); } + +AARU_EXPORT int AARU_CALL AARU_dd_stac_lzs_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, + const uint8_t *src_buffer, size_t src_size) +{ return dd_stac_lzs_decode_buffer(dst_buffer, dst_size, src_buffer, src_size); } + +AARU_EXPORT int AARU_CALL AARU_dd_cpt_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, + size_t src_size) +{ return dd_cpt_decode_buffer(dst_buffer, dst_size, src_buffer, src_size); } + +AARU_EXPORT int AARU_CALL AARU_dd_lzw_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, + size_t src_size, int flags) +{ return dd_lzw_decode_buffer(dst_buffer, dst_size, src_buffer, src_size, flags); } + AARU_EXPORT uint64_t AARU_CALL AARU_get_acn_version() { return AARU_CHECKUMS_NATIVE_VERSION; } \ No newline at end of file diff --git a/library.h b/library.h index f12e0b4..16c4c16 100644 --- a/library.h +++ b/library.h @@ -340,4 +340,28 @@ AARU_EXPORT int AARU_CALL AARU_cpt_rle_decode_buffer(uint8_t *dst_buffer, size_t AARU_EXPORT int AARU_CALL AARU_cpt_lzh_rle_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, size_t src_size); +// DiskDoubler: ADn block LZSS decompression (methods 6 and 9) +AARU_EXPORT int AARU_CALL AARU_dd_adn_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, + size_t src_size); + +// DiskDoubler: DDn block Huffman LZ77 decompression (method 10) +AARU_EXPORT int AARU_CALL AARU_dd_ddn_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, + size_t src_size); + +// DiskDoubler: Method 2 adaptive Huffman decompression (methods 2 and 5) +AARU_EXPORT int AARU_CALL AARU_dd_method2_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, + const uint8_t *src_buffer, size_t src_size, int num_trees); + +// DiskDoubler: Stac LZS decompression (method 7) +AARU_EXPORT int AARU_CALL AARU_dd_stac_lzs_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, + const uint8_t *src_buffer, size_t src_size); + +// DiskDoubler: Compact Pro decompression (method 8) +AARU_EXPORT int AARU_CALL AARU_dd_cpt_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, + size_t src_size); + +// DiskDoubler: LZW decompression (method 1, Unix compress variant) +AARU_EXPORT int AARU_CALL AARU_dd_lzw_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, + size_t src_size, int flags); + #endif // AARU_COMPRESSION_NATIVE_LIBRARY_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dd0ac7f..9c4719b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -153,6 +153,21 @@ file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/cpt_rle.bin file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/cpt_lzh_rle.bin DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/dd_ad1.bin + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/dd_ad2.bin + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/dd_dd1.bin + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/dd_dd2.bin + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/dd_dd3.bin + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + # 'Google_Tests_run' is the target name # 'test1.cpp tests2.cpp' are source files with tests add_executable(tests_run apple_rle.cpp crc32.c crc32.h adc.cpp bzip2.cpp lzip.cpp lzfse.cpp zstd.cpp lzma.cpp flac.cpp lz4.cpp @@ -164,5 +179,6 @@ add_executable(tests_run apple_rle.cpp crc32.c crc32.h adc.cpp bzip2.cpp lzip.cp arjz/arjz.cpp zip/zip.cpp rar/rar.cpp - cpt/cpt.cpp) + cpt/cpt.cpp + dd/dd.cpp) target_link_libraries(tests_run gtest gtest_main "Aaru.Compression.Native") diff --git a/tests/data/dd_ad1.bin b/tests/data/dd_ad1.bin new file mode 100644 index 0000000..11f1615 Binary files /dev/null and b/tests/data/dd_ad1.bin differ diff --git a/tests/data/dd_ad2.bin b/tests/data/dd_ad2.bin new file mode 100644 index 0000000..5c93281 Binary files /dev/null and b/tests/data/dd_ad2.bin differ diff --git a/tests/data/dd_dd1.bin b/tests/data/dd_dd1.bin new file mode 100644 index 0000000..96c067e Binary files /dev/null and b/tests/data/dd_dd1.bin differ diff --git a/tests/data/dd_dd2.bin b/tests/data/dd_dd2.bin new file mode 100644 index 0000000..87d020f Binary files /dev/null and b/tests/data/dd_dd2.bin differ diff --git a/tests/data/dd_dd3.bin b/tests/data/dd_dd3.bin new file mode 100644 index 0000000..7f8c6d4 Binary files /dev/null and b/tests/data/dd_dd3.bin differ diff --git a/tests/dd/dd.cpp b/tests/dd/dd.cpp new file mode 100644 index 0000000..e812c3b --- /dev/null +++ b/tests/dd/dd.cpp @@ -0,0 +1,226 @@ +/* + * 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 . + */ + +#include +#include +#include +#include +#include +#include + +#include "../../library.h" +#include "../crc32.h" +#include "gtest/gtest.h" + +#define EXPECTED_CRC32 0x66007DBA +#define EXPECTED_ORIGSIZE 152089 + +/* ---- ADn method 9 (AD1) ---- */ + +#define ADN_COMPRESSED_SIZE 102392 + +static const uint8_t *adn_buffer; + +class DdAdnFixture : public ::testing::Test +{ +protected: + void SetUp() + { + char path[PATH_MAX]; + char filename[PATH_MAX]; + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/dd_ad1.bin", path); + FILE *file = fopen(filename, "rb"); + adn_buffer = (const uint8_t *)malloc(ADN_COMPRESSED_SIZE); + fread((void *)adn_buffer, 1, ADN_COMPRESSED_SIZE, file); + fclose(file); + } + + void TearDown() { free((void *)adn_buffer); } +}; + +TEST_F(DdAdnFixture, dd_adn) +{ + size_t destLen = EXPECTED_ORIGSIZE; + auto *outBuf = (uint8_t *)malloc(EXPECTED_ORIGSIZE); + + auto err = AARU_dd_adn_decode_buffer(outBuf, &destLen, adn_buffer, ADN_COMPRESSED_SIZE); + + EXPECT_EQ(err, 0); + EXPECT_EQ(destLen, (size_t)EXPECTED_ORIGSIZE); + + auto crc = crc32_data(outBuf, EXPECTED_ORIGSIZE); + free(outBuf); + EXPECT_EQ(crc, EXPECTED_CRC32); +} + +/* ---- ADn method 6 (AD2) ---- */ + +#define ADN2_COMPRESSED_SIZE 77708 + +static const uint8_t *adn2_buffer; + +class DdAdn2Fixture : public ::testing::Test +{ +protected: + void SetUp() + { + char path[PATH_MAX]; + char filename[PATH_MAX]; + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/dd_ad2.bin", path); + FILE *file = fopen(filename, "rb"); + adn2_buffer = (const uint8_t *)malloc(ADN2_COMPRESSED_SIZE); + fread((void *)adn2_buffer, 1, ADN2_COMPRESSED_SIZE, file); + fclose(file); + } + + void TearDown() { free((void *)adn2_buffer); } +}; + +TEST_F(DdAdn2Fixture, dd_adn2) +{ + size_t destLen = EXPECTED_ORIGSIZE; + auto *outBuf = (uint8_t *)malloc(EXPECTED_ORIGSIZE); + + auto err = AARU_dd_adn_decode_buffer(outBuf, &destLen, adn2_buffer, ADN2_COMPRESSED_SIZE); + + EXPECT_EQ(err, 0); + EXPECT_EQ(destLen, (size_t)EXPECTED_ORIGSIZE); + + auto crc = crc32_data(outBuf, EXPECTED_ORIGSIZE); + free(outBuf); + EXPECT_EQ(crc, EXPECTED_CRC32); +} + +/* ---- DDn method 10 (DD1 - fastest) ---- */ + +#define DDN1_COMPRESSED_SIZE 59903 + +static const uint8_t *ddn1_buffer; + +class DdDdn1Fixture : public ::testing::Test +{ +protected: + void SetUp() + { + char path[PATH_MAX]; + char filename[PATH_MAX]; + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/dd_dd1.bin", path); + FILE *file = fopen(filename, "rb"); + ddn1_buffer = (const uint8_t *)malloc(DDN1_COMPRESSED_SIZE); + fread((void *)ddn1_buffer, 1, DDN1_COMPRESSED_SIZE, file); + fclose(file); + } + + void TearDown() { free((void *)ddn1_buffer); } +}; + +TEST_F(DdDdn1Fixture, dd_ddn1) +{ + size_t destLen = EXPECTED_ORIGSIZE; + auto *outBuf = (uint8_t *)malloc(EXPECTED_ORIGSIZE); + + auto err = AARU_dd_ddn_decode_buffer(outBuf, &destLen, ddn1_buffer, DDN1_COMPRESSED_SIZE); + + EXPECT_EQ(err, 0); + EXPECT_EQ(destLen, (size_t)EXPECTED_ORIGSIZE); + + auto crc = crc32_data(outBuf, EXPECTED_ORIGSIZE); + free(outBuf); + EXPECT_EQ(crc, EXPECTED_CRC32); +} + +/* ---- DDn method 10 (DD2 - normal) ---- */ + +#define DDN2_COMPRESSED_SIZE 54219 + +static const uint8_t *ddn2_buffer; + +class DdDdn2Fixture : public ::testing::Test +{ +protected: + void SetUp() + { + char path[PATH_MAX]; + char filename[PATH_MAX]; + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/dd_dd2.bin", path); + FILE *file = fopen(filename, "rb"); + ddn2_buffer = (const uint8_t *)malloc(DDN2_COMPRESSED_SIZE); + fread((void *)ddn2_buffer, 1, DDN2_COMPRESSED_SIZE, file); + fclose(file); + } + + void TearDown() { free((void *)ddn2_buffer); } +}; + +TEST_F(DdDdn2Fixture, dd_ddn2) +{ + size_t destLen = EXPECTED_ORIGSIZE; + auto *outBuf = (uint8_t *)malloc(EXPECTED_ORIGSIZE); + + auto err = AARU_dd_ddn_decode_buffer(outBuf, &destLen, ddn2_buffer, DDN2_COMPRESSED_SIZE); + + EXPECT_EQ(err, 0); + EXPECT_EQ(destLen, (size_t)EXPECTED_ORIGSIZE); + + auto crc = crc32_data(outBuf, EXPECTED_ORIGSIZE); + free(outBuf); + EXPECT_EQ(crc, EXPECTED_CRC32); +} + +/* ---- DDn method 10 (DD3 - best) ---- */ + +#define DDN3_COMPRESSED_SIZE 52881 + +static const uint8_t *ddn3_buffer; + +class DdDdn3Fixture : public ::testing::Test +{ +protected: + void SetUp() + { + char path[PATH_MAX]; + char filename[PATH_MAX]; + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/dd_dd3.bin", path); + FILE *file = fopen(filename, "rb"); + ddn3_buffer = (const uint8_t *)malloc(DDN3_COMPRESSED_SIZE); + fread((void *)ddn3_buffer, 1, DDN3_COMPRESSED_SIZE, file); + fclose(file); + } + + void TearDown() { free((void *)ddn3_buffer); } +}; + +TEST_F(DdDdn3Fixture, dd_ddn3) +{ + size_t destLen = EXPECTED_ORIGSIZE; + auto *outBuf = (uint8_t *)malloc(EXPECTED_ORIGSIZE); + + auto err = AARU_dd_ddn_decode_buffer(outBuf, &destLen, ddn3_buffer, DDN3_COMPRESSED_SIZE); + + EXPECT_EQ(err, 0); + EXPECT_EQ(destLen, (size_t)EXPECTED_ORIGSIZE); + + auto crc = crc32_data(outBuf, EXPECTED_ORIGSIZE); + free(outBuf); + EXPECT_EQ(crc, EXPECTED_CRC32); +}