diff --git a/CMakeLists.txt b/CMakeLists.txt index 2cd2cbc..87ceb68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,7 +145,23 @@ add_library("Aaru.Compression.Native" SHARED library.c apple_rle.c apple_rle.h a ha/hsc.h ha/internal.h ha/swdict.c - ha/swdict.h) + ha/swdict.h + lha/bitio.c + lha/bitio.h + lha/lzss.c + lha/lzss.h + lha/huffman.c + lha/huffman.h + lha/larc.c + lha/larc.h + lha/lh1.c + lha/lh1.h + lha/lh_static.c + lha/lh_static.h + lha/lh_old.c + lha/lh_old.h + lha/pmarc1.c + lha/pmarc1.h) include(3rdparty/bzip2.cmake) include(3rdparty/flac.cmake) diff --git a/lha/bitio.c b/lha/bitio.c new file mode 100644 index 0000000..22cfe7c --- /dev/null +++ b/lha/bitio.c @@ -0,0 +1,98 @@ +/* + * 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 "bitio.h" + +#include + +void lha_bitio_init(lha_bitio *self, const uint8_t *buffer, size_t length) +{ + self->buffer = buffer; + self->length = length; + self->pos = 0; + self->bits = 0; + self->numbits = 0; + self->eof = false; +} + +static void lha_bitio_fill(lha_bitio *self) +{ + while(self->numbits <= 24 && self->pos < self->length) + { + self->bits |= (uint32_t)self->buffer[self->pos++] << (24 - self->numbits); + self->numbits += 8; + } + + if(self->pos >= self->length && self->numbits == 0) self->eof = true; +} + +bool lha_bitio_at_eof(lha_bitio *self) { return self->eof && self->numbits == 0; } + +unsigned int lha_bitio_next_bit(lha_bitio *self) +{ + if(self->numbits == 0) lha_bitio_fill(self); + + if(self->numbits == 0) return 0; + + unsigned int bit = (self->bits >> 31) & 1; + self->bits <<= 1; + self->numbits--; + return bit; +} + +unsigned int lha_bitio_next_bits(lha_bitio *self, int numbits) +{ + if(numbits == 0) return 0; + + if((int)self->numbits < numbits) lha_bitio_fill(self); + + unsigned int result = self->bits >> (32 - numbits); + self->bits <<= numbits; + self->numbits -= numbits; + return result; +} + +unsigned int lha_bitio_peek_bits(lha_bitio *self, int numbits) +{ + if(numbits == 0) return 0; + + if((int)self->numbits < numbits) lha_bitio_fill(self); + + return self->bits >> (32 - numbits); +} + +void lha_bitio_skip_bits(lha_bitio *self, int numbits) +{ + self->bits <<= numbits; + self->numbits -= numbits; +} + +int lha_bitio_next_byte(lha_bitio *self) +{ + if(self->pos >= self->length && self->numbits < 8) return -1; + + /* If on byte boundary, read directly */ + if(self->numbits == 0) + { + if(self->pos < self->length) return self->buffer[self->pos++]; + + return -1; + } + + return (int)lha_bitio_next_bits(self, 8); +} diff --git a/lha/bitio.h b/lha/bitio.h new file mode 100644 index 0000000..86c6ad6 --- /dev/null +++ b/lha/bitio.h @@ -0,0 +1,44 @@ +/* + * 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__LHA_BITIO_H_ +#define AARU_COMPRESSION_NATIVE__LHA_BITIO_H_ + +#include +#include +#include + +typedef struct lha_bitio +{ + const uint8_t *buffer; + size_t length; + size_t pos; + uint32_t bits; + unsigned int numbits; + bool eof; +} lha_bitio; + +void lha_bitio_init(lha_bitio *self, const uint8_t *buffer, size_t length); +bool lha_bitio_at_eof(lha_bitio *self); +unsigned int lha_bitio_next_bit(lha_bitio *self); +unsigned int lha_bitio_next_bits(lha_bitio *self, int numbits); +unsigned int lha_bitio_peek_bits(lha_bitio *self, int numbits); +void lha_bitio_skip_bits(lha_bitio *self, int numbits); +int lha_bitio_next_byte(lha_bitio *self); + +#endif /* AARU_COMPRESSION_NATIVE__LHA_BITIO_H_ */ diff --git a/lha/huffman.c b/lha/huffman.c new file mode 100644 index 0000000..8d05238 --- /dev/null +++ b/lha/huffman.c @@ -0,0 +1,259 @@ +/* + * 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 "huffman.h" + +#include +#include +#include + +/* --- Internal helpers --- */ + +static inline int left_branch(lha_prefix_code *c, int node) { return c->tree[node].branches[0]; } + +static inline int right_branch(lha_prefix_code *c, int node) { return c->tree[node].branches[1]; } + +static inline void set_branch(lha_prefix_code *c, int node, int bit, int next) +{ + c->tree[node].branches[bit] = next; +} + +static inline bool is_leaf(lha_prefix_code *c, int node) +{ + return c->tree[node].branches[0] == c->tree[node].branches[1]; +} + +static inline int leaf_value(lha_prefix_code *c, int node) { return left_branch(c, node); } + +static inline void set_leaf(lha_prefix_code *c, int node, int value) +{ + c->tree[node].branches[0] = value; + c->tree[node].branches[1] = value; +} + +static inline void set_empty(lha_prefix_code *c, int node) +{ + c->tree[node].branches[0] = -1; + c->tree[node].branches[1] = -2; +} + +static inline bool is_empty(lha_prefix_code *c, int node) +{ + return c->tree[node].branches[0] == -1 && c->tree[node].branches[1] == -2; +} + +static inline bool is_open(lha_prefix_code *c, int node, int bit) { return c->tree[node].branches[bit] < 0; } + +static int new_node(lha_prefix_code *c) +{ + lha_tree_node *tmp = (lha_tree_node *)realloc(c->tree, (size_t)(c->numentries + 1) * sizeof(lha_tree_node)); + + if(!tmp) return -1; + + c->tree = tmp; + set_empty(c, c->numentries); + return c->numentries++; +} + +/* --- Build lookup table --- */ + +static void make_table(lha_prefix_code *code, int node, lha_table_entry *table, int depth, int maxdepth) +{ + int currtablesize = 1 << (maxdepth - depth); + + if(node < 0) + { + int i; + for(i = 0; i < currtablesize; i++) table[i].length = (uint32_t)-1; + } + else if(is_leaf(code, node)) + { + int i; + for(i = 0; i < currtablesize; i++) + { + table[i].length = (uint32_t)depth; + table[i].value = leaf_value(code, node); + } + } + else + { + if(depth == maxdepth) + { + table[0].length = (uint32_t)(maxdepth + 1); + table[0].value = node; + } + else + { + make_table(code, left_branch(code, node), table, depth + 1, maxdepth); + make_table(code, right_branch(code, node), table + currtablesize / 2, depth + 1, maxdepth); + } + } +} + +static void build_table(lha_prefix_code *code) +{ + if(code->table) return; + + if(code->maxlength < code->minlength) + code->tablesize = LHA_HUFFMAN_TABLE_SIZE; + else if(code->maxlength >= LHA_HUFFMAN_TABLE_SIZE) + code->tablesize = LHA_HUFFMAN_TABLE_SIZE; + else + code->tablesize = code->maxlength; + + code->table = (lha_table_entry *)malloc(sizeof(lha_table_entry) * (size_t)(1 << code->tablesize)); + + if(code->table) make_table(code, 0, code->table, 0, code->tablesize); +} + +/* --- Public API --- */ + +lha_prefix_code *lha_prefix_code_new(void) +{ + lha_prefix_code *code = (lha_prefix_code *)calloc(1, sizeof(lha_prefix_code)); + + if(!code) return NULL; + + code->tree = (lha_tree_node *)malloc(sizeof(lha_tree_node)); + + if(!code->tree) + { + free(code); + return NULL; + } + + set_empty(code, 0); + code->numentries = 1; + code->minlength = INT_MAX; + code->maxlength = INT_MIN; + code->table = NULL; + code->tablesize = 0; + + return code; +} + +lha_prefix_code *lha_prefix_code_from_lengths(const int *lengths, int num_symbols, int max_length, + bool shortest_is_zeros) +{ + lha_prefix_code *code = lha_prefix_code_new(); + + if(!code) return NULL; + + int codeval = 0; + int symbolsleft = num_symbols; + int length, i; + + for(length = 1; length <= max_length; length++) + { + for(i = 0; i < num_symbols; i++) + { + if(lengths[i] != length) continue; + + if(shortest_is_zeros) + lha_prefix_code_add(code, i, (uint32_t)codeval, length); + else + lha_prefix_code_add(code, i, (uint32_t)~codeval, length); + + codeval++; + + if(--symbolsleft == 0) return code; + } + + codeval <<= 1; + } + + return code; +} + +void lha_prefix_code_free(lha_prefix_code *code) +{ + if(!code) return; + + free(code->tree); + free(code->table); + free(code); +} + +void lha_prefix_code_add(lha_prefix_code *code, int value, uint32_t codebits, int length) +{ + int bitpos, lastnode; + + /* Invalidate lookup table */ + free(code->table); + code->table = NULL; + + if(length > code->maxlength) code->maxlength = length; + if(length < code->minlength) code->minlength = length; + + lastnode = 0; + + for(bitpos = length - 1; bitpos >= 0; bitpos--) + { + int bit = (codebits >> bitpos) & 1; + + if(is_leaf(code, lastnode)) return; /* prefix collision */ + + if(is_open(code, lastnode, bit)) set_branch(code, lastnode, bit, new_node(code)); + + lastnode = code->tree[lastnode].branches[bit]; + + if(lastnode < 0) return; /* allocation failure */ + } + + if(!is_empty(code, lastnode)) return; /* prefix collision */ + + set_leaf(code, lastnode, value); +} + +int lha_prefix_code_decode(lha_bitio *bitio, lha_prefix_code *code) +{ + int bits, length, value, node; + + if(!code) return -1; + + build_table(code); + + if(!code->table) return -1; + + bits = (int)lha_bitio_peek_bits(bitio, code->tablesize); + length = (int)code->table[bits].length; + value = code->table[bits].value; + + if(length == (int)(uint32_t)-1) return -1; /* invalid code */ + + if(length <= code->tablesize) + { + lha_bitio_skip_bits(bitio, length); + return value; + } + + lha_bitio_skip_bits(bitio, code->tablesize); + + node = value; + + while(!is_leaf(code, node)) + { + int bit = (int)lha_bitio_next_bit(bitio); + + if(is_open(code, node, bit)) return -1; + + node = code->tree[node].branches[bit]; + } + + return leaf_value(code, node); +} diff --git a/lha/huffman.h b/lha/huffman.h new file mode 100644 index 0000000..c4f3a3e --- /dev/null +++ b/lha/huffman.h @@ -0,0 +1,57 @@ +/* + * 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__LHA_HUFFMAN_H_ +#define AARU_COMPRESSION_NATIVE__LHA_HUFFMAN_H_ + +#include +#include +#include "bitio.h" + +#define LHA_HUFFMAN_TABLE_SIZE 10 + +typedef struct +{ + int branches[2]; +} lha_tree_node; + +typedef struct +{ + uint32_t length; + int32_t value; +} lha_table_entry; + +typedef struct lha_prefix_code +{ + lha_tree_node *tree; + int numentries; + int minlength; + int maxlength; + + int tablesize; + lha_table_entry *table; +} lha_prefix_code; + +lha_prefix_code *lha_prefix_code_new(void); +lha_prefix_code *lha_prefix_code_from_lengths(const int *lengths, int num_symbols, int max_length, + bool shortest_is_zeros); +void lha_prefix_code_free(lha_prefix_code *code); +void lha_prefix_code_add(lha_prefix_code *code, int value, uint32_t codebits, int length); +int lha_prefix_code_decode(lha_bitio *bitio, lha_prefix_code *code); + +#endif /* AARU_COMPRESSION_NATIVE__LHA_HUFFMAN_H_ */ diff --git a/lha/larc.c b/lha/larc.c new file mode 100644 index 0000000..1573905 --- /dev/null +++ b/lha/larc.c @@ -0,0 +1,134 @@ +/* + * 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 "larc.h" +#include "bitio.h" +#include "lzss.h" + +#include + +/* + * LArc -lzs- decompression. + * Window: 2048 bytes. + * Bit stream, MSB first. + * Flag bit 1 = literal (8 bits), flag bit 0 = match (11-bit offset + 4-bit length). + */ +AARU_EXPORT int AARU_CALL larc_decompress_lzs(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len) +{ + lha_bitio bitio; + lha_lzss lzss; + size_t expected; + + if(!in_buf || !out_buf || !out_len || in_len == 0) return -1; + + expected = *out_len; + + if(!lha_lzss_init(&lzss, 2048, out_buf, expected)) return -1; + + lha_bitio_init(&bitio, in_buf, in_len); + + while(lzss.out_pos < expected && !lha_bitio_at_eof(&bitio)) + { + if(lha_bitio_next_bit(&bitio)) + { + /* Literal byte */ + uint8_t literal = (uint8_t)lha_bitio_next_bits(&bitio, 8); + lha_lzss_emit_literal(&lzss, literal); + } + else + { + /* Match */ + int raw_offset = (int)lha_bitio_next_bits(&bitio, 11); + int length = (int)lha_bitio_next_bits(&bitio, 4) + 2; + int offset = (int)lzss.position - raw_offset - 17; + + lha_lzss_emit_match(&lzss, offset, length); + } + } + + *out_len = lzss.out_pos; + lha_lzss_cleanup(&lzss); + return 0; +} + +/* + * LArc -lz5- decompression. + * Window: 4096 bytes, pre-filled with specific byte pattern. + * Flag-byte stream: each flag byte controls 8 items. + * Flag bit set = literal (1 byte), flag bit clear = match (2 bytes). + */ +AARU_EXPORT int AARU_CALL larc_decompress_lz5(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len) +{ + lha_lzss lzss; + size_t expected; + size_t in_pos; + int i; + + if(!in_buf || !out_buf || !out_len || in_len == 0) return -1; + + expected = *out_len; + + if(!lha_lzss_init(&lzss, 4096, out_buf, expected)) return -1; + + /* Pre-fill window with specific pattern (same as lharc -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); + memset(&lzss.window[256 * 13 + 512 + 128 + 18], ' ', 128 - 18); + + in_pos = 0; + + while(lzss.out_pos < expected && in_pos < in_len) + { + uint8_t flags = in_buf[in_pos++]; + int bit; + + for(bit = 0; bit < 8 && lzss.out_pos < expected && in_pos < in_len; bit++) + { + if(flags & (1 << bit)) + { + /* Literal */ + lha_lzss_emit_literal(&lzss, in_buf[in_pos++]); + } + else + { + /* Match: 2 bytes */ + uint8_t byte1, byte2; + int offset, length; + + if(in_pos + 1 >= in_len) break; + + byte1 = in_buf[in_pos++]; + byte2 = in_buf[in_pos++]; + + offset = (int)lzss.position - (int)byte1 - ((byte2 & 0xf0) << 4) - 18; + length = (byte2 & 0x0f) + 3; + + lha_lzss_emit_match(&lzss, offset, length); + } + } + } + + *out_len = lzss.out_pos; + lha_lzss_cleanup(&lzss); + return 0; +} diff --git a/lha/larc.h b/lha/larc.h new file mode 100644 index 0000000..54af3e5 --- /dev/null +++ b/lha/larc.h @@ -0,0 +1,30 @@ +/* + * 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__LHA_LARC_H_ +#define AARU_COMPRESSION_NATIVE__LHA_LARC_H_ + +#include +#include +#include "../library.h" + +AARU_EXPORT int AARU_CALL larc_decompress_lzs(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +AARU_EXPORT int AARU_CALL larc_decompress_lz5(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +#endif /* AARU_COMPRESSION_NATIVE__LHA_LARC_H_ */ diff --git a/lha/lh1.c b/lha/lh1.c new file mode 100644 index 0000000..f5305a9 --- /dev/null +++ b/lha/lh1.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 . + */ + +#include "lh1.h" +#include "bitio.h" +#include "huffman.h" +#include "lzss.h" + +#include + +#define LH1_NUM_LEAVES 314 +#define LH1_NUM_NODES (LH1_NUM_LEAVES * 2 - 1) + +typedef struct lh1_node +{ + struct lh1_node *parent; + struct lh1_node *leftchild; + struct lh1_node *rightchild; + int index; /* Position in sorted nodes[] array */ + int freq; + int value; /* Symbol value for leaf nodes */ +} lh1_node; + +typedef struct +{ + lh1_node storage[LH1_NUM_NODES]; + lh1_node *nodes[LH1_NUM_NODES]; /* Sorted by frequency order */ +} lh1_tree; + +static void lh1_init_tree(lh1_tree *tree) +{ + int i; + + memset(tree->storage, 0, sizeof(tree->storage)); + + for(i = 0; i < LH1_NUM_NODES; i++) tree->nodes[i] = &tree->storage[i]; + + /* Initialize leaf nodes (at the high end of the sorted array) */ + for(i = 0; i < LH1_NUM_LEAVES; i++) + { + int index = LH1_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; + } + + /* Build branch nodes bottom-up */ + for(i = LH1_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 lh1_rearrange(lh1_tree *tree, lh1_node *node) +{ + int p_index = node->index; + int q_index = p_index; + lh1_node *p = node; + lh1_node *q; + + /* Find highest position where this node should be */ + while(q_index > 0 && tree->nodes[q_index - 1]->freq < p->freq) q_index--; + + if(q_index >= p_index) return; + + q = tree->nodes[q_index]; + + /* Swap parent-child links using pointers */ + { + lh1_node *new_q_parent = p->parent; + lh1_node *new_p_parent = q->parent; + int p_is_right = (p->parent && p->parent->rightchild == p); + int q_is_right = (q->parent && q->parent->rightchild == q); + + if(p->parent) + { + if(p_is_right) + p->parent->rightchild = q; + else + p->parent->leftchild = q; + } + + if(q->parent) + { + if(q_is_right) + q->parent->rightchild = p; + else + q->parent->leftchild = p; + } + + p->parent = new_p_parent; + q->parent = new_q_parent; + } + + /* Swap positions in sorted array */ + tree->nodes[p_index] = q; + tree->nodes[p_index]->index = p_index; + tree->nodes[q_index] = p; + tree->nodes[q_index]->index = q_index; +} + +static void lh1_reconstruct(lh1_tree *tree) +{ + lh1_node *leaves[LH1_NUM_LEAVES]; + int n = 0; + int i; + int leaf_index, branch_index, node_index, pair_index; + + /* Collect all leaf nodes and halve their frequencies */ + for(i = 0; i < LH1_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 = LH1_NUM_LEAVES - 1; + branch_index = LH1_NUM_LEAVES - 2; + node_index = LH1_NUM_NODES - 1; + pair_index = LH1_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--; + } + + { + lh1_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 lh1_update(lh1_tree *tree, lh1_node *node) +{ + if(tree->nodes[0]->freq == 0x8000) lh1_reconstruct(tree); + + for(;;) + { + node->freq++; + + if(!node->parent) break; + + lh1_rearrange(tree, node); + node = node->parent; + } +} + +AARU_EXPORT int AARU_CALL lha_decompress_lh1(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len) +{ + lha_bitio bitio; + lha_lzss lzss; + lh1_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(!in_buf || !out_buf || !out_len || in_len == 0) return -1; + + expected = *out_len; + + if(!lha_lzss_init(&lzss, 4096, out_buf, expected)) return -1; + + /* Pre-fill window with specific pattern */ + 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); + memset(&lzss.window[256 * 13 + 512 + 128 + 18], ' ', 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, in_buf, in_len); + lh1_init_tree(&tree); + + while(lzss.out_pos < expected && !lha_bitio_at_eof(&bitio)) + { + /* Traverse adaptive Huffman tree */ + lh1_node *node = tree.nodes[0]; /* root */ + + while(node->leftchild || node->rightchild) + { + if(lha_bitio_next_bit(&bitio)) + node = node->leftchild; + else + node = node->rightchild; + + if(!node) break; + } + + if(!node) break; + + lh1_update(&tree, node); + + if(node->value < 0x100) + { + /* Literal */ + lha_lzss_emit_literal(&lzss, (uint8_t)node->value); + } + else + { + /* Match */ + 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); + } + } + + *out_len = lzss.out_pos; + lha_prefix_code_free(distancecode); + lha_lzss_cleanup(&lzss); + return 0; +} diff --git a/lha/lh1.h b/lha/lh1.h new file mode 100644 index 0000000..59b2149 --- /dev/null +++ b/lha/lh1.h @@ -0,0 +1,28 @@ +/* + * 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__LHA_LH1_H_ +#define AARU_COMPRESSION_NATIVE__LHA_LH1_H_ + +#include +#include +#include "../library.h" + +AARU_EXPORT int AARU_CALL lha_decompress_lh1(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +#endif /* AARU_COMPRESSION_NATIVE__LHA_LH1_H_ */ diff --git a/lha/lh_old.c b/lha/lh_old.c new file mode 100644 index 0000000..091ba6e --- /dev/null +++ b/lha/lh_old.c @@ -0,0 +1,1217 @@ +/* + * 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 . + */ + +/* + * Self-contained port of LH2, LH3 and PM2 decompression from legacy code. + * Uses its own 16-bit bit I/O and Huffman tree management. + */ + +#include "lh_old.h" + +#include +#include + +/* --- Constants --- */ + +#define UCHAR_MAX_VAL 255 +#define CHAR_BIT_VAL 8 +#define USHRT_BIT 16 +#define MAXMATCH 256 +#define NC (UCHAR_MAX_VAL + MAXMATCH + 2 - THRESHOLD) +#define THRESHOLD 3 +#define NPT 0x80 +#define CBIT 9 +#define TBIT 5 +#define NT (USHRT_BIT + 3) +#define N_CHAR (256 + 60 - THRESHOLD + 1) +#define TREESIZE_C (N_CHAR * 2) +#define TREESIZE_P (128 * 2) +#define TREESIZE (TREESIZE_C + TREESIZE_P) +#define ROOT_C 0 +#define ROOT_P TREESIZE_C +#define N1 286 +#define EXTRABITS 8 +#define BUFBITS 16 +#define NP (MAX_DICBIT + 1) +#define MAX_DICBIT 16 +#define LENFIELD 4 +#define PMARC2_OFFSET (0x100 - 2) + +/* --- Buffer I/O --- */ + +typedef struct +{ + const uint8_t *in_buf; + size_t in_len; + size_t in_pos; + uint8_t *out_buf; + size_t out_len; + size_t out_pos; + int error; +} lhold_io; + +static int lhold_getchar(lhold_io *io) +{ + if(io->in_pos >= io->in_len) return 0; + + return io->in_buf[io->in_pos++]; +} + +static void lhold_putchar(lhold_io *io, uint8_t c) +{ + if(io->out_pos < io->out_len) io->out_buf[io->out_pos++] = c; +} + +static int lhold_at_end(lhold_io *io) { return io->out_pos >= io->out_len; } + +/* --- PM2 Tree --- */ + +struct pmarc2_tree +{ + uint8_t *leftarr; + uint8_t *rightarr; + uint8_t root; +}; + +/* --- Decompression data --- */ + +struct lhold_st +{ + int32_t pbit; + int32_t np; + int32_t nn; + int32_t n1; + int32_t most_p; + int32_t avail; + uint32_t n_max; + uint16_t maxmatch; + uint16_t total_p; + uint16_t blocksize; + uint16_t c_table[4096]; + uint16_t pt_table[256]; + uint16_t left[2 * NC - 1]; + uint16_t right[2 * NC - 1]; + uint16_t freq[TREESIZE]; + uint16_t pt_code[NPT]; + int16_t child[TREESIZE]; + int16_t stock[TREESIZE]; + int16_t s_node[TREESIZE / 2]; + int16_t block[TREESIZE]; + int16_t parent[TREESIZE]; + int16_t edge[TREESIZE]; + uint8_t c_len[NC]; + uint8_t pt_len[NPT]; +}; + +struct lhold_pm +{ + struct pmarc2_tree tree1; + struct pmarc2_tree tree2; + uint16_t lastupdate; + uint16_t dicsiz1; + uint8_t gettree1; + uint8_t tree1left[32]; + uint8_t tree1right[32]; + uint8_t table1[32]; + uint8_t tree2left[8]; + uint8_t tree2right[8]; + uint8_t table2[8]; + uint8_t tree1bound; + uint8_t mindepth; + uint8_t prev[0x100]; + uint8_t next[0x100]; + uint8_t parentarr[0x100]; + uint8_t lastbyte; +}; + +struct lhold_data +{ + lhold_io *io; + uint8_t *text; + uint16_t dicbit; + + uint16_t bitbuf; + uint8_t subbitbuf; + uint8_t bitcount; + uint32_t loc; + uint32_t count; + uint32_t nextcount; + + union + { + struct lhold_st st; + struct lhold_pm pm; + } d; +}; + +/* --- Bit I/O (16-bit accumulator) --- */ + +static void fillbuf(struct lhold_data *dat, uint8_t n) +{ + if(dat->io->error) return; + + while(n > dat->bitcount) + { + n -= dat->bitcount; + dat->bitbuf = (uint16_t)((dat->bitbuf << dat->bitcount) + (dat->subbitbuf >> (CHAR_BIT_VAL - dat->bitcount))); + dat->subbitbuf = (uint8_t)lhold_getchar(dat->io); + dat->bitcount = CHAR_BIT_VAL; + } + + dat->bitcount -= n; + dat->bitbuf = (uint16_t)((dat->bitbuf << n) + (dat->subbitbuf >> (CHAR_BIT_VAL - n))); + dat->subbitbuf <<= n; +} + +static uint16_t getbits(struct lhold_data *dat, uint8_t n) +{ + uint16_t x = (uint16_t)(dat->bitbuf >> (2 * CHAR_BIT_VAL - n)); + + fillbuf(dat, n); + return x; +} + +#define init_getbits(a) fillbuf((a), 2 * CHAR_BIT_VAL) + +/* --- Huffman table construction --- */ + +static void make_table(struct lhold_data *dat, int16_t nchar, uint8_t bitlen[], int16_t tablebits, uint16_t table[]) +{ + uint16_t count[17]; + uint16_t weight[17]; + uint16_t start[17]; + uint16_t total; + uint32_t i; + int32_t j, k, l, m, n, avail; + uint16_t *p; + + if(dat->io->error) return; + + avail = nchar; + memset(count, 0, sizeof(count)); + + for(i = 1; i <= 16; i++) weight[i] = (uint16_t)(1 << (16 - i)); + + for(i = 0; (int32_t)i < nchar; i++) count[bitlen[i]]++; + + total = 0; + + for(i = 1; i <= 16; i++) + { + start[i] = total; + total = (uint16_t)(total + weight[i] * count[i]); + } + + if(total & 0xFFFF) + { + dat->io->error = 1; + return; + } + + m = 16 - tablebits; + + for(i = 1; (int32_t)i <= tablebits; i++) + { + start[i] >>= m; + weight[i] >>= m; + } + + j = start[tablebits + 1] >> m; + k = 1 << tablebits; + + if(j != 0) + for(i = (uint32_t)j; (int32_t)i < k; i++) table[i] = 0; + + for(j = 0; j < nchar; j++) + { + k = bitlen[j]; + + if(k == 0) continue; + + l = start[k] + weight[k]; + + if(k <= tablebits) + { + for(i = (uint32_t)start[k]; (int32_t)i < l; i++) table[i] = (uint16_t)j; + } + else + { + p = &table[(i = (uint32_t)start[k]) >> m]; + i <<= tablebits; + n = k - tablebits; + + while(--n >= 0) + { + if(*p == 0) + { + dat->d.st.right[avail] = dat->d.st.left[avail] = 0; + *p = (uint16_t)avail++; + } + + if(i & 0x8000) + p = &dat->d.st.right[*p]; + else + p = &dat->d.st.left[*p]; + + i <<= 1; + } + + *p = (uint16_t)j; + } + + start[k] = (uint16_t)l; + } +} + +/* --- LH2: Dynamic Huffman --- */ + +static void start_p_dyn(struct lhold_data *dat) +{ + dat->d.st.freq[ROOT_P] = 1; + dat->d.st.child[ROOT_P] = (int16_t)(~(N_CHAR)); + dat->d.st.s_node[N_CHAR] = ROOT_P; + dat->d.st.edge[dat->d.st.block[ROOT_P] = dat->d.st.stock[dat->d.st.avail++]] = ROOT_P; + dat->d.st.most_p = ROOT_P; + dat->d.st.total_p = 0; + dat->d.st.nn = 1 << dat->dicbit; + dat->nextcount = 64; +} + +static void start_c_dyn(struct lhold_data *dat) +{ + int32_t i, j, f; + + dat->d.st.n1 = (int32_t)((dat->d.st.n_max >= (uint32_t)(256 + dat->d.st.maxmatch - THRESHOLD + 1)) ? 512 + : dat->d.st.n_max - 1); + + for(i = 0; i < TREESIZE_C; i++) + { + dat->d.st.stock[i] = (int16_t)i; + dat->d.st.block[i] = 0; + } + + for(i = 0, j = (int32_t)(dat->d.st.n_max * 2 - 2); i < (int32_t)dat->d.st.n_max; i++, j--) + { + dat->d.st.freq[j] = 1; + dat->d.st.child[j] = (int16_t)(~i); + dat->d.st.s_node[i] = (int16_t)j; + dat->d.st.block[j] = 1; + } + + dat->d.st.avail = 2; + dat->d.st.edge[1] = (int16_t)(dat->d.st.n_max - 1); + i = (int32_t)(dat->d.st.n_max * 2 - 2); + + while(j >= 0) + { + f = dat->d.st.freq[j] = dat->d.st.freq[i] + dat->d.st.freq[i - 1]; + dat->d.st.child[j] = (int16_t)i; + dat->d.st.parent[i] = dat->d.st.parent[i - 1] = (int16_t)j; + + if(f == dat->d.st.freq[j + 1]) + dat->d.st.edge[dat->d.st.block[j] = dat->d.st.block[j + 1]] = (int16_t)j; + else + dat->d.st.edge[dat->d.st.block[j] = dat->d.st.stock[dat->d.st.avail++]] = (int16_t)j; + + i -= 2; + j--; + } +} + +static void decode_start_dyn(struct lhold_data *dat) +{ + dat->d.st.n_max = 286; + dat->d.st.maxmatch = MAXMATCH; + init_getbits(dat); + start_c_dyn(dat); + start_p_dyn(dat); +} + +static void reconst(struct lhold_data *dat, int32_t start, int32_t end) +{ + int32_t i, j, k, l, b = 0; + uint32_t f, g; + + for(i = j = start; i < end; i++) + { + if((k = dat->d.st.child[i]) < 0) + { + dat->d.st.freq[j] = (uint16_t)((dat->d.st.freq[i] + 1) / 2); + dat->d.st.child[j] = (int16_t)k; + j++; + } + + if(dat->d.st.edge[b = dat->d.st.block[i]] == i) dat->d.st.stock[--dat->d.st.avail] = (int16_t)b; + } + + j--; + i = end - 1; + l = end - 2; + + while(i >= start) + { + while(i >= l) + { + dat->d.st.freq[i] = dat->d.st.freq[j]; + dat->d.st.child[i] = dat->d.st.child[j]; + i--; + j--; + } + + f = (uint32_t)(dat->d.st.freq[l] + dat->d.st.freq[l + 1]); + + for(k = start; f < (uint32_t)dat->d.st.freq[k]; k++) + ; + + while(j >= k) + { + dat->d.st.freq[i] = dat->d.st.freq[j]; + dat->d.st.child[i] = dat->d.st.child[j]; + i--; + j--; + } + + dat->d.st.freq[i] = (uint16_t)f; + dat->d.st.child[i] = (int16_t)(l + 1); + i--; + l -= 2; + } + + f = 0; + + for(i = start; i < end; i++) + { + if((j = dat->d.st.child[i]) < 0) + dat->d.st.s_node[~j] = (int16_t)i; + else + dat->d.st.parent[j] = dat->d.st.parent[j - 1] = (int16_t)i; + + if((g = (uint32_t)dat->d.st.freq[i]) == f) + dat->d.st.block[i] = (int16_t)b; + else + { + dat->d.st.edge[b = dat->d.st.block[i] = dat->d.st.stock[dat->d.st.avail++]] = (int16_t)i; + f = g; + } + } +} + +static int32_t swap_inc(struct lhold_data *dat, int32_t p) +{ + int32_t b, q, r, s; + + b = dat->d.st.block[p]; + + if((q = dat->d.st.edge[b]) != p) + { + r = dat->d.st.child[p]; + s = dat->d.st.child[q]; + dat->d.st.child[p] = (int16_t)s; + dat->d.st.child[q] = (int16_t)r; + + if(r >= 0) + dat->d.st.parent[r] = dat->d.st.parent[r - 1] = (int16_t)q; + else + dat->d.st.s_node[~r] = (int16_t)q; + + if(s >= 0) + dat->d.st.parent[s] = dat->d.st.parent[s - 1] = (int16_t)p; + else + dat->d.st.s_node[~s] = (int16_t)p; + + p = q; + dat->d.st.edge[b]++; + + if(++dat->d.st.freq[p] == dat->d.st.freq[p - 1]) + dat->d.st.block[p] = dat->d.st.block[p - 1]; + else + dat->d.st.edge[dat->d.st.block[p] = dat->d.st.stock[dat->d.st.avail++]] = (int16_t)p; + } + else if(b == dat->d.st.block[p + 1]) + { + dat->d.st.edge[b]++; + + if(++dat->d.st.freq[p] == dat->d.st.freq[p - 1]) + dat->d.st.block[p] = dat->d.st.block[p - 1]; + else + dat->d.st.edge[dat->d.st.block[p] = dat->d.st.stock[dat->d.st.avail++]] = (int16_t)p; + } + else if(++dat->d.st.freq[p] == dat->d.st.freq[p - 1]) + { + dat->d.st.stock[--dat->d.st.avail] = (int16_t)b; + dat->d.st.block[p] = dat->d.st.block[p - 1]; + } + + return dat->d.st.parent[p]; +} + +static void update_p(struct lhold_data *dat, int32_t p) +{ + int32_t q; + + if(dat->d.st.total_p == 0x8000) + { + reconst(dat, ROOT_P, dat->d.st.most_p + 1); + dat->d.st.total_p = dat->d.st.freq[ROOT_P]; + dat->d.st.freq[ROOT_P] = 0xffff; + } + + q = dat->d.st.s_node[p + N_CHAR]; + + while(q != ROOT_P) q = swap_inc(dat, q); + + dat->d.st.total_p++; +} + +static void make_new_node(struct lhold_data *dat, int32_t p) +{ + int32_t q, r; + + r = dat->d.st.most_p + 1; + q = r + 1; + dat->d.st.s_node[~(dat->d.st.child[r] = dat->d.st.child[dat->d.st.most_p])] = (int16_t)r; + dat->d.st.child[q] = (int16_t)(~(p + N_CHAR)); + dat->d.st.child[dat->d.st.most_p] = (int16_t)q; + dat->d.st.freq[r] = dat->d.st.freq[dat->d.st.most_p]; + dat->d.st.freq[q] = 0; + dat->d.st.block[r] = dat->d.st.block[dat->d.st.most_p]; + + if(dat->d.st.most_p == ROOT_P) + { + dat->d.st.freq[ROOT_P] = 0xffff; + dat->d.st.edge[dat->d.st.block[ROOT_P]]++; + } + + dat->d.st.parent[r] = dat->d.st.parent[q] = (int16_t)dat->d.st.most_p; + dat->d.st.edge[dat->d.st.block[q] = dat->d.st.stock[dat->d.st.avail++]] = dat->d.st.s_node[p + N_CHAR] = + (int16_t)(dat->d.st.most_p = q); + update_p(dat, p); +} + +static void update_c(struct lhold_data *dat, int32_t p) +{ + int32_t q; + + if(dat->d.st.freq[ROOT_C] == 0x8000) reconst(dat, 0, (int32_t)dat->d.st.n_max * 2 - 1); + + dat->d.st.freq[ROOT_C]++; + q = dat->d.st.s_node[p]; + + do { q = swap_inc(dat, q); } while(q != ROOT_C); +} + +static uint16_t decode_c_dyn(struct lhold_data *dat) +{ + int32_t c; + int16_t buf, cnt; + + c = dat->d.st.child[ROOT_C]; + buf = (int16_t)dat->bitbuf; + cnt = 0; + + do + { + c = dat->d.st.child[c - (buf < 0)]; + buf <<= 1; + + if(++cnt == 16) + { + fillbuf(dat, 16); + buf = (int16_t)dat->bitbuf; + cnt = 0; + } + } while(c > 0); + + fillbuf(dat, (uint8_t)cnt); + c = ~c; + update_c(dat, c); + + if(c == dat->d.st.n1) c += getbits(dat, 8); + + return (uint16_t)c; +} + +static uint16_t decode_p_dyn(struct lhold_data *dat) +{ + int32_t c; + int16_t buf, cnt; + + while(dat->count > dat->nextcount) + { + make_new_node(dat, (int32_t)dat->nextcount / 64); + + if((dat->nextcount += 64) >= (uint32_t)dat->d.st.nn) dat->nextcount = 0xffffffff; + } + + c = dat->d.st.child[ROOT_P]; + buf = (int16_t)dat->bitbuf; + cnt = 0; + + while(c > 0) + { + c = dat->d.st.child[c - (buf < 0)]; + buf <<= 1; + + if(++cnt == 16) + { + fillbuf(dat, 16); + buf = (int16_t)dat->bitbuf; + cnt = 0; + } + } + + fillbuf(dat, (uint8_t)cnt); + c = (~c) - N_CHAR; + update_p(dat, c); + + return (uint16_t)((c << 6) + getbits(dat, 6)); +} + +/* --- LH3: Static Huffman --- */ + +static const int32_t fixed_tables[2][16] = {{3, 0x01, 0x04, 0x0c, 0x18, 0x30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {2, 0x01, 0x01, 0x03, 0x06, 0x0D, 0x1F, 0x4E, 0, 0, 0, 0, 0, 0, 0, 0}}; + +static void ready_made(struct lhold_data *dat, int32_t method) +{ + int32_t i, j; + uint32_t code, weight_val; + const int32_t *tbl; + + tbl = fixed_tables[method]; + j = *tbl++; + weight_val = (uint32_t)(1 << (16 - j)); + code = 0; + + for(i = 0; i < dat->d.st.np; i++) + { + while(*tbl == i) + { + j++; + tbl++; + weight_val >>= 1; + } + + dat->d.st.pt_len[i] = (uint8_t)j; + dat->d.st.pt_code[i] = (uint16_t)code; + code += weight_val; + } +} + +static uint16_t decode_p_st0(struct lhold_data *dat) +{ + int32_t i, j; + + j = dat->d.st.pt_table[dat->bitbuf >> 8]; + + if(j < dat->d.st.np) + { + fillbuf(dat, dat->d.st.pt_len[j]); + } + else + { + fillbuf(dat, 8); + i = dat->bitbuf; + + do + { + if((int16_t)i < 0) + j = dat->d.st.right[j]; + else + j = dat->d.st.left[j]; + + i <<= 1; + } while(j >= dat->d.st.np); + + fillbuf(dat, (uint8_t)(dat->d.st.pt_len[j] - 8)); + } + + return (uint16_t)((j << 6) + getbits(dat, 6)); +} + +static void decode_start_st0(struct lhold_data *dat) +{ + dat->d.st.n_max = 286; + dat->d.st.maxmatch = MAXMATCH; + init_getbits(dat); + dat->d.st.np = 1 << (MAX_DICBIT - 6); +} + +static void read_tree_c(struct lhold_data *dat) +{ + int32_t i, c; + + i = 0; + + while(i < N1) + { + if(getbits(dat, 1)) + dat->d.st.c_len[i] = (uint8_t)(getbits(dat, LENFIELD) + 1); + else + dat->d.st.c_len[i] = 0; + + if(++i == 3 && dat->d.st.c_len[0] == 1 && dat->d.st.c_len[1] == 1 && dat->d.st.c_len[2] == 1) + { + c = getbits(dat, CBIT); + memset(dat->d.st.c_len, 0, N1); + + for(i = 0; i < 4096; i++) dat->d.st.c_table[i] = (uint16_t)c; + + return; + } + } + + make_table(dat, N1, dat->d.st.c_len, 12, dat->d.st.c_table); +} + +static void read_tree_p(struct lhold_data *dat) +{ + int32_t i, c; + + i = 0; + + while(i < NP) + { + dat->d.st.pt_len[i] = (uint8_t)getbits(dat, LENFIELD); + + if(++i == 3 && dat->d.st.pt_len[0] == 1 && dat->d.st.pt_len[1] == 1 && dat->d.st.pt_len[2] == 1) + { + c = getbits(dat, MAX_DICBIT - 6); + + for(i = 0; i < NP; i++) dat->d.st.c_len[i] = 0; + + for(i = 0; i < 256; i++) dat->d.st.c_table[i] = (uint16_t)c; + + return; + } + } +} + +static uint16_t decode_c_st0(struct lhold_data *dat) +{ + int32_t i, j; + + if(!dat->d.st.blocksize) + { + dat->d.st.blocksize = getbits(dat, BUFBITS); + read_tree_c(dat); + + if(getbits(dat, 1)) + read_tree_p(dat); + else + ready_made(dat, 1); + + make_table(dat, NP, dat->d.st.pt_len, 8, dat->d.st.pt_table); + } + + dat->d.st.blocksize--; + j = dat->d.st.c_table[dat->bitbuf >> 4]; + + if(j < N1) + { + fillbuf(dat, dat->d.st.c_len[j]); + } + else + { + fillbuf(dat, 12); + i = dat->bitbuf; + + do + { + if((int16_t)i < 0) + j = dat->d.st.right[j]; + else + j = dat->d.st.left[j]; + + i <<= 1; + } while(j >= N1); + + fillbuf(dat, (uint8_t)(dat->d.st.c_len[j] - 12)); + } + + if(j == N1 - 1) j += getbits(dat, EXTRABITS); + + return (uint16_t)j; +} + +/* --- PM2: PMarc v2 --- */ + +static const int32_t pmarc2_historyBits[8] = {3, 3, 4, 5, 5, 5, 6, 6}; +static const int32_t pmarc2_historyBase[8] = {0, 8, 16, 32, 64, 96, 128, 192}; +static const int32_t pmarc2_repeatBits[6] = {3, 3, 5, 6, 7, 0}; +static const int32_t pmarc2_repeatBase[6] = {17, 25, 33, 65, 129, 256}; + +static void pmarc2_hist_update(struct lhold_data *dat, uint8_t data) +{ + if(data != dat->d.pm.lastbyte) + { + uint8_t oldNext, oldPrev, newNext; + + oldNext = dat->d.pm.next[data]; + oldPrev = dat->d.pm.prev[data]; + dat->d.pm.prev[oldNext] = oldPrev; + dat->d.pm.next[oldPrev] = oldNext; + + newNext = dat->d.pm.next[dat->d.pm.lastbyte]; + dat->d.pm.prev[newNext] = data; + dat->d.pm.next[data] = newNext; + + dat->d.pm.prev[data] = dat->d.pm.lastbyte; + dat->d.pm.next[dat->d.pm.lastbyte] = data; + + dat->d.pm.lastbyte = data; + } +} + +static int32_t pmarc2_tree_get(struct lhold_data *dat, struct pmarc2_tree *t) +{ + int32_t i = t->root; + + while(i < 0x80) i = (getbits(dat, 1) == 0 ? t->leftarr[i] : t->rightarr[i]); + + return i & 0x7F; +} + +static void pmarc2_tree_rebuild(struct lhold_data *dat, struct pmarc2_tree *t, uint8_t bound, uint8_t mindepth, + uint8_t *table) +{ + uint8_t d; + int32_t i, curr, empty_node, n; + + t->root = 0; + memset(t->leftarr, 0, bound); + memset(t->rightarr, 0, bound); + memset(dat->d.pm.parentarr, 0, bound); + + for(i = 0; i < dat->d.pm.mindepth - 1; i++) + { + t->leftarr[i] = (uint8_t)(i + 1); + dat->d.pm.parentarr[i + 1] = (uint8_t)i; + } + + curr = dat->d.pm.mindepth - 1; + empty_node = dat->d.pm.mindepth; + + for(d = dat->d.pm.mindepth;; d++) + { + for(i = 0; i < bound; i++) + { + if(table[i] == d) + { + if(t->leftarr[curr] == 0) + { + t->leftarr[curr] = (uint8_t)(i | 128); + } + else + { + t->rightarr[curr] = (uint8_t)(i | 128); + n = 0; + + while(t->rightarr[curr] != 0) + { + if(curr == 0) return; + + curr = dat->d.pm.parentarr[curr]; + n++; + } + + t->rightarr[curr] = (uint8_t)empty_node; + + for(;;) + { + dat->d.pm.parentarr[empty_node] = (uint8_t)curr; + curr = empty_node; + empty_node++; + n--; + + if(n == 0) break; + + t->leftarr[curr] = (uint8_t)empty_node; + } + } + } + } + + if(t->leftarr[curr] == 0) + t->leftarr[curr] = (uint8_t)empty_node; + else + t->rightarr[curr] = (uint8_t)empty_node; + + dat->d.pm.parentarr[empty_node] = (uint8_t)curr; + curr = empty_node; + empty_node++; + } +} + +static uint8_t pmarc2_hist_lookup(struct lhold_data *dat, int32_t n) +{ + uint8_t i; + uint8_t *direction = dat->d.pm.prev; + + if(n >= 0x80) + { + direction = dat->d.pm.next; + n = 0x100 - n; + } + + for(i = dat->d.pm.lastbyte; n != 0; n--) i = direction[i]; + + return i; +} + +static void pmarc2_maketree1(struct lhold_data *dat) +{ + int32_t i, nbits, x; + + dat->d.pm.tree1bound = (uint8_t)getbits(dat, 5); + dat->d.pm.mindepth = (uint8_t)getbits(dat, 3); + + if(dat->d.pm.mindepth == 0) + { + dat->d.pm.tree1.root = (uint8_t)(128 | (dat->d.pm.tree1bound - 1)); + } + else + { + memset(dat->d.pm.table1, 0, 32); + nbits = getbits(dat, 3); + + for(i = 0; i < dat->d.pm.tree1bound; i++) + { + if((x = getbits(dat, (uint8_t)nbits))) dat->d.pm.table1[i] = (uint8_t)(x - 1 + dat->d.pm.mindepth); + } + + pmarc2_tree_rebuild(dat, &dat->d.pm.tree1, dat->d.pm.tree1bound, dat->d.pm.mindepth, dat->d.pm.table1); + } +} + +static void pmarc2_maketree2(struct lhold_data *dat, int32_t par_b) +{ + int32_t i, count, index; + + if(dat->d.pm.tree1bound < 10) return; + + if(dat->d.pm.tree1bound == 29 && dat->d.pm.mindepth == 0) return; + + for(i = 0; i < 8; i++) dat->d.pm.table2[i] = 0; + + for(i = 0; i < par_b; i++) dat->d.pm.table2[i] = (uint8_t)getbits(dat, 3); + + index = 0; + count = 0; + + for(i = 0; i < 8; i++) + { + if(dat->d.pm.table2[i] != 0) + { + index = i; + count++; + } + } + + if(count == 1) + dat->d.pm.tree2.root = (uint8_t)(128 | index); + else if(count > 1) + { + dat->d.pm.mindepth = 1; + pmarc2_tree_rebuild(dat, &dat->d.pm.tree2, 8, dat->d.pm.mindepth, dat->d.pm.table2); + } +} + +static void decode_start_pm2(struct lhold_data *dat) +{ + int32_t i; + + dat->d.pm.tree1.leftarr = dat->d.pm.tree1left; + dat->d.pm.tree1.rightarr = dat->d.pm.tree1right; + dat->d.pm.tree2.leftarr = dat->d.pm.tree2left; + dat->d.pm.tree2.rightarr = dat->d.pm.tree2right; + + dat->d.pm.dicsiz1 = (uint16_t)((1 << dat->dicbit) - 1); + init_getbits(dat); + + /* Initialize history list */ + for(i = 0; i < 0x100; i++) + { + dat->d.pm.prev[(0xFF + i) & 0xFF] = (uint8_t)i; + dat->d.pm.next[(0x01 + i) & 0xFF] = (uint8_t)i; + } + + dat->d.pm.prev[0x7F] = 0x00; + dat->d.pm.next[0x00] = 0x7F; + dat->d.pm.prev[0xDF] = 0x80; + dat->d.pm.next[0x80] = 0xDF; + dat->d.pm.prev[0x9F] = 0xE0; + dat->d.pm.next[0xE0] = 0x9F; + dat->d.pm.prev[0x1F] = 0xA0; + dat->d.pm.next[0xA0] = 0x1F; + dat->d.pm.prev[0xFF] = 0x20; + dat->d.pm.next[0x20] = 0xFF; + dat->d.pm.lastbyte = 0x20; + + getbits(dat, 1); /* discard bit */ +} + +static uint16_t decode_c_pm2(struct lhold_data *dat) +{ + while(dat->d.pm.lastupdate != dat->loc) + { + pmarc2_hist_update(dat, dat->text[dat->d.pm.lastupdate]); + dat->d.pm.lastupdate = (uint16_t)((dat->d.pm.lastupdate + 1) & dat->d.pm.dicsiz1); + } + + while(dat->count >= dat->nextcount) + { + if(dat->nextcount == 0x0000) + { + pmarc2_maketree1(dat); + pmarc2_maketree2(dat, 5); + dat->nextcount = 0x0400; + } + else if(dat->nextcount == 0x0400) + { + pmarc2_maketree2(dat, 6); + dat->nextcount = 0x0800; + } + else if(dat->nextcount == 0x0800) + { + pmarc2_maketree2(dat, 7); + dat->nextcount = 0x1000; + } + else if(dat->nextcount == 0x1000) + { + if(getbits(dat, 1) != 0) pmarc2_maketree1(dat); + + pmarc2_maketree2(dat, 8); + dat->nextcount = 0x2000; + } + else + { + if(getbits(dat, 1) != 0) + { + pmarc2_maketree1(dat); + pmarc2_maketree2(dat, 8); + } + + dat->nextcount += 0x1000; + } + } + + dat->d.pm.gettree1 = (uint8_t)pmarc2_tree_get(dat, &dat->d.pm.tree1); + + if(dat->d.pm.gettree1 < 8) + return (uint16_t)(pmarc2_hist_lookup(dat, + pmarc2_historyBase[dat->d.pm.gettree1] + + getbits(dat, (uint8_t)pmarc2_historyBits[dat->d.pm.gettree1]))); + + if(dat->d.pm.gettree1 < 23) return (uint16_t)(PMARC2_OFFSET + 2 + (dat->d.pm.gettree1 - 8)); + + return (uint16_t)(PMARC2_OFFSET + pmarc2_repeatBase[dat->d.pm.gettree1 - 23] + + getbits(dat, (uint8_t)pmarc2_repeatBits[dat->d.pm.gettree1 - 23])); +} + +static uint16_t decode_p_pm2(struct lhold_data *dat) +{ + int32_t nbits, delta, gettree2; + + if(dat->d.pm.gettree1 == 8) + { + nbits = 6; + delta = 0; + } + else if(dat->d.pm.gettree1 < 28) + { + if(!(gettree2 = pmarc2_tree_get(dat, &dat->d.pm.tree2))) + { + nbits = 6; + delta = 0; + } + else + { + nbits = 5 + gettree2; + delta = 1 << nbits; + } + } + else + { + nbits = 0; + delta = 0; + } + + return (uint16_t)(delta + getbits(dat, (uint8_t)nbits)); +} + +/* --- Main decompression dispatcher --- */ + +#define METHOD_LH2 0 +#define METHOD_LH3 1 +#define METHOD_PM2 2 + +static int lhold_decrunch(lhold_io *io, int method) +{ + struct lhold_data *dd; + int result = -1; + + dd = (struct lhold_data *)calloc(1, sizeof(struct lhold_data)); + + if(!dd) return -1; + + dd->io = io; + dd->dicbit = 13; + + { + void (*decodeStart)(struct lhold_data *) = NULL; + uint16_t (*decodeC)(struct lhold_data *) = NULL; + uint16_t (*decodeP)(struct lhold_data *) = NULL; + + switch(method) + { + case METHOD_LH2: + decodeStart = decode_start_dyn; + decodeC = decode_c_dyn; + decodeP = decode_p_dyn; + break; + case METHOD_LH3: + decodeStart = decode_start_st0; + decodeP = decode_p_st0; + decodeC = decode_c_st0; + break; + case METHOD_PM2: + decodeStart = decode_start_pm2; + decodeP = decode_p_pm2; + decodeC = decode_c_pm2; + break; + default: free(dd); return -1; + } + + { + uint32_t dicsiz = (uint32_t)(1 << dd->dicbit); + int32_t offset = (method == METHOD_PM2) ? 0x100 - 2 : 0x100 - 3; + + dd->text = (uint8_t *)calloc(dicsiz, 1); + + if(!dd->text) + { + free(dd); + return -1; + } + + memset(dd->text, ' ', dicsiz); + + decodeStart(dd); + dicsiz--; /* now used with AND */ + + while(!lhold_at_end(io) && !io->error) + { + int32_t c = decodeC(dd); + + if(io->error) break; + + if(c <= UCHAR_MAX_VAL) + { + dd->text[dd->loc++] = (uint8_t)c; + lhold_putchar(io, (uint8_t)c); + dd->loc &= dicsiz; + dd->count++; + } + else + { + int32_t i; + uint16_t p; + + c -= offset; + p = decodeP(dd); + + if(io->error) break; + + i = (int32_t)(dd->loc - p - 1); + dd->count += (uint32_t)c; + + while(c--) + { + uint8_t byte = dd->text[i++ & dicsiz]; + dd->text[dd->loc++] = byte; + lhold_putchar(io, byte); + dd->loc &= dicsiz; + } + } + } + + result = io->error ? -1 : 0; + free(dd->text); + } + } + + free(dd); + return result; +} + +/* --- Public API --- */ + +AARU_EXPORT int AARU_CALL lha_decompress_lh2(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len) +{ + lhold_io io; + int result; + + if(!in_buf || !out_buf || !out_len || in_len == 0) return -1; + + memset(&io, 0, sizeof(io)); + io.in_buf = in_buf; + io.in_len = in_len; + io.out_buf = out_buf; + io.out_len = *out_len; + + result = lhold_decrunch(&io, METHOD_LH2); + *out_len = io.out_pos; + return result; +} + +AARU_EXPORT int AARU_CALL lha_decompress_lh3(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len) +{ + lhold_io io; + int result; + + if(!in_buf || !out_buf || !out_len || in_len == 0) return -1; + + memset(&io, 0, sizeof(io)); + io.in_buf = in_buf; + io.in_len = in_len; + io.out_buf = out_buf; + io.out_len = *out_len; + + result = lhold_decrunch(&io, METHOD_LH3); + *out_len = io.out_pos; + return result; +} + +AARU_EXPORT int AARU_CALL pmarc_decompress_pm2(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len) +{ + lhold_io io; + int result; + + if(!in_buf || !out_buf || !out_len || in_len == 0) return -1; + + memset(&io, 0, sizeof(io)); + io.in_buf = in_buf; + io.in_len = in_len; + io.out_buf = out_buf; + io.out_len = *out_len; + + result = lhold_decrunch(&io, METHOD_PM2); + *out_len = io.out_pos; + return result; +} diff --git a/lha/lh_old.h b/lha/lh_old.h new file mode 100644 index 0000000..ad9a867 --- /dev/null +++ b/lha/lh_old.h @@ -0,0 +1,32 @@ +/* + * 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__LHA_LH_OLD_H_ +#define AARU_COMPRESSION_NATIVE__LHA_LH_OLD_H_ + +#include +#include +#include "../library.h" + +AARU_EXPORT int AARU_CALL lha_decompress_lh2(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +AARU_EXPORT int AARU_CALL lha_decompress_lh3(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +AARU_EXPORT int AARU_CALL pmarc_decompress_pm2(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +#endif /* AARU_COMPRESSION_NATIVE__LHA_LH_OLD_H_ */ diff --git a/lha/lh_static.c b/lha/lh_static.c new file mode 100644 index 0000000..21b8eda --- /dev/null +++ b/lha/lh_static.c @@ -0,0 +1,252 @@ +/* + * 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 "lh_static.h" +#include "bitio.h" +#include "huffman.h" +#include "lzss.h" + +#include +#include + +/* + * Parse a generic Huffman code from the bitstream. + * Width = number of bits for the symbol count. + * specialindex = index at which to insert extra zero-run bits (-1 for none). + */ +static lha_prefix_code *parse_code(lha_bitio *bitio, int width, int specialindex) +{ + int num = (int)lha_bitio_next_bits(bitio, width); + + if(num == 0) + { + /* Single symbol */ + int val = (int)lha_bitio_next_bits(bitio, width); + lha_prefix_code *code = lha_prefix_code_new(); + + if(!code) return NULL; + + lha_prefix_code_add(code, val, 0, 0); + return code; + } + else + { + int *codelengths = (int *)calloc((size_t)num, sizeof(int)); + int n = 0; + lha_prefix_code *code; + + if(!codelengths) return NULL; + + while(n < num) + { + int len = (int)lha_bitio_next_bits(bitio, 3); + + if(len == 7) + while(lha_bitio_next_bit(bitio)) len++; + + codelengths[n++] = len; + + if(n == specialindex) + { + int zeroes = (int)lha_bitio_next_bits(bitio, 2); + int i; + + for(i = 0; i < zeroes && n < num; i++) codelengths[n++] = 0; + } + } + + code = lha_prefix_code_from_lengths(codelengths, num, 16, true); + free(codelengths); + return code; + } +} + +/* + * Parse the literal Huffman code from the bitstream (uses metacode). + */ +static lha_prefix_code *parse_literal_code(lha_bitio *bitio) +{ + lha_prefix_code *metacode = parse_code(bitio, 5, 3); + lha_prefix_code *code; + int num, n; + int *codelengths; + + if(!metacode) return NULL; + + num = (int)lha_bitio_next_bits(bitio, 9); + + if(num == 0) + { + int val = (int)lha_bitio_next_bits(bitio, 9); + + lha_prefix_code_free(metacode); + code = lha_prefix_code_new(); + + if(!code) return NULL; + + lha_prefix_code_add(code, val, 0, 0); + return code; + } + + codelengths = (int *)calloc((size_t)num, sizeof(int)); + + if(!codelengths) + { + lha_prefix_code_free(metacode); + return NULL; + } + + n = 0; + + while(n < num) + { + int c = lha_prefix_code_decode(bitio, metacode); + + if(c < 0) break; + + if(c <= 2) + { + int zeros = 0, i; + + switch(c) + { + case 0: zeros = 1; break; + case 1: zeros = (int)lha_bitio_next_bits(bitio, 4) + 3; break; + case 2: zeros = (int)lha_bitio_next_bits(bitio, 9) + 20; break; + } + + if(n + zeros > num) zeros = num - n; + + for(i = 0; i < zeros; i++) codelengths[n++] = 0; + } + else + { + codelengths[n++] = c - 2; + } + } + + lha_prefix_code_free(metacode); + code = lha_prefix_code_from_lengths(codelengths, num, 16, true); + free(codelengths); + return code; +} + +/* + * Internal decompression for LH4/5/6/7 with parameterized window bits. + */ +static int lh_static_decompress(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len, + int window_bits) +{ + lha_bitio bitio; + lha_lzss lzss; + lha_prefix_code *literalcode = NULL; + lha_prefix_code *distancecode = NULL; + int blocksize = 0; + int blockpos = 0; + size_t expected; + int dist_width; + + if(!in_buf || !out_buf || !out_len || in_len == 0) return -1; + + expected = *out_len; + dist_width = window_bits < 15 ? 4 : 5; + + if(!lha_lzss_init(&lzss, (size_t)1 << window_bits, out_buf, expected)) return -1; + + lha_bitio_init(&bitio, in_buf, in_len); + + while(lzss.out_pos < expected && !lha_bitio_at_eof(&bitio)) + { + if(blockpos >= blocksize) + { + /* New block */ + blocksize = (int)lha_bitio_next_bits(&bitio, 16); + blockpos = 0; + + lha_prefix_code_free(literalcode); + lha_prefix_code_free(distancecode); + + literalcode = parse_literal_code(&bitio); + distancecode = parse_code(&bitio, dist_width, -1); + + if(!literalcode || !distancecode) + { + lha_prefix_code_free(literalcode); + lha_prefix_code_free(distancecode); + lha_lzss_cleanup(&lzss); + return -1; + } + } + + blockpos++; + + { + int lit = lha_prefix_code_decode(&bitio, literalcode); + + if(lit < 0) break; + + if(lit < 0x100) + { + lha_lzss_emit_literal(&lzss, (uint8_t)lit); + } + else + { + int length = lit - 0x100 + 3; + int bit = lha_prefix_code_decode(&bitio, distancecode); + int offset; + + if(bit < 0) break; + + if(bit == 0) + offset = 1; + else if(bit == 1) + offset = 2; + else + offset = (1 << (bit - 1)) + (int)lha_bitio_next_bits(&bitio, bit - 1) + 1; + + lha_lzss_emit_match(&lzss, offset, length); + } + } + } + + *out_len = lzss.out_pos; + lha_prefix_code_free(literalcode); + lha_prefix_code_free(distancecode); + lha_lzss_cleanup(&lzss); + return 0; +} + +AARU_EXPORT int AARU_CALL lha_decompress_lh4(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len) +{ + return lh_static_decompress(in_buf, in_len, out_buf, out_len, 12); +} + +AARU_EXPORT int AARU_CALL lha_decompress_lh5(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len) +{ + return lh_static_decompress(in_buf, in_len, out_buf, out_len, 13); +} + +AARU_EXPORT int AARU_CALL lha_decompress_lh6(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len) +{ + return lh_static_decompress(in_buf, in_len, out_buf, out_len, 15); +} + +AARU_EXPORT int AARU_CALL lha_decompress_lh7(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len) +{ + return lh_static_decompress(in_buf, in_len, out_buf, out_len, 16); +} diff --git a/lha/lh_static.h b/lha/lh_static.h new file mode 100644 index 0000000..ac8d727 --- /dev/null +++ b/lha/lh_static.h @@ -0,0 +1,34 @@ +/* + * 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__LHA_LH_STATIC_H_ +#define AARU_COMPRESSION_NATIVE__LHA_LH_STATIC_H_ + +#include +#include +#include "../library.h" + +AARU_EXPORT int AARU_CALL lha_decompress_lh4(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +AARU_EXPORT int AARU_CALL lha_decompress_lh5(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +AARU_EXPORT int AARU_CALL lha_decompress_lh6(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +AARU_EXPORT int AARU_CALL lha_decompress_lh7(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +#endif /* AARU_COMPRESSION_NATIVE__LHA_LH_STATIC_H_ */ diff --git a/lha/lzss.c b/lha/lzss.c new file mode 100644 index 0000000..5023bc9 --- /dev/null +++ b/lha/lzss.c @@ -0,0 +1,48 @@ +/* + * 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 "lzss.h" + +#include +#include + +bool lha_lzss_init(lha_lzss *self, size_t window_size, uint8_t *out_buf, size_t out_size) +{ + self->window = (uint8_t *)malloc(window_size); + + if(!self->window) return false; + + self->mask = window_size - 1; /* Assume power-of-two */ + self->position = 0; + self->out_buf = out_buf; + self->out_pos = 0; + self->out_size = out_size; + + memset(self->window, 0, window_size); + + return true; +} + +void lha_lzss_cleanup(lha_lzss *self) +{ + if(self->window) + { + free(self->window); + self->window = NULL; + } +} diff --git a/lha/lzss.h b/lha/lzss.h new file mode 100644 index 0000000..0161cf5 --- /dev/null +++ b/lha/lzss.h @@ -0,0 +1,68 @@ +/* + * 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__LHA_LZSS_H_ +#define AARU_COMPRESSION_NATIVE__LHA_LZSS_H_ + +#include +#include +#include + +typedef struct lha_lzss +{ + uint8_t *window; + size_t mask; + int64_t position; + uint8_t *out_buf; + size_t out_pos; + size_t out_size; +} lha_lzss; + +bool lha_lzss_init(lha_lzss *self, size_t window_size, uint8_t *out_buf, size_t out_size); +void lha_lzss_cleanup(lha_lzss *self); + +static inline void lha_lzss_emit_literal(lha_lzss *self, uint8_t literal) +{ + self->window[self->position & self->mask] = literal; + self->position++; + + if(self->out_pos < self->out_size) self->out_buf[self->out_pos++] = literal; +} + +static inline void lha_lzss_emit_match(lha_lzss *self, int offset, int length) +{ + size_t windowoffs = (size_t)(self->position & self->mask); + int i; + + for(i = 0; i < length; i++) + { + uint8_t byte = self->window[(windowoffs + i - offset) & self->mask]; + self->window[(windowoffs + i) & self->mask] = byte; + + if(self->out_pos < self->out_size) self->out_buf[self->out_pos++] = byte; + } + + self->position += length; +} + +static inline uint8_t lha_lzss_get_byte(lha_lzss *self, int64_t pos) +{ + return self->window[pos & self->mask]; +} + +#endif /* AARU_COMPRESSION_NATIVE__LHA_LZSS_H_ */ diff --git a/lha/pmarc1.c b/lha/pmarc1.c new file mode 100644 index 0000000..8ca352c --- /dev/null +++ b/lha/pmarc1.c @@ -0,0 +1,387 @@ +/* + * This file is part of the Aaru Data Preservation Suite. + * Copyright (c) 2019-2026 Natalia Portillo. + * + * Based on code by Simon Howard, ISC license: + * Copyright (c) 2011, 2012, Simon Howard + * Permission to use, copy, modify, and/or distribute this software + * for any purpose with or without fee is hereby granted, provided + * that the above copyright notice and this permission notice appear + * in all copies. + * + * 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 "pmarc1.h" +#include "bitio.h" +#include "lzss.h" + +#include +#include + +#define MAX_BYTE_BLOCK_LEN 216 + +typedef struct +{ + uint8_t prev; + uint8_t next; +} pma1_history_node; + +typedef struct +{ + pma1_history_node history[256]; + uint8_t history_head; +} pma1_history_list; + +typedef struct +{ + unsigned int offset; + unsigned int bits; +} variable_length_table; + +/* --- History list --- */ + +static void init_history_list(pma1_history_list *list) +{ + int i; + + for(i = 0; i < 256; i++) + { + list->history[i].prev = (uint8_t)(i + 1); + list->history[i].next = (uint8_t)(i - 1); + } + + list->history_head = 0x20; + + list->history[0x7f].prev = 0x00; + list->history[0x00].next = 0x7f; + list->history[0x1f].prev = 0xa0; + list->history[0xa0].next = 0x1f; + list->history[0xdf].prev = 0x80; + list->history[0x80].next = 0xdf; + list->history[0x9f].prev = 0xe0; + list->history[0xe0].next = 0x9f; + list->history[0xff].prev = 0x20; + list->history[0x20].next = 0xff; +} + +static uint8_t find_in_history_list(pma1_history_list *list, uint8_t count) +{ + uint8_t code = list->history_head; + int i; + + if(count < 128) + { + for(i = 0; i < count; i++) code = list->history[code].prev; + } + else + { + for(i = 0; i < 256 - count; i++) code = list->history[code].next; + } + + return code; +} + +static void update_history_list(pma1_history_list *list, uint8_t b) +{ + pma1_history_node *node, *old_head; + + if(list->history_head == b) return; + + node = &list->history[b]; + old_head = &list->history[list->history_head]; + + /* Unhook from current position */ + list->history[node->next].prev = node->prev; + list->history[node->prev].next = node->next; + + /* Hook between old head and its next */ + node->prev = list->history_head; + node->next = old_head->next; + list->history[old_head->next].prev = b; + old_head->next = b; + + list->history_head = b; +} + +/* --- Variable length tables --- */ + +static const variable_length_table copy_ranges[] = { + {0, 6}, /* 0: 0 + (1<<6) = 64 */ + {64, 8}, /* 1: 64 + (1<<8) = 320 */ + {0, 6}, /* 2: 0 + (1<<6) = 64 */ + {64, 9}, /* 3: 64 + (1<<9) = 576 */ + {576, 11}, /* 4: 576 + (1<<11) = 2624 */ + {2624, 13}, /* 5: 2624 + (1<<13) = 10816 */ + /* Early-stream overrides: */ + {64, 8}, /* 6: replaces 3 when pos < 320 */ + {576, 8}, /* 7: replaces 4 when pos < 832 */ + {576, 9}, /* 8: replaces 4 when pos < 1088 */ + {576, 10}, /* 9: replaces 4 when pos < 1600 */ + {2624, 8}, /* 10: replaces 5 when pos < 2880 */ + {2624, 9}, /* 11: replaces 5 when pos < 3136 */ + {2624, 10}, /* 12: replaces 5 when pos < 3648 */ + {2624, 11}, /* 13: replaces 5 when pos < 4672 */ + {2624, 12}, /* 14: replaces 5 when pos < 6720 */ +}; + +static const variable_length_table byte_ranges[] = { + {0, 4}, /* 0 + (1<<4) = 16 */ + {16, 4}, /* 16 + (1<<4) = 32 */ + {32, 5}, /* 32 + (1<<5) = 64 */ + {64, 6}, /* 64 + (1<<6) = 128 */ + {128, 6}, /* 128 + (1<<6) = 192 */ + {192, 6}, /* 192 + (1<<6) = 256 */ +}; + +static const uint8_t byte_decode_trees[][5] = { + {0x12, 0x2d, 0xef, 0x1c, 0xab}, {0x12, 0x23, 0xde, 0xab, 0xcf}, {0x12, 0x2c, 0xd2, 0xab, 0xef}, + {0x12, 0xa2, 0xd2, 0xbc, 0xef}, {0x12, 0xa2, 0xc2, 0xbd, 0xef}, {0x12, 0xa2, 0xcd, 0xb1, 0xef}, + {0x12, 0xab, 0x12, 0xcd, 0xef}, {0x12, 0xab, 0x1d, 0xc1, 0xef}, {0x12, 0xab, 0xc1, 0xd1, 0xef}, + {0xa1, 0x12, 0x2c, 0xde, 0xbf}, {0xa1, 0x1d, 0x1c, 0xb1, 0xef}, {0xa1, 0x12, 0x2d, 0xef, 0xbc}, + {0xa1, 0x12, 0xb2, 0xde, 0xcf}, {0xa1, 0x12, 0xbc, 0xd1, 0xef}, {0xa1, 0x1c, 0xb1, 0xd1, 0xef}, + {0xa1, 0xb1, 0x12, 0xcd, 0xef}, {0xa1, 0xb1, 0xc1, 0xd1, 0xef}, {0x12, 0x1c, 0xde, 0xab, 0x00}, + {0x12, 0xa2, 0xcd, 0xbe, 0x00}, {0x12, 0xab, 0xc1, 0xde, 0x00}, {0xa1, 0x1d, 0x1c, 0xbe, 0x00}, + {0xa1, 0x12, 0xbc, 0xde, 0x00}, {0xa1, 0x1c, 0xb1, 0xde, 0x00}, {0xa1, 0xb1, 0xc1, 0xde, 0x00}, + {0x1d, 0x1c, 0xab, 0x00, 0x00}, {0x1c, 0xa1, 0xbd, 0x00, 0x00}, {0x12, 0xab, 0xcd, 0x00, 0x00}, + {0xa1, 0x1c, 0xbd, 0x00, 0x00}, {0xa1, 0xb1, 0xcd, 0x00, 0x00}, {0xa1, 0xbc, 0x00, 0x00, 0x00}, + {0xab, 0x00, 0x00, 0x00, 0x00}, {0x00, 0x00, 0x00, 0x00, 0x00}, +}; + +/* --- Decode helper functions --- */ + +static int decode_variable_length(lha_bitio *input, const variable_length_table *table, unsigned int header) +{ + int value = (int)lha_bitio_next_bits(input, (int)table[header].bits); + + return (int)table[header].offset + value; +} + +static int next_bit_after_threshold(lha_bitio *input, unsigned int pos, unsigned int threshold, int def) +{ + if(pos >= threshold) return (int)lha_bitio_next_bit(input); + + return def; +} + +static int read_copy_byte_count(lha_bitio *input) +{ + int x = (int)lha_bitio_next_bits(input, 2); + + if(x < 3) return x + 3; + + x = (int)lha_bitio_next_bits(input, 3); + + if(x < 5) return x + 6; + else if(x == 5) return (int)lha_bitio_next_bits(input, 2) + 11; + else if(x == 6) return (int)lha_bitio_next_bits(input, 3) + 15; + + /* x == 7 */ + x = (int)lha_bitio_next_bits(input, 6); + + if(x < 62) return x + 23; + else if(x == 62) return (int)lha_bitio_next_bits(input, 5) + 85; + + /* x == 63 */ + return (int)lha_bitio_next_bits(input, 7) + 117; +} + +static int read_copy_type_range(lha_bitio *input, unsigned int pos) +{ + int x = (int)lha_bitio_next_bit(input); + + if(x == 0) + { + x = next_bit_after_threshold(input, pos, 576, 0); + + if(x != 0) return 4; + + return next_bit_after_threshold(input, pos, 64, 0); + } + else + { + x = next_bit_after_threshold(input, pos, 64, 1); + + if(x == 0) return 3; + + x = next_bit_after_threshold(input, pos, 2624, 1); + + if(x != 0) return 2; + + return 5; + } +} + +static int read_byte_decode_index(lha_bitio *input, const uint8_t *tree) +{ + const uint8_t *ptr = tree; + + if(ptr[0] == 0) return 0; + + for(;;) + { + int bit = (int)lha_bitio_next_bit(input); + unsigned int child; + + if(bit == 0) + child = (*ptr >> 4) & 0x0f; + else + child = *ptr & 0x0f; + + if(child >= 10) return (int)(child - 10); + + ptr += child; + } +} + +static int read_byte(lha_bitio *input, const uint8_t *tree, pma1_history_list *history) +{ + int index = read_byte_decode_index(input, tree); + int count = decode_variable_length(input, byte_ranges, (unsigned int)index); + + return find_in_history_list(history, (uint8_t)count); +} + +static int read_byte_block_count(lha_bitio *input) +{ + int x = (int)lha_bitio_next_bits(input, 2); + + if(x < 3) return x + 1; + + x = (int)lha_bitio_next_bits(input, 3); + + if(x < 7) return x + 4; + + x = (int)lha_bitio_next_bits(input, 4); + + if(x < 14) return x + 11; + else if(x == 14) return (int)lha_bitio_next_bits(input, 6) + 25; + + /* x == 15 */ + return (int)lha_bitio_next_bits(input, 7) + 89; +} + +/* --- Main decompression --- */ + +AARU_EXPORT int AARU_CALL pmarc_decompress_pm1(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len) +{ + lha_bitio bitio; + lha_lzss lzss; + pma1_history_list history; + const uint8_t *byte_tree; + size_t expected; + int bytes_left = 0; + bool next_is_match = false; + + if(!in_buf || !out_buf || !out_len || in_len == 0) return -1; + + expected = *out_len; + + if(!lha_lzss_init(&lzss, 16384, out_buf, expected)) return -1; + + lha_bitio_init(&bitio, in_buf, in_len); + init_history_list(&history); + + /* Read 5-bit header selecting the byte decode tree */ + { + int index = (int)lha_bitio_next_bits(&bitio, 5); + byte_tree = byte_decode_trees[index]; + } + + while(lzss.out_pos < expected && !lha_bitio_at_eof(&bitio)) + { + if(bytes_left > 0) + { + /* Emit a byte from the history-encoded stream */ + uint8_t b = (uint8_t)read_byte(&bitio, byte_tree, &history); + lha_lzss_emit_literal(&lzss, b); + update_history_list(&history, b); + bytes_left--; + } + else if(next_is_match || lha_bitio_next_bit(&bitio) == 0) + { + /* Match/copy */ + int range_index, count, history_distance, offset; + + next_is_match = false; + range_index = read_copy_type_range(&bitio, (unsigned int)lzss.position); + + if(range_index < 2) + count = 2; + else + count = read_copy_byte_count(&bitio); + + /* Apply early-stream overrides */ + if(range_index == 3) + { + if(lzss.position < 320) range_index = 6; + } + else if(range_index == 4) + { + if(lzss.position < 832) + range_index = 7; + else if(lzss.position < 1088) + range_index = 8; + else if(lzss.position < 1600) + range_index = 9; + } + else if(range_index == 5) + { + if(lzss.position < 2880) + range_index = 10; + else if(lzss.position < 3136) + range_index = 11; + else if(lzss.position < 3648) + range_index = 12; + else if(lzss.position < 4672) + range_index = 13; + else if(lzss.position < 6720) + range_index = 14; + } + + history_distance = decode_variable_length(&bitio, copy_ranges, (unsigned int)range_index); + offset = history_distance + 1; + + lha_lzss_emit_match(&lzss, offset, count); + + /* Update history for all emitted match bytes */ + { + size_t match_start = lzss.out_pos - (size_t)count; + int i; + + for(i = 0; i < count; i++) update_history_list(&history, out_buf[match_start + i]); + } + } + else + { + /* Byte block: read count, then emit that many literal bytes */ + bytes_left = read_byte_block_count(&bitio); + + if(bytes_left < MAX_BYTE_BLOCK_LEN) next_is_match = true; + + /* Process first byte immediately */ + { + uint8_t b = (uint8_t)read_byte(&bitio, byte_tree, &history); + lha_lzss_emit_literal(&lzss, b); + update_history_list(&history, b); + bytes_left--; + } + } + } + + *out_len = lzss.out_pos; + lha_lzss_cleanup(&lzss); + return 0; +} diff --git a/lha/pmarc1.h b/lha/pmarc1.h new file mode 100644 index 0000000..dca902e --- /dev/null +++ b/lha/pmarc1.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__LHA_PMARC1_H_ +#define AARU_COMPRESSION_NATIVE__LHA_PMARC1_H_ + +#include +#include +#include "../library.h" + +AARU_EXPORT int AARU_CALL pmarc_decompress_pm1(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len); + +#endif /* AARU_COMPRESSION_NATIVE__LHA_PMARC1_H_ */ diff --git a/library.h b/library.h index eed194a..caeead9 100644 --- a/library.h +++ b/library.h @@ -201,4 +201,37 @@ AARU_EXPORT int AARU_CALL ha_asc_decompress(const unsigned char *in_buf, size_t AARU_EXPORT int AARU_CALL ha_hsc_decompress(const unsigned char *in_buf, size_t in_len, unsigned char *out_buf, size_t *out_len); +// LHA -lh1- (Dynamic Huffman, 4KB window) +AARU_EXPORT int AARU_CALL lha_decompress_lh1(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// LHA -lh2- (Dynamic Huffman, legacy) +AARU_EXPORT int AARU_CALL lha_decompress_lh2(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// LHA -lh3- (Static Huffman, legacy) +AARU_EXPORT int AARU_CALL lha_decompress_lh3(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// LHA -lh4- (Block Huffman, 4KB window) +AARU_EXPORT int AARU_CALL lha_decompress_lh4(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// LHA -lh5- (Block Huffman, 8KB window) +AARU_EXPORT int AARU_CALL lha_decompress_lh5(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// LHA -lh6- (Block Huffman, 32KB window) +AARU_EXPORT int AARU_CALL lha_decompress_lh6(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// LHA -lh7- (Block Huffman, 64KB window) +AARU_EXPORT int AARU_CALL lha_decompress_lh7(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// LArc -lzs- (LZSS, 2KB window) +AARU_EXPORT int AARU_CALL larc_decompress_lzs(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// LArc -lz5- (Flag-byte LZSS, 4KB window) +AARU_EXPORT int AARU_CALL larc_decompress_lz5(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// PMarc -pm1- (Adaptive history + Huffman) +AARU_EXPORT int AARU_CALL pmarc_decompress_pm1(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// PMarc -pm2- (Dynamic trees + history, legacy) +AARU_EXPORT int AARU_CALL pmarc_decompress_pm2(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + #endif // AARU_COMPRESSION_NATIVE_LIBRARY_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dca949b..6919daf 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -78,9 +78,22 @@ file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/ha_hsc.bin file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/xz.xz DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/larc_lz5.lzs + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/lha_lh6.lzh + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/lharc_lh1.lzh + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/pmarc_pm2.pma + 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 zoo/lzd.cpp arc/pack.cpp lh5.cpp arc/squeeze.cpp arc/crunch.cpp arc/squash.cpp pak/crush.cpp - pak/distill.cpp ha.cpp xz.cpp) + pak/distill.cpp ha.cpp xz.cpp + lha/lh_static.cpp lha/lh1.cpp lha/larc.cpp lha/lh_old.cpp) target_link_libraries(tests_run gtest gtest_main "Aaru.Compression.Native") diff --git a/tests/data/larc_lz5.lzs b/tests/data/larc_lz5.lzs new file mode 100644 index 0000000..ba1f3a7 Binary files /dev/null and b/tests/data/larc_lz5.lzs differ diff --git a/tests/data/lha_lh6.lzh b/tests/data/lha_lh6.lzh new file mode 100644 index 0000000..535d3a1 Binary files /dev/null and b/tests/data/lha_lh6.lzh differ diff --git a/tests/data/lharc_lh1.lzh b/tests/data/lharc_lh1.lzh new file mode 100755 index 0000000..82e4700 Binary files /dev/null and b/tests/data/lharc_lh1.lzh differ diff --git a/tests/data/pmarc_pm2.pma b/tests/data/pmarc_pm2.pma new file mode 100755 index 0000000..9cd15f4 Binary files /dev/null and b/tests/data/pmarc_pm2.pma differ diff --git a/tests/lha/larc.cpp b/tests/lha/larc.cpp new file mode 100644 index 0000000..f4dc6f7 --- /dev/null +++ b/tests/lha/larc.cpp @@ -0,0 +1,74 @@ +/* + * 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 "../../library.h" +#include "../../lha/larc.h" +#include "../crc32.h" +#include "gtest/gtest.h" +#include "lha_helpers.h" + +/* alice29.txt decompressed CRC32 */ +#define EXPECTED_CRC32 0x66007dba +#define EXPECTED_SIZE 152089 + +static uint8_t *lz5_payload; +static lha_test_info lz5_info; + +class lz5Fixture : public ::testing::Test +{ +protected: + void SetUp() + { + char path[PATH_MAX]; + char filename[PATH_MAX]; + + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/larc_lz5.lzs", path); + + lz5_payload = load_lha_payload(filename, &lz5_info); + } + + void TearDown() + { + if(lz5_payload) free(lz5_payload); + } +}; + +TEST_F(lz5Fixture, lz5_decompress) +{ + ASSERT_NE(lz5_payload, nullptr); + EXPECT_EQ(lz5_info.uncompressed_size, (uint32_t)EXPECTED_SIZE); + + size_t out_len = EXPECTED_SIZE; + uint8_t *out_buf = (uint8_t *)malloc(out_len); + + int err = larc_decompress_lz5(lz5_payload, lz5_info.payload_size, out_buf, &out_len); + + EXPECT_EQ(err, 0); + EXPECT_EQ(out_len, (size_t)EXPECTED_SIZE); + + uint32_t crc = crc32_data(out_buf, (uint32_t)out_len); + + free(out_buf); + + EXPECT_EQ(crc, (uint32_t)EXPECTED_CRC32); +} diff --git a/tests/lha/lh1.cpp b/tests/lha/lh1.cpp new file mode 100644 index 0000000..814416c --- /dev/null +++ b/tests/lha/lh1.cpp @@ -0,0 +1,74 @@ +/* + * 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 "../../library.h" +#include "../../lha/lh1.h" +#include "../crc32.h" +#include "gtest/gtest.h" +#include "lha_helpers.h" + +/* alice29.txt decompressed CRC32 */ +#define EXPECTED_CRC32 0x66007dba +#define EXPECTED_SIZE 152089 + +static uint8_t *lh1_payload; +static lha_test_info lh1_info; + +class lh1Fixture : public ::testing::Test +{ +protected: + void SetUp() + { + char path[PATH_MAX]; + char filename[PATH_MAX]; + + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/lharc_lh1.lzh", path); + + lh1_payload = load_lha_payload(filename, &lh1_info); + } + + void TearDown() + { + if(lh1_payload) free(lh1_payload); + } +}; + +TEST_F(lh1Fixture, lh1_decompress) +{ + ASSERT_NE(lh1_payload, nullptr); + EXPECT_EQ(lh1_info.uncompressed_size, (uint32_t)EXPECTED_SIZE); + + size_t out_len = EXPECTED_SIZE; + uint8_t *out_buf = (uint8_t *)malloc(out_len); + + int err = lha_decompress_lh1(lh1_payload, lh1_info.payload_size, out_buf, &out_len); + + EXPECT_EQ(err, 0); + EXPECT_EQ(out_len, (size_t)EXPECTED_SIZE); + + uint32_t crc = crc32_data(out_buf, (uint32_t)out_len); + + free(out_buf); + + EXPECT_EQ(crc, (uint32_t)EXPECTED_CRC32); +} diff --git a/tests/lha/lh_old.cpp b/tests/lha/lh_old.cpp new file mode 100644 index 0000000..6dd2006 --- /dev/null +++ b/tests/lha/lh_old.cpp @@ -0,0 +1,75 @@ +/* + * 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 "../../library.h" +#include "../../lha/lh_old.h" +#include "../crc32.h" +#include "gtest/gtest.h" +#include "lha_helpers.h" + +/* PMarc -pm2- decompresses to 152192 bytes */ +#define PM2_EXPECTED_SIZE 152192 +#define PM2_EXPECTED_CRC32 0x1bbf031e + +static uint8_t *pm2_payload; +static lha_test_info pm2_info; + +class pm2Fixture : public ::testing::Test +{ +protected: + void SetUp() + { + char path[PATH_MAX]; + char filename[PATH_MAX]; + + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/pmarc_pm2.pma", path); + + pm2_payload = load_lha_payload(filename, &pm2_info); + } + + void TearDown() + { + if(pm2_payload) free(pm2_payload); + } +}; + +TEST_F(pm2Fixture, pm2_decompress) +{ + ASSERT_NE(pm2_payload, nullptr); + EXPECT_EQ(pm2_info.uncompressed_size, (uint32_t)PM2_EXPECTED_SIZE); + + size_t out_len = PM2_EXPECTED_SIZE; + uint8_t *out_buf = (uint8_t *)malloc(out_len); + + int err = pmarc_decompress_pm2(pm2_payload, pm2_info.payload_size, out_buf, &out_len); + + EXPECT_EQ(err, 0); + EXPECT_EQ(out_len, (size_t)PM2_EXPECTED_SIZE); + + uint32_t crc = crc32_data(out_buf, (uint32_t)out_len); + + free(out_buf); + + /* Validate CRC32 of decompressed data */ + EXPECT_EQ(crc, (uint32_t)PM2_EXPECTED_CRC32); +} diff --git a/tests/lha/lh_static.cpp b/tests/lha/lh_static.cpp new file mode 100644 index 0000000..bb8e5bb --- /dev/null +++ b/tests/lha/lh_static.cpp @@ -0,0 +1,74 @@ +/* + * 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 "../../library.h" +#include "../../lha/lh_static.h" +#include "../crc32.h" +#include "gtest/gtest.h" +#include "lha_helpers.h" + +/* alice29.txt decompressed CRC32 (same as zoo/lh5 test) */ +#define EXPECTED_CRC32 0x66007dba +#define EXPECTED_SIZE 152089 + +static uint8_t *lh6_payload; +static lha_test_info lh6_info; + +class lh6Fixture : public ::testing::Test +{ +protected: + void SetUp() + { + char path[PATH_MAX]; + char filename[PATH_MAX]; + + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/lha_lh6.lzh", path); + + lh6_payload = load_lha_payload(filename, &lh6_info); + } + + void TearDown() + { + if(lh6_payload) free(lh6_payload); + } +}; + +TEST_F(lh6Fixture, lh6_decompress) +{ + ASSERT_NE(lh6_payload, nullptr); + EXPECT_EQ(lh6_info.uncompressed_size, (uint32_t)EXPECTED_SIZE); + + size_t out_len = EXPECTED_SIZE; + uint8_t *out_buf = (uint8_t *)malloc(out_len); + + int err = lha_decompress_lh6(lh6_payload, lh6_info.payload_size, out_buf, &out_len); + + EXPECT_EQ(err, 0); + EXPECT_EQ(out_len, (size_t)EXPECTED_SIZE); + + uint32_t crc = crc32_data(out_buf, (uint32_t)out_len); + + free(out_buf); + + EXPECT_EQ(crc, (uint32_t)EXPECTED_CRC32); +} diff --git a/tests/lha/lha_helpers.h b/tests/lha/lha_helpers.h new file mode 100644 index 0000000..653fc26 --- /dev/null +++ b/tests/lha/lha_helpers.h @@ -0,0 +1,122 @@ +/* + * 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__TESTS_LHA_HELPERS_H_ +#define AARU_COMPRESSION_NATIVE__TESTS_LHA_HELPERS_H_ + +#include +#include +#include +#include +#include +#include + +/* + * Minimal LHA archive header parser for tests. + * Extracts compressed payload from an LHA/LArc/PMarc archive file. + * Supports header levels 0, 1, and 2. + */ +struct lha_test_info +{ + char method[6]; /* e.g., "-lh6-" */ + uint32_t compressed_size; + uint32_t uncompressed_size; + uint8_t level; + size_t payload_offset; /* offset of compressed data in the file */ + size_t payload_size; /* actual compressed data size */ +}; + +static int parse_lha_header(const uint8_t *data, size_t data_len, struct lha_test_info *info) +{ + if(data_len < 22) return -1; + + uint8_t header_size_byte = data[0]; + uint32_t compressed_size = (uint32_t)data[7] | ((uint32_t)data[8] << 8) | ((uint32_t)data[9] << 16) | + ((uint32_t)data[10] << 24); + uint32_t uncompressed_size = (uint32_t)data[11] | ((uint32_t)data[12] << 8) | ((uint32_t)data[13] << 16) | + ((uint32_t)data[14] << 24); + uint8_t level = data[20]; + + memcpy(info->method, &data[2], 5); + info->method[5] = '\0'; + info->compressed_size = compressed_size; + info->uncompressed_size = uncompressed_size; + info->level = level; + + if(level == 0 || level == 1) + { + size_t base_header = (size_t)header_size_byte + 2; + info->payload_offset = base_header; + info->payload_size = compressed_size; + } + else if(level == 2) + { + uint16_t header_size = (uint16_t)data[0] | ((uint16_t)data[1] << 8); + info->payload_offset = header_size; + info->payload_size = compressed_size; + } + else + { + return -1; + } + + return 0; +} + +static uint8_t *load_lha_payload(const char *filename, struct lha_test_info *info) +{ + FILE *file; + size_t file_size; + uint8_t *file_data; + uint8_t *payload; + + file = fopen(filename, "rb"); + if(!file) return NULL; + + fseek(file, 0, SEEK_END); + file_size = (size_t)ftell(file); + fseek(file, 0, SEEK_SET); + + file_data = (uint8_t *)malloc(file_size); + if(!file_data) { fclose(file); return NULL; } + + fread(file_data, 1, file_size, file); + fclose(file); + + if(parse_lha_header(file_data, file_size, info) != 0) + { + free(file_data); + return NULL; + } + + if(info->payload_offset + info->payload_size > file_size) + { + free(file_data); + return NULL; + } + + payload = (uint8_t *)malloc(info->payload_size); + if(!payload) { free(file_data); return NULL; } + + memcpy(payload, file_data + info->payload_offset, info->payload_size); + free(file_data); + + return payload; +} + +#endif /* AARU_COMPRESSION_NATIVE__TESTS_LHA_HELPERS_H_ */