Add LHA, LHARC, LARC and PMARC decompression support for various algorithms

- Implemented LZSS compression in lha/lzss.c and lha/lzss.h.
- Added PMarc decompression functionality in lha/pmarc1.c and lha/pmarc1.h.
- Updated library.h to include declarations for new decompression functions.
- Enhanced tests to cover LHA decompression for LH1, LH6, and PMarc formats.
- Added test helpers for loading LHA payloads and validating decompression results.
- Included sample data files for testing LHA and PMarc decompression.
This commit is contained in:
2026-04-14 20:23:16 +01:00
parent 6f5a9a4539
commit 882b775f71
28 changed files with 3480 additions and 2 deletions

View File

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

98
lha/bitio.c Normal file
View File

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

44
lha/bitio.h Normal file
View File

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

259
lha/huffman.c Normal file
View File

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

57
lha/huffman.h Normal file
View File

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

134
lha/larc.c Normal file
View File

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

30
lha/larc.h Normal file
View File

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

280
lha/lh1.c Normal file
View File

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

28
lha/lh1.h Normal file
View File

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

1217
lha/lh_old.c Normal file

File diff suppressed because it is too large Load Diff

32
lha/lh_old.h Normal file
View File

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

252
lha/lh_static.c Normal file
View File

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

34
lha/lh_static.h Normal file
View File

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

48
lha/lzss.c Normal file
View File

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

68
lha/lzss.h Normal file
View File

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

387
lha/pmarc1.c Normal file
View File

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

29
lha/pmarc1.h Normal file
View File

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

View File

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

View File

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

BIN
tests/data/larc_lz5.lzs Normal file

Binary file not shown.

BIN
tests/data/lha_lh6.lzh Normal file

Binary file not shown.

BIN
tests/data/lharc_lh1.lzh Executable file

Binary file not shown.

BIN
tests/data/pmarc_pm2.pma Executable file

Binary file not shown.

74
tests/lha/larc.cpp Normal file
View File

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

74
tests/lha/lh1.cpp Normal file
View File

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

75
tests/lha/lh_old.cpp Normal file
View File

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

74
tests/lha/lh_static.cpp Normal file
View File

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

122
tests/lha/lha_helpers.h Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef AARU_COMPRESSION_NATIVE__TESTS_LHA_HELPERS_H_
#define AARU_COMPRESSION_NATIVE__TESTS_LHA_HELPERS_H_
#include <climits>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
/*
* 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_ */