Add DiskDoubler decompression support

- Introduced dd.h header file with function declarations for various DiskDoubler decompression methods including ADn, DDn, Method 2, Stac LZS, Compact Pro, and LZW.
- Implemented wrapper functions in library.c to expose DiskDoubler decompression methods to the Aaru API.
- Updated library.h to declare the new DiskDoubler functions.
- Added test cases for DiskDoubler decompression methods in dd.cpp, covering ADn and DDn methods with corresponding binary test data.
- Included necessary test data files for ADn and DDn methods in the tests/data directory.
This commit is contained in:
2026-04-16 23:19:47 +01:00
parent e3e6a874fd
commit 7a6c85b44f
12 changed files with 1469 additions and 2 deletions

View File

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

1032
dd/dd.c Normal file

File diff suppressed because it is too large Load Diff

140
dd/dd.h Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef AARU_DD_H
#define AARU_DD_H
#include <stddef.h>
#include <stdint.h>
/**
* 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 */

View File

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

View File

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

View File

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

BIN
tests/data/dd_ad1.bin Normal file

Binary file not shown.

BIN
tests/data/dd_ad2.bin Normal file

Binary file not shown.

BIN
tests/data/dd_dd1.bin Normal file

Binary file not shown.

BIN
tests/data/dd_dd2.bin Normal file

Binary file not shown.

BIN
tests/data/dd_dd3.bin Normal file

Binary file not shown.

226
tests/dd/dd.cpp Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <unistd.h>
#include <climits>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#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);
}