diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f31f5c..513c36a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,7 +131,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) add_subdirectory(3rdparty) -add_library("Aaru.Compression.Native" SHARED library.c apple_rle.c apple_rle.h adc.c adc.h lzip.c flac.c flac.h +add_library("Aaru.Compression.Native" SHARED library.c apple_rle.c apple_rle.h apple_lzh.c apple_lzh.h adc.c adc.h lzip.c flac.c flac.h zoo/lzd.c zoo/lzd.h zoo/lzh.c zoo/decode.c zoo/huf.c zoo/io.c zoo/lh5.c zoo/lh5.h zoo/lzh.h zoo/ar.h zoo/maketbl.c arc/pack.c arc/squeeze.c arc/crunch.c arc/lzw.c pak/crush.c pak/distill.c pak/bitstream.c pak/bitstream.h pak/lzw.c pak/lzw.h pak/prefixcode.c diff --git a/apple_lzh.c b/apple_lzh.c new file mode 100644 index 0000000..b88cfd0 --- /dev/null +++ b/apple_lzh.c @@ -0,0 +1,280 @@ +/* + * 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 . + */ + +/* + * Apple LZH decompression — used by DART disk image format "best" compression. + * + * This is identical to LHA -lh1- (adaptive Huffman + LZSS, 4KB window) except + * the LZSS window is pre-filled with zeros in the last 110 bytes rather than + * spaces (0x20). The standard LH1 format uses spaces as specified by Okumura's + * original LZHUF.C; Apple's DART tool uses zeros instead. + * + * Reference: XADLZHDynamicHandle from The Unarchiver with prefill:NO + */ + +#include "apple_lzh.h" +#include "lha/bitio.h" +#include "lha/huffman.h" +#include "lha/lzss.h" + +#include + +#define APPLE_LZH_NUM_LEAVES 314 +#define APPLE_LZH_NUM_NODES (APPLE_LZH_NUM_LEAVES * 2 - 1) + +typedef struct apple_lzh_node +{ + struct apple_lzh_node *parent; + struct apple_lzh_node *leftchild; + struct apple_lzh_node *rightchild; + int index; + int freq; + int value; +} apple_lzh_node; + +typedef struct +{ + apple_lzh_node storage[APPLE_LZH_NUM_NODES]; + apple_lzh_node *nodes[APPLE_LZH_NUM_NODES]; +} apple_lzh_tree; + +static void apple_lzh_init_tree(apple_lzh_tree *tree) +{ + int i; + + memset(tree->storage, 0, sizeof(tree->storage)); + + for(i = 0; i < APPLE_LZH_NUM_NODES; i++) tree->nodes[i] = &tree->storage[i]; + + for(i = 0; i < APPLE_LZH_NUM_LEAVES; i++) + { + int index = APPLE_LZH_NUM_NODES - 1 - i; + tree->nodes[index]->index = index; + tree->nodes[index]->freq = 1; + tree->nodes[index]->value = i; + tree->nodes[index]->leftchild = NULL; + tree->nodes[index]->rightchild = NULL; + } + + for(i = APPLE_LZH_NUM_LEAVES - 2; i >= 0; i--) + { + tree->nodes[i]->index = i; + tree->nodes[i]->leftchild = tree->nodes[2 * i + 1]; + tree->nodes[i]->rightchild = tree->nodes[2 * i + 2]; + tree->nodes[i]->leftchild->parent = tree->nodes[i]; + tree->nodes[i]->rightchild->parent = tree->nodes[i]; + tree->nodes[i]->freq = tree->nodes[i]->leftchild->freq + tree->nodes[i]->rightchild->freq; + } + + tree->nodes[0]->parent = NULL; +} + +static void apple_lzh_rearrange(apple_lzh_tree *tree, apple_lzh_node *node) +{ + int p_index = node->index; + int q_index = p_index; + apple_lzh_node *q; + + while(q_index > 0 && tree->nodes[q_index - 1]->freq < node->freq) q_index--; + + if(q_index >= p_index) return; + + q = tree->nodes[q_index]; + + { + apple_lzh_node *new_q_parent = node->parent; + apple_lzh_node *new_p_parent = q->parent; + int p_is_right = (node->parent && node->parent->rightchild == node); + int q_is_right = (q->parent && q->parent->rightchild == q); + + if(node->parent) + { + if(p_is_right) + node->parent->rightchild = q; + else + node->parent->leftchild = q; + } + + if(q->parent) + { + if(q_is_right) + q->parent->rightchild = node; + else + q->parent->leftchild = node; + } + + node->parent = new_p_parent; + q->parent = new_q_parent; + } + + tree->nodes[p_index] = q; + tree->nodes[p_index]->index = p_index; + tree->nodes[q_index] = node; + tree->nodes[q_index]->index = q_index; +} + +static void apple_lzh_reconstruct(apple_lzh_tree *tree) +{ + apple_lzh_node *leaves[APPLE_LZH_NUM_LEAVES]; + int n = 0; + int i; + int leaf_index, branch_index, node_index, pair_index; + + for(i = 0; i < APPLE_LZH_NUM_NODES; i++) + { + if(!tree->nodes[i]->leftchild && !tree->nodes[i]->rightchild) + { + tree->nodes[i]->freq = (tree->nodes[i]->freq + 1) / 2; + leaves[n++] = tree->nodes[i]; + } + } + + leaf_index = APPLE_LZH_NUM_LEAVES - 1; + branch_index = APPLE_LZH_NUM_LEAVES - 2; + node_index = APPLE_LZH_NUM_NODES - 1; + pair_index = APPLE_LZH_NUM_NODES - 2; + + while(node_index >= 0) + { + while(node_index >= pair_index) + { + tree->nodes[node_index] = leaves[leaf_index]; + tree->nodes[node_index]->index = node_index; + node_index--; + leaf_index--; + } + + { + apple_lzh_node *branch = &tree->storage[branch_index--]; + branch->leftchild = tree->nodes[pair_index]; + branch->rightchild = tree->nodes[pair_index + 1]; + branch->leftchild->parent = branch; + branch->rightchild->parent = branch; + branch->freq = tree->nodes[pair_index]->freq + tree->nodes[pair_index + 1]->freq; + + while(leaf_index >= 0 && leaves[leaf_index]->freq <= branch->freq) + { + tree->nodes[node_index] = leaves[leaf_index]; + tree->nodes[node_index]->index = node_index; + node_index--; + leaf_index--; + } + + tree->nodes[node_index] = branch; + tree->nodes[node_index]->index = node_index; + node_index--; + pair_index -= 2; + } + } + + tree->nodes[0]->parent = NULL; +} + +static void apple_lzh_update(apple_lzh_tree *tree, apple_lzh_node *node) +{ + if(tree->nodes[0]->freq == 0x8000) apple_lzh_reconstruct(tree); + + for(;;) + { + node->freq++; + + if(!node->parent) break; + + apple_lzh_rearrange(tree, node); + node = node->parent; + } +} + +AARU_EXPORT int AARU_CALL AARU_apple_lzh_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, + size_t src_size) +{ + lha_bitio bitio; + lha_lzss lzss; + apple_lzh_tree tree; + lha_prefix_code *distancecode; + size_t expected; + int i; + + static const int dist_lengths[64] = {3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8}; + + if(!dst_buffer || !dst_size || !src_buffer || src_size == 0) return -1; + + expected = *dst_size; + + if(!lha_lzss_init(&lzss, 4096, dst_buffer, expected)) return -1; + + /* Pre-fill window with pattern identical to LH1... */ + for(i = 0; i < 256; i++) memset(&lzss.window[i * 13 + 18], i, 13); + + for(i = 0; i < 256; i++) lzss.window[256 * 13 + 18 + i] = (uint8_t)i; + + for(i = 0; i < 256; i++) lzss.window[256 * 13 + 256 + 18 + i] = (uint8_t)(255 - i); + + memset(&lzss.window[256 * 13 + 512 + 18], 0, 128); + + /* Apple variant: zeros instead of spaces for last 110 bytes */ + memset(&lzss.window[256 * 13 + 512 + 128 + 18], 0, 128 - 18); + + distancecode = lha_prefix_code_from_lengths(dist_lengths, 64, 8, true); + + if(!distancecode) + { + lha_lzss_cleanup(&lzss); + return -1; + } + + lha_bitio_init(&bitio, src_buffer, src_size); + apple_lzh_init_tree(&tree); + + while(lzss.out_pos < expected && !lha_bitio_at_eof(&bitio)) + { + apple_lzh_node *node = tree.nodes[0]; + + while(node->leftchild || node->rightchild) + { + if(lha_bitio_next_bit(&bitio)) + node = node->leftchild; + else + node = node->rightchild; + + if(!node) break; + } + + if(!node) break; + + apple_lzh_update(&tree, node); + + if(node->value < 0x100) { lha_lzss_emit_literal(&lzss, (uint8_t)node->value); } + else + { + int length = node->value - 0x100 + 3; + int highbits = lha_prefix_code_decode(&bitio, distancecode); + int lowbits = (int)lha_bitio_next_bits(&bitio, 6); + int offset = (highbits << 6) + lowbits + 1; + + lha_lzss_emit_match(&lzss, offset, length); + } + } + + *dst_size = lzss.out_pos; + lha_prefix_code_free(distancecode); + lha_lzss_cleanup(&lzss); + return 0; +} diff --git a/apple_lzh.h b/apple_lzh.h new file mode 100644 index 0000000..fd32b8f --- /dev/null +++ b/apple_lzh.h @@ -0,0 +1,29 @@ +/* + * 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_COMPRESSION_NATIVE__APPLE_LZH_H_ +#define AARU_COMPRESSION_NATIVE__APPLE_LZH_H_ + +#include +#include +#include "library.h" + +AARU_EXPORT int AARU_CALL AARU_apple_lzh_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, + size_t src_size); + +#endif /* AARU_COMPRESSION_NATIVE__APPLE_LZH_H_ */ diff --git a/library.h b/library.h index 86973d5..c9bb2e3 100644 --- a/library.h +++ b/library.h @@ -60,6 +60,10 @@ AARU_EXPORT int32_t AARU_CALL AARU_adc_decode_buffer(uint8_t *dst_buffer, int32_ AARU_EXPORT int32_t AARU_CALL AARU_apple_rle_decode_buffer(uint8_t *dst_buffer, int32_t dst_size, const uint8_t *src_buffer, int32_t src_size); +// Apple LZH decompression (DART "best" mode: LH1 variant with zero-filled window tail) +AARU_EXPORT int AARU_CALL AARU_apple_lzh_decode_buffer(uint8_t *dst_buffer, size_t *dst_size, const uint8_t *src_buffer, + size_t src_size); + AARU_EXPORT size_t AARU_CALL AARU_flac_decode_redbook_buffer(uint8_t *dst_buffer, size_t dst_size, const uint8_t *src_buffer, size_t src_size); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index eddae02..fa6e371 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -195,6 +195,30 @@ file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/stuffitx_deflate.bin file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/stuffitx_blend.bin DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/mf2dd_hfs_fast.dart.lz + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/mf2dd_hfs_best.dart.lz + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/mf1dd_hfs_fast.dart.lz + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/mf1dd_hfs_best.dart.lz + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/mf2dd_mfs_fast.dart.lz + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/mf2dd_mfs_best.dart.lz + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/mf1dd_mfs_fast.dart.lz + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/mf1dd_mfs_best.dart.lz + 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 @@ -208,5 +232,6 @@ add_executable(tests_run apple_rle.cpp crc32.c crc32.h adc.cpp bzip2.cpp lzip.cp rar/rar.cpp cpt/cpt.cpp dd/dd.cpp - stuffit/stuffit.cpp) + stuffit/stuffit.cpp + dart/dart.cpp) target_link_libraries(tests_run gtest gtest_main "Aaru.Compression.Native") diff --git a/tests/dart/dart.cpp b/tests/dart/dart.cpp new file mode 100644 index 0000000..9f38c28 --- /dev/null +++ b/tests/dart/dart.cpp @@ -0,0 +1,244 @@ +/* + * 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 "dart_helpers.h" +#include "gtest/gtest.h" + +typedef struct +{ + uint8_t *data; + size_t total_size; + dart_header_t header; + int16_t block_lengths[DART_BLOCK_ARRAY_LEN_HIGH]; + int num_blocks; + uint8_t *raw; + size_t raw_size; + size_t data_offset; +} dart_image_t; + +static size_t read_file_to_buffer(const char *path, uint8_t **out) +{ + FILE *f = fopen(path, "rb"); + if(!f) return 0; + fseek(f, 0, SEEK_END); + long sz = ftell(f); + fseek(f, 0, SEEK_SET); + if(sz <= 0) + { + fclose(f); + return 0; + } + *out = (uint8_t *)malloc(sz); + if(!*out) + { + fclose(f); + return 0; + } + size_t rd = fread(*out, 1, sz, f); + fclose(f); + if(rd != (size_t)sz) + { + free(*out); + *out = NULL; + return 0; + } + return (size_t)sz; +} + +static size_t lzip_decompress_file(const char *path, uint8_t **out) +{ + uint8_t *lz_buf = NULL; + size_t lz_size = read_file_to_buffer(path, &lz_buf); + if(lz_size == 0) return 0; + + size_t try_sizes[] = {512 * 1024, 1024 * 1024, 2 * 1024 * 1024}; + for(int i = 0; i < 3; i++) + { + *out = (uint8_t *)malloc(try_sizes[i]); + int32_t result = AARU_lzip_decode_buffer(*out, (int32_t)try_sizes[i], lz_buf, (int32_t)lz_size); + if(result > 0) + { + free(lz_buf); + return (size_t)result; + } + free(*out); + *out = NULL; + } + free(lz_buf); + return 0; +} + +static bool load_dart_image(const char *path, dart_image_t *img) +{ + memset(img, 0, sizeof(*img)); + + img->raw_size = lzip_decompress_file(path, &img->raw); + if(img->raw_size == 0) return false; + + img->data_offset = dart_parse_header(img->raw, img->raw_size, &img->header, img->block_lengths, &img->num_blocks); + if(img->data_offset == 0) + { + free(img->raw); + img->raw = NULL; + return false; + } + + int active_blocks = 0; + for(int i = 0; i < img->num_blocks; i++) + { + if(img->block_lengths[i] != 0) active_blocks++; + } + img->total_size = active_blocks * DART_BUFFER_SIZE; + + if(img->header.srcCmp == DART_COMPRESS_RLE) + { + img->data = (uint8_t *)malloc(img->total_size); + size_t out_pos = 0; + size_t in_pos = img->data_offset; + + for(int i = 0; i < img->num_blocks; i++) + { + if(img->block_lengths[i] == 0) continue; + + if(img->block_lengths[i] == -1) + { + if(in_pos + DART_BUFFER_SIZE <= img->raw_size) + memcpy(img->data + out_pos, img->raw + in_pos, DART_BUFFER_SIZE); + in_pos += DART_BUFFER_SIZE; + } + else + { + size_t comp_size = (size_t)img->block_lengths[i] * 2; + if(in_pos + comp_size <= img->raw_size) + { + AARU_apple_rle_decode_buffer(img->data + out_pos, DART_BUFFER_SIZE, img->raw + in_pos, + (int32_t)comp_size); + } + in_pos += comp_size; + } + out_pos += DART_BUFFER_SIZE; + } + } + + return true; +} + +static void free_dart_image(dart_image_t *img) +{ + if(img->data) free(img->data); + if(img->raw) free(img->raw); + memset(img, 0, sizeof(*img)); +} + +static bool verify_dart_pair(const char *fast_name, const char *best_name) +{ + char path[PATH_MAX]; + char fast_path[PATH_MAX]; + char best_path[PATH_MAX]; + + getcwd(path, PATH_MAX); + snprintf(fast_path, PATH_MAX, "%s/data/%s", path, fast_name); + snprintf(best_path, PATH_MAX, "%s/data/%s", path, best_name); + + dart_image_t fast_img, best_img; + + if(!load_dart_image(fast_path, &fast_img)) + { + printf(" SKIP: cannot load %s\n", fast_name); + return false; + } + if(!load_dart_image(best_path, &best_img)) + { + printf(" SKIP: cannot load %s\n", best_name); + free_dart_image(&fast_img); + return false; + } + + if(fast_img.header.srcCmp != DART_COMPRESS_RLE || best_img.header.srcCmp != DART_COMPRESS_LZH) + { + printf(" SKIP: unexpected compression types\n"); + free_dart_image(&fast_img); + free_dart_image(&best_img); + return false; + } + + size_t in_pos = best_img.data_offset; + size_t ref_block_idx = 0; + int blocks_ok = 0; + int blocks_fail = 0; + + for(int i = 0; i < best_img.num_blocks; i++) + { + if(best_img.block_lengths[i] == 0) + { + if(fast_img.block_lengths[i] != 0) ref_block_idx++; + continue; + } + + const uint8_t *reference = fast_img.data + ref_block_idx * DART_BUFFER_SIZE; + + if(best_img.block_lengths[i] == -1) + { + if(memcmp(best_img.raw + in_pos, reference, DART_BUFFER_SIZE) == 0) + blocks_ok++; + else + blocks_fail++; + in_pos += DART_BUFFER_SIZE; + } + else + { + size_t comp_size = (size_t)best_img.block_lengths[i]; + uint8_t out[DART_BUFFER_SIZE]; + size_t out_len = DART_BUFFER_SIZE; + + memset(out, 0, DART_BUFFER_SIZE); + + int ret = AARU_apple_lzh_decode_buffer(out, &out_len, best_img.raw + in_pos, comp_size); + if(ret == 0 && out_len == DART_BUFFER_SIZE && memcmp(out, reference, DART_BUFFER_SIZE) == 0) + blocks_ok++; + else + blocks_fail++; + in_pos += comp_size; + } + ref_block_idx++; + } + + printf(" %s: %d OK, %d FAIL\n", best_name, blocks_ok, blocks_fail); + + free_dart_image(&fast_img); + free_dart_image(&best_img); + + return blocks_fail == 0 && blocks_ok > 0; +} + +TEST(DartAppleLzh, mf2dd_hfs) { EXPECT_TRUE(verify_dart_pair("mf2dd_hfs_fast.dart.lz", "mf2dd_hfs_best.dart.lz")); } + +TEST(DartAppleLzh, mf2dd_mfs) { EXPECT_TRUE(verify_dart_pair("mf2dd_mfs_fast.dart.lz", "mf2dd_mfs_best.dart.lz")); } + +TEST(DartAppleLzh, mf1dd_hfs) { EXPECT_TRUE(verify_dart_pair("mf1dd_hfs_fast.dart.lz", "mf1dd_hfs_best.dart.lz")); } + +TEST(DartAppleLzh, mf1dd_mfs) { EXPECT_TRUE(verify_dart_pair("mf1dd_mfs_fast.dart.lz", "mf1dd_mfs_best.dart.lz")); } diff --git a/tests/dart/dart_helpers.h b/tests/dart/dart_helpers.h new file mode 100644 index 0000000..613fc98 --- /dev/null +++ b/tests/dart/dart_helpers.h @@ -0,0 +1,99 @@ +/* + * 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_TESTS_DART_HELPERS_H +#define AARU_TESTS_DART_HELPERS_H + +#include +#include + +// DART header constants +#define DART_COMPRESS_RLE 0 +#define DART_COMPRESS_LZH 1 +#define DART_COMPRESS_NONE 2 + +#define DART_SECTORS_PER_BLOCK 40 +#define DART_SECTOR_SIZE 512 +#define DART_TAG_SECTOR_SIZE 12 +#define DART_DATA_SIZE (DART_SECTORS_PER_BLOCK * DART_SECTOR_SIZE) // 20480 +#define DART_TAG_SIZE (DART_SECTORS_PER_BLOCK * DART_TAG_SECTOR_SIZE) // 480 +#define DART_BUFFER_SIZE (DART_DATA_SIZE + DART_TAG_SIZE) // 20960 + +#define DART_BLOCK_ARRAY_LEN_LOW 40 +#define DART_BLOCK_ARRAY_LEN_HIGH 72 + +#define DART_HEADER_SIZE 4 + +// DART disk types +#define DART_TYPE_MAC 1 +#define DART_TYPE_LISA 2 +#define DART_TYPE_APPLE2 3 +#define DART_TYPE_MAC_HD 16 +#define DART_TYPE_DOS 17 +#define DART_TYPE_DOS_HD 18 + +#pragma pack(push, 1) + +typedef struct +{ + uint8_t srcCmp; // Compression type (0=RLE, 1=LZH, 2=None) + uint8_t srcType; // Disk type + uint16_t srcSize; // Size in blocks (big-endian) +} dart_header_t; + +#pragma pack(pop) + +static inline uint16_t be16(const uint8_t *p) { return (uint16_t)((p[0] << 8) | p[1]); } + +static inline int16_t be16s(const uint8_t *p) { return (int16_t)((p[0] << 8) | p[1]); } + +// Returns the number of block length entries based on disk type +static inline int dart_block_array_len(uint8_t srcType) +{ + switch(srcType) + { + case DART_TYPE_MAC_HD: + case DART_TYPE_DOS_HD: + return DART_BLOCK_ARRAY_LEN_HIGH; + default: + return DART_BLOCK_ARRAY_LEN_LOW; + } +} + +// Parse DART header from raw data. Returns the offset past the block lengths array. +static inline size_t dart_parse_header(const uint8_t *data, size_t data_len, dart_header_t *hdr, int16_t *block_lengths, + int *num_blocks) +{ + if(data_len < DART_HEADER_SIZE) return 0; + + hdr->srcCmp = data[0]; + hdr->srcType = data[1]; + hdr->srcSize = be16(data + 2); + + *num_blocks = dart_block_array_len(hdr->srcType); + size_t needed = DART_HEADER_SIZE + (*num_blocks) * 2; + + if(data_len < needed) return 0; + + const uint8_t *blk_ptr = data + DART_HEADER_SIZE; + for(int i = 0; i < *num_blocks; i++) { block_lengths[i] = be16s(blk_ptr + i * 2); } + + return needed; +} + +#endif // AARU_TESTS_DART_HELPERS_H diff --git a/tests/data/mf1dd_hfs_best.dart.lz b/tests/data/mf1dd_hfs_best.dart.lz new file mode 100644 index 0000000..9a36d64 Binary files /dev/null and b/tests/data/mf1dd_hfs_best.dart.lz differ diff --git a/tests/data/mf1dd_hfs_fast.dart.lz b/tests/data/mf1dd_hfs_fast.dart.lz new file mode 100644 index 0000000..9b897c6 Binary files /dev/null and b/tests/data/mf1dd_hfs_fast.dart.lz differ diff --git a/tests/data/mf1dd_mfs_best.dart.lz b/tests/data/mf1dd_mfs_best.dart.lz new file mode 100644 index 0000000..b3f995f Binary files /dev/null and b/tests/data/mf1dd_mfs_best.dart.lz differ diff --git a/tests/data/mf1dd_mfs_fast.dart.lz b/tests/data/mf1dd_mfs_fast.dart.lz new file mode 100644 index 0000000..517e1f3 Binary files /dev/null and b/tests/data/mf1dd_mfs_fast.dart.lz differ diff --git a/tests/data/mf2dd_hfs_best.dart.lz b/tests/data/mf2dd_hfs_best.dart.lz new file mode 100644 index 0000000..83be36c Binary files /dev/null and b/tests/data/mf2dd_hfs_best.dart.lz differ diff --git a/tests/data/mf2dd_hfs_fast.dart.lz b/tests/data/mf2dd_hfs_fast.dart.lz new file mode 100644 index 0000000..8f72480 Binary files /dev/null and b/tests/data/mf2dd_hfs_fast.dart.lz differ diff --git a/tests/data/mf2dd_mfs_best.dart.lz b/tests/data/mf2dd_mfs_best.dart.lz new file mode 100644 index 0000000..8993c21 Binary files /dev/null and b/tests/data/mf2dd_mfs_best.dart.lz differ diff --git a/tests/data/mf2dd_mfs_fast.dart.lz b/tests/data/mf2dd_mfs_fast.dart.lz new file mode 100644 index 0000000..fed435d Binary files /dev/null and b/tests/data/mf2dd_mfs_fast.dart.lz differ diff --git a/tests/tests_run b/tests/tests_run new file mode 100755 index 0000000..4d4b4af Binary files /dev/null and b/tests/tests_run differ