diff --git a/CMakeLists.txt b/CMakeLists.txt index 8abe34a..03c3461 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,7 +171,12 @@ add_library("Aaru.Compression.Native" SHARED library.c apple_rle.c apple_rle.h a ace/sound.c ace/pic.c ace/v1.c - ace/v2.c) + ace/v2.c + arj/arj.c + arj/arj.h + arj/arj_fastest.c + arjz/arjz.c + arjz/arjz.h) include(3rdparty/bzip2.cmake) include(3rdparty/flac.cmake) diff --git a/arj/arj.c b/arj/arj.c new file mode 100644 index 0000000..c61c5b8 --- /dev/null +++ b/arj/arj.c @@ -0,0 +1,257 @@ +/* + * 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 "arj.h" +#include "../lha/bitio.h" +#include "../lha/huffman.h" +#include "../lha/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 *arj_parse_code(lha_bitio *bitio, int width, int specialindex) +{ + int num = (int)lha_bitio_next_bits(bitio, width); + + if(num == 0) + { + 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/length Huffman code (uses metacode compression). + */ +static lha_prefix_code *arj_parse_literal_code(lha_bitio *bitio) +{ + lha_prefix_code *metacode = arj_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; +} + +/* + * Block-based static Huffman + LZSS decompression for ARJ methods 1-3. + * window_bits: 15 for standard ARJ, 16 for ARJZ version 51. + */ +static int arj_decompress_lzh(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) + { + blocksize = (int)lha_bitio_next_bits(&bitio, 16); + blockpos = 0; + + lha_prefix_code_free(literalcode); + lha_prefix_code_free(distancecode); + + literalcode = arj_parse_literal_code(&bitio); + distancecode = arj_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 arj_decompress_method1(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len) +{ return arj_decompress_lzh(in_buf, in_len, out_buf, out_len, 15); } + +AARU_EXPORT int AARU_CALL arj_decompress_method2(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len) +{ return arj_decompress_lzh(in_buf, in_len, out_buf, out_len, 15); } + +AARU_EXPORT int AARU_CALL arj_decompress_method3(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len) +{ return arj_decompress_lzh(in_buf, in_len, out_buf, out_len, 15); } + +AARU_EXPORT int AARU_CALL arjz_decompress_method1(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len) +{ return arj_decompress_lzh(in_buf, in_len, out_buf, out_len, 16); } + +AARU_EXPORT int AARU_CALL arjz_decompress_method2(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len) +{ return arj_decompress_lzh(in_buf, in_len, out_buf, out_len, 16); } + +AARU_EXPORT int AARU_CALL arjz_decompress_method3(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len) +{ return arj_decompress_lzh(in_buf, in_len, out_buf, out_len, 16); } diff --git a/arj/arj.h b/arj/arj.h new file mode 100644 index 0000000..41cb46c --- /dev/null +++ b/arj/arj.h @@ -0,0 +1,47 @@ +/* + * 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__ARJ_ARJ_H_ +#define AARU_COMPRESSION_NATIVE__ARJ_ARJ_H_ + +#include +#include +#include "../library.h" + +AARU_EXPORT int AARU_CALL arj_decompress_method1(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len); + +AARU_EXPORT int AARU_CALL arj_decompress_method2(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len); + +AARU_EXPORT int AARU_CALL arj_decompress_method3(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len); + +AARU_EXPORT int AARU_CALL arj_decompress_fastest(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len); + +AARU_EXPORT int AARU_CALL arjz_decompress_method1(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len); + +AARU_EXPORT int AARU_CALL arjz_decompress_method2(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len); + +AARU_EXPORT int AARU_CALL arjz_decompress_method3(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len); + +#endif /* AARU_COMPRESSION_NATIVE__ARJ_ARJ_H_ */ diff --git a/arj/arj_fastest.c b/arj/arj_fastest.c new file mode 100644 index 0000000..7fd215d --- /dev/null +++ b/arj/arj_fastest.c @@ -0,0 +1,118 @@ +/* + * 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 "../lha/bitio.h" +#include "../lha/lzss.h" +#include "arj.h" + +#include + +#define ARJ_FASTEST_WINDOW_SIZE 32768 +#define ARJ_FASTEST_LEN_START 0 +#define ARJ_FASTEST_LEN_STOP 7 +#define ARJ_FASTEST_PTR_START 9 +#define ARJ_FASTEST_PTR_STOP 13 + +/* + * Decode a variable-width length value. + * Reads continuation bits from width LEN_START to LEN_STOP. + * Returns 0 for literal, >0 for match length component. + */ +static int arj_decode_len(lha_bitio *bitio) +{ + int val = 0; + int w = ARJ_FASTEST_LEN_START; + + while(w < ARJ_FASTEST_LEN_STOP) + { + if(!lha_bitio_next_bit(bitio)) break; + + val += 1 << w; + w++; + } + + if(w > ARJ_FASTEST_LEN_START) val += (int)lha_bitio_next_bits(bitio, w); + + return val; +} + +/* + * Decode a variable-width distance/pointer value. + * Reads continuation bits from width PTR_START to PTR_STOP. + */ +static int arj_decode_ptr(lha_bitio *bitio) +{ + int val = 0; + int w = ARJ_FASTEST_PTR_START; + + while(w < ARJ_FASTEST_PTR_STOP) + { + if(!lha_bitio_next_bit(bitio)) break; + + val += 1 << w; + w++; + } + + val += (int)lha_bitio_next_bits(bitio, w); + return val; +} + +/* + * ARJ method 4 ("Fastest") decompression. + * LZSS with variable-width Golomb-Rice codes for length and distance. + * 32KB sliding window, MSB-first bit extraction. + */ +AARU_EXPORT int AARU_CALL arj_decompress_fastest(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, ARJ_FASTEST_WINDOW_SIZE, out_buf, expected)) return -1; + + lha_bitio_init(&bitio, in_buf, in_len); + + while(lzss.out_pos < expected && !lha_bitio_at_eof(&bitio)) + { + int c = arj_decode_len(&bitio); + + if(c == 0) + { + /* Literal byte */ + uint8_t byte = (uint8_t)lha_bitio_next_bits(&bitio, 8); + lha_lzss_emit_literal(&lzss, byte); + } + else + { + /* Match: length = c - 1 + threshold(3) = c + 2 */ + int length = c + 2; + int offset = arj_decode_ptr(&bitio) + 1; + + lha_lzss_emit_match(&lzss, offset, length); + } + } + + *out_len = lzss.out_pos; + lha_lzss_cleanup(&lzss); + return 0; +} diff --git a/arjz/arjz.c b/arjz/arjz.c new file mode 100644 index 0000000..aedcd61 --- /dev/null +++ b/arjz/arjz.c @@ -0,0 +1,1123 @@ +/* + * 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 "arjz.h" + +#include +#include + +/* Constants */ +#define ARJZ_MIN_WINDOW_SIZE 65536 + +#define LENGTH_CODES 29 +#define LITERALS 256 +#define REP_BOTH_LIT 256 +#define MMBLOCK_LIT 257 +#define STR2_LIT 258 +#define STR2_LAST_LIT 265 +#define STR3_LIT 266 +#define STR3_LAST_LIT 301 +#define END_BLOCK_LIT 302 + +#define SPEC_CODES (END_BLOCK_LIT - LITERALS + 1) +#define L_CODES (LITERALS + SPEC_CODES + LENGTH_CODES) + +#define REPDIST_CODES 4 +#define D_CODES_LB 6 +#define D_CODES (REPDIST_CODES + 60) + +#define BL_CODES 19 + +/* Huffman table entry types (stored in e field) */ +#define LIT_CODE 36 +#define EOB_CODE 37 +#define REPB_CODE 38 +#define MM_CODE 39 +#define STR2_CODE 40 +#define STR2_LASTCODE 49 +#define STR3_CODE 50 +#define STR3_LASTCODE 89 +#define REPDIST_CODE 90 +#define REPDIST_LASTCODE 99 +#define TABLE_BASECODE 100 +#define NONSENSE_CODE 119 + +#define BMAX 16 +#define N_MAX 512 + +#define MAX_MMDIST 4 + +/* Huffman table entry */ +typedef struct arjz_huft +{ + uint8_t e; /* extra bits or operation */ + uint8_t b; /* bits in this code */ + + union + { + uint32_t n; /* literal, length base, or distance base */ + struct arjz_huft *t; /* pointer to next level of table */ + } v; +} arjz_huft; + +/* Decompression context */ +typedef struct +{ + /* Input */ + const uint8_t *in_buf; + size_t in_len; + size_t in_pos; + + /* Bit buffer */ + uint32_t bb; + unsigned bk; + + /* Output */ + uint8_t *out_buf; + size_t out_len; + size_t out_pos; + + /* Sliding window */ + uint8_t *window; + size_t window_size; + size_t window_mask; + size_t wp; + size_t lastwp; + + /* MM block state */ + size_t mmstart; + unsigned mmdist; + unsigned mmcount; + unsigned mmtype; + char mmvalue[MAX_MMDIST]; + + /* E8/E9 state */ + unsigned exediff; + size_t filesize; + size_t filepos; + char hold[5]; + int bytes_on_hold; + + /* Stream parameters */ + uint32_t md4; + + /* Huffman table memory pool */ + uint8_t *pool; + size_t pool_size; + size_t pool_used; + + /* Literal/distance code lengths (persistent for DIFF_TREES) */ + unsigned ll[L_CODES + D_CODES]; +} arjz_ctx; + +/* Bit length code order */ +static const unsigned arjz_border[19] = {16, 17, 18, 0, 1, 15, 2, 14, 3, 13, 4, 12, 5, 11, 6, 10, 7, 9, 8}; + +/* Base distances for literal codes (2-byte strings, 3-byte strings, lengths) */ +/* Padded to 256 entries for static block table builder (codes 256-511 need indices 0-255) */ +static const uint32_t arjz_cplens[256] = { + 0, 0, /* 2-byte: */ 1, 5, 9, 17, 33, 65, 129, 193, + /* 3-byte: */ 0, 0, 0, 0, 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, + 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0x8001, 0xC001, 0, + /* Length bases: */ + 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 32, 36, 44, 52, 60, 68, 84, 100, 116, 132, 164, 196, 228, 260, + 0, 0 + /* remaining entries are zero-initialized */ +}; + +/* Extra bits/codes for literal codes */ +/* Padded to 256 entries; extra entries set to NONSENSE_CODE for safety */ +static const uint32_t arjz_cplext[256] = { + REPB_CODE, MM_CODE, + /* 2-byte strings: */ + STR2_CODE + 2, STR2_CODE + 2, STR2_CODE + 3, STR2_CODE + 4, STR2_CODE + 5, STR2_CODE + 6, STR2_CODE + 6, + STR2_CODE + 6, + /* 3-byte strings: */ + REPDIST_CODE, REPDIST_CODE + 1, REPDIST_CODE + 2, REPDIST_CODE + 3, STR3_CODE + 0, STR3_CODE + 0, STR3_CODE + 0, + STR3_CODE + 0, STR3_CODE + 1, STR3_CODE + 1, STR3_CODE + 2, STR3_CODE + 2, STR3_CODE + 3, STR3_CODE + 3, + STR3_CODE + 4, STR3_CODE + 4, STR3_CODE + 5, STR3_CODE + 5, STR3_CODE + 6, STR3_CODE + 6, STR3_CODE + 7, + STR3_CODE + 7, STR3_CODE + 8, STR3_CODE + 8, STR3_CODE + 9, STR3_CODE + 9, STR3_CODE + 10, STR3_CODE + 10, + STR3_CODE + 11, STR3_CODE + 11, STR3_CODE + 12, STR3_CODE + 12, STR3_CODE + 13, STR3_CODE + 13, STR3_CODE + 14, + STR3_CODE + 14, EOB_CODE, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 14, + /* Padding: unused code slots filled with NONSENSE_CODE */ + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, + NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE, NONSENSE_CODE}; + +/* Distance base values */ +static const uint32_t arjz_cpdist[] = { + 0, 0, 0, 0, 1, 2, 3, 4, 5, + 7, 9, 13, 17, 25, 33, 49, 65, 97, + 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, + 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0x8001, 0xC001, + 0x00010001, 0x00018001, 0x00020001, 0x00030001, 0x00040001, 0x00060001, 0x00080001, 0x000C0001, 0x00100001, + 0x00180001, 0x00200001, 0x00300001, 0x00400001, 0x00600001, 0x00800001, 0x00C00001, 0x01000001, 0x01800001, + 0x02000001, 0x03000001, 0x04000001, 0x06000001, 0x08000001, 0x0C000001, 0x10000001, 0x18000001, 0x20000001, + 0x30000001, 0x40000001, 0x60000001, 0x80000001U, 0xC0000001U}; + +/* Distance extra bits */ +static const uint32_t arjz_cpdext[] = {REPDIST_CODE, + REPDIST_CODE + 1, + REPDIST_CODE + 2, + REPDIST_CODE + 3, + 0, + 0, + 0, + 0, + 1, + 1, + 2, + 2, + 3, + 3, + 4, + 4, + 5, + 5, + 6, + 6, + 7, + 7, + 8, + 8, + 9, + 9, + 10, + 10, + 11, + 11, + 12, + 12, + 13, + 13, + 14, + 14, + 15, + 15, + 16, + 16, + 17, + 17, + 18, + 18, + 19, + 19, + 20, + 20, + 21, + 21, + 22, + 22, + 23, + 23, + 24, + 24, + 25, + 25, + 26, + 26, + 27, + 27, + 28, + 28, + 29, + 29, + 30, + 30}; + +/* Bit masks */ +static const uint32_t arjz_mask[] = {0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, + 0x007f, 0x00ff, 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, + 0x3fff, 0x7fff, 0xffff, 0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff, + 0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff, 0x01ffffff, 0x03ffffff, 0x07ffffff, + 0x0fffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff}; + +/* ---- Bit I/O ---- */ + +static int arjz_nextbyte(arjz_ctx *ctx) +{ + if(ctx->in_pos >= ctx->in_len) return -1; + return ctx->in_buf[ctx->in_pos++]; +} + +static int arjz_needbits(arjz_ctx *ctx, unsigned n) +{ + while(ctx->bk < n) + { + int c = arjz_nextbyte(ctx); + if(c < 0) return -1; + ctx->bb |= (uint32_t)c << ctx->bk; + ctx->bk += 8; + } + return 0; +} + +static unsigned arjz_readbits(arjz_ctx *ctx, unsigned n) +{ + unsigned val; + arjz_needbits(ctx, n); + val = (unsigned)(ctx->bb & arjz_mask[n]); + ctx->bb >>= n; + ctx->bk -= n; + return val; +} + +/* ---- Memory pool for Huffman tables ---- */ + +#define ARJZ_POOL_SIZE (256 * 1024) + +static arjz_huft *arjz_pool_alloc(arjz_ctx *ctx, size_t count) +{ + size_t need = count * sizeof(arjz_huft); + arjz_huft *ptr; + + if(ctx->pool_used + need > ctx->pool_size) return NULL; + + ptr = (arjz_huft *)(ctx->pool + ctx->pool_used); + ctx->pool_used += need; + return ptr; +} + +static void arjz_pool_reset(arjz_ctx *ctx) { ctx->pool_used = 0; } + +/* ---- Huffman table builder ---- */ + +static int arjz_huft_build(arjz_ctx *ctx, unsigned *b, unsigned n, unsigned s, const uint32_t *d, const uint32_t *e, + arjz_huft **t, int *m) +{ + unsigned a; + unsigned c[BMAX + 1]; + unsigned el; + unsigned f; + int g; + int h; + unsigned i, j; + int k; + int lx[BMAX + 1]; + int *l = lx + 1; + unsigned *p; + arjz_huft *q; + arjz_huft r; + arjz_huft *u[BMAX]; + unsigned v[N_MAX]; + int w; + unsigned x[BMAX + 1]; + unsigned *xp; + int y; + unsigned z; + + el = n >= END_BLOCK_LIT ? b[END_BLOCK_LIT] : BMAX; + memset(c, 0, sizeof(c)); + p = b; + i = n; + do + { + c[*p]++; + p++; + } while(--i); + + if(c[0] == n) + { + *t = NULL; + *m = 0; + return 0; + } + + for(j = 1; j <= BMAX; j++) + if(c[j]) break; + k = (int)j; + if((unsigned)*m < j) *m = (int)j; + + for(i = BMAX; i; i--) + if(c[i]) break; + g = (int)i; + if((unsigned)*m > i) *m = (int)i; + + for(y = 1 << j; (int)j < (int)i; j++, y <<= 1) + if((y -= (int)c[j]) < 0) return 2; + if((y -= (int)c[i]) < 0) return 2; + c[i] += (unsigned)y; + + x[1] = j = 0; + p = c + 1; + xp = x + 2; + { + unsigned ii = i; + while(--ii) *xp++ = (j += *p++); + } + + p = b; + i = 0; + do + { + if((j = *p++) != 0) v[x[j]++] = i; + } while(++i < n); + + x[0] = i = 0; + p = v; + h = -1; + w = l[-1] = 0; + u[0] = NULL; + q = NULL; + z = 0; + + for(; k <= g; k++) + { + a = c[k]; + while(a--) + { + while(k > w + l[h]) + { + w += l[h++]; + z = (unsigned)(g - w); + if(z > (unsigned)*m) z = (unsigned)*m; + if((f = 1 << (j = (unsigned)(k - w))) > a + 1) + { + f -= a + 1; + xp = c + k; + while(++j < z) + { + if((f <<= 1) <= *++xp) break; + f -= *xp; + } + } + if((unsigned)w + j > el && (unsigned)w < el) j = el - (unsigned)w; + z = 1 << j; + l[h] = (int)j; + + q = arjz_pool_alloc(ctx, z + 1); + if(!q) return 3; + + *t = q + 1; + *(t = &(q->v.t)) = NULL; + u[h] = ++q; + + if(h) + { + x[h] = i; + r.b = (uint8_t)l[h - 1]; + r.e = (uint8_t)(TABLE_BASECODE + j); + r.v.t = q; + j = (i & ((1 << w) - 1)) >> (w - l[h - 1]); + u[h - 1][j] = r; + } + } + + r.b = (uint8_t)(k - w); + if(p >= v + n) + r.e = NONSENSE_CODE; + else if(*p < s) + { + r.e = LIT_CODE; + r.v.n = *p++; + } + else + { + r.e = (uint8_t)e[*p - s]; + r.v.n = d[*p++ - s]; + } + + f = 1 << (k - w); + for(j = i >> w; j < z; j += f) q[j] = r; + + for(j = 1 << (k - 1); i & j; j >>= 1) i ^= j; + i ^= j; + + while((i & ((1 << w) - 1)) != x[h]) w -= l[--h]; + } + } + + *m = l[0]; + return y != 0 && g != 1; +} + +/* ---- Delta encoding (multimedia) ---- */ + +static void arjz_undiff1(uint8_t *buf, unsigned items, unsigned step) +{ + uint8_t prev = 0; + unsigned i; + for(i = 0; i < items; i++) + { + prev += buf[i * step]; + buf[i * step] = prev; + } +} + +static void arjz_undiff2(uint8_t *buf, unsigned items, unsigned step) +{ + int16_t prev = 0; + unsigned i; + for(i = 0; i < items; i++) + { + int16_t val; + memcpy(&val, buf + i * step, 2); + prev += val; + memcpy(buf + i * step, &prev, 2); + } +} + +static void arjz_undiff4(uint8_t *buf, unsigned items, unsigned step) +{ + int32_t prev = 0; + unsigned i; + for(i = 0; i < items; i++) + { + int32_t val; + memcpy(&val, buf + i * step, 4); + prev += val; + memcpy(buf + i * step, &prev, 4); + } +} + +static void arjz_diff1(uint8_t *buf, unsigned items, unsigned step) +{ + uint8_t cur, prev = 0; + unsigned i; + for(i = 0; i < items; i++) + { + cur = buf[i * step]; + buf[i * step] = cur - prev; + prev = cur; + } +} + +static void arjz_diff2(uint8_t *buf, unsigned items, unsigned step) +{ + int16_t cur, prev = 0; + unsigned i; + for(i = 0; i < items; i++) + { + memcpy(&cur, buf + i * step, 2); + int16_t diff = cur - prev; + memcpy(buf + i * step, &diff, 2); + prev = cur; + } +} + +static void arjz_diff4(uint8_t *buf, unsigned items, unsigned step) +{ + int32_t cur, prev = 0; + unsigned i; + for(i = 0; i < items; i++) + { + memcpy(&cur, buf + i * step, 4); + int32_t diff = cur - prev; + memcpy(buf + i * step, &diff, 4); + prev = cur; + } +} + +/* ---- E8/E9 post-processing ---- */ + +static int32_t arjz_untranslate(int32_t n, int32_t pos, int32_t fsize) +{ + if(n > 0 && n < fsize) + n -= pos; + else if(n <= 0 && n > -pos) + n += fsize - 1; + return n; +} + +static void arjz_e8_process(arjz_ctx *ctx, uint8_t *buf, size_t len) +{ + size_t i = 0; + + while(i < len) + { + if(buf[i] == 0xE8 || buf[i] == 0xE9) + { + if(i + 5 <= len) + { + int32_t pos = (int32_t)(ctx->filepos + i + 5); + int32_t val; + memcpy(&val, buf + i + 1, 4); + val = arjz_untranslate(val, pos, (int32_t)ctx->filesize); + memcpy(buf + i + 1, &val, 4); + i += 5; + continue; + } + } + i++; + } +} + +/* ---- MM block write-out ---- */ + +static void arjz_apply_mm(arjz_ctx *ctx, uint8_t *buf, size_t start, size_t bytes) +{ + if(ctx->mmcount > 0 && start < bytes) + { + uint8_t *mm_start = buf + start; + unsigned cnt = (bytes - start) / ctx->mmdist + 1; + + if(cnt <= 1) return; + if(cnt > ctx->mmcount) cnt = ctx->mmcount; + + /* Save delta values before undiff */ + char mmtmp[MAX_MMDIST]; + memcpy(mmtmp, mm_start, ctx->mmdist); + + if(ctx->mmtype == 2) memcpy(mm_start, ctx->mmvalue, ctx->mmdist); + + switch(ctx->mmdist) + { + case 1: + arjz_undiff1(mm_start, cnt, ctx->mmdist); + break; + case 2: + arjz_undiff2(mm_start, cnt, ctx->mmdist); + break; + case 3: + arjz_undiff1(mm_start, cnt, ctx->mmdist); + arjz_undiff1(mm_start + 1, cnt, ctx->mmdist); + arjz_undiff1(mm_start + 2, cnt, ctx->mmdist); + break; + case 4: + arjz_undiff4(mm_start, cnt, ctx->mmdist); + break; + } + + /* Save partial sum for continuation */ + memcpy(ctx->mmvalue, mm_start + (cnt - 2) * ctx->mmdist, ctx->mmdist); + + /* Re-apply diff for continued processing */ + switch(ctx->mmdist) + { + case 1: + arjz_diff1(mm_start, cnt, ctx->mmdist); + break; + case 2: + arjz_diff2(mm_start, cnt, ctx->mmdist); + break; + case 3: + arjz_diff1(mm_start, cnt, ctx->mmdist); + arjz_diff1(mm_start + 1, cnt, ctx->mmdist); + arjz_diff1(mm_start + 2, cnt, ctx->mmdist); + break; + case 4: + arjz_diff4(mm_start, cnt, ctx->mmdist); + break; + } + + memcpy(mm_start, mmtmp, ctx->mmdist); + + if((bytes - start) / ctx->mmdist >= ctx->mmcount) + ctx->mmcount = 0; + else + { + ctx->mmcount -= cnt - 2; + ctx->mmstart += (cnt - 2) * ctx->mmdist; + ctx->mmtype = 2; + } + } +} + +/* ---- Flush window to output ---- */ + +static void arjz_flush(arjz_ctx *ctx, size_t end) +{ + size_t count = end - ctx->lastwp; + uint8_t *src = ctx->window + ctx->lastwp; + + /* Apply MM block processing */ + if(ctx->mmcount) + { + unsigned mm_rel = 0; + if(ctx->mmstart >= ctx->lastwp) mm_rel = ctx->mmstart - ctx->lastwp; + arjz_apply_mm(ctx, src, mm_rel, count); + } + + /* Apply E8/E9 processing */ + if(ctx->exediff) arjz_e8_process(ctx, src, count); + + /* Copy to output */ + if(ctx->out_pos + count > ctx->out_len) count = ctx->out_len - ctx->out_pos; + + memcpy(ctx->out_buf + ctx->out_pos, src, count); + ctx->out_pos += count; + ctx->filepos += count; + ctx->lastwp = end; +} + +/* ---- Window management ---- */ + +static void arjz_output_char(arjz_ctx *ctx, size_t *wp, uint8_t c) +{ + ctx->window[*wp & ctx->window_mask] = c; + (*wp)++; + + if(*wp >= ctx->window_size) + { + arjz_flush(ctx, ctx->window_size); + *wp = 0; + ctx->lastwp = 0; + ctx->mmstart = 0; + } +} + +static void arjz_output_string(arjz_ctx *ctx, size_t *wp, uint32_t dist, unsigned len) +{ + unsigned i; + + for(i = 0; i < len; i++) + { + size_t src = (*wp - (size_t)dist) & ctx->window_mask; + uint8_t byte = ctx->window[src]; + ctx->window[*wp & ctx->window_mask] = byte; + (*wp)++; + + if(*wp >= ctx->window_size) + { + arjz_flush(ctx, ctx->window_size); + *wp = 0; + ctx->lastwp = 0; + ctx->mmstart = 0; + } + } +} + +/* ---- Inflate stored block ---- */ + +static int arjz_inflate_stored(arjz_ctx *ctx) +{ + unsigned n; + size_t w; + + arjz_readbits(ctx, ctx->bk & 7); /* align to byte boundary */ + n = arjz_readbits(ctx, 16); + + if(n != (arjz_readbits(ctx, 16) ^ 0xffff)) return -1; + + w = ctx->wp; + + while(n--) { arjz_output_char(ctx, &w, (uint8_t)arjz_readbits(ctx, 8)); } + + ctx->wp = w; + return 0; +} + +/* ---- Inflate coded block ---- */ + +static int arjz_inflate_codes(arjz_ctx *ctx, arjz_huft *tl, arjz_huft *td, int bl, int bd) +{ + unsigned e; + unsigned n; + uint32_t o; + size_t w; + arjz_huft *t; + unsigned ml, md; + + uint32_t lastdist[REPDIST_CODES]; + unsigned lastptr; + + w = ctx->wp; + ml = arjz_mask[bl]; + md = arjz_mask[bd]; + + lastptr = 0; + for(n = 0; n < REPDIST_CODES; n++) lastdist[n] = 1; + n = 1; + o = 1; + + for(;;) + { + if(arjz_needbits(ctx, (unsigned)bl) < 0) return -1; + t = tl + ((unsigned)ctx->bb & ml); + e = t->e; + + while(e > TABLE_BASECODE) + { + if(e == NONSENSE_CODE) return -1; + ctx->bb >>= t->b; + ctx->bk -= t->b; + e -= TABLE_BASECODE; + if(arjz_needbits(ctx, e) < 0) return -1; + t = t->v.t + ((unsigned)ctx->bb & arjz_mask[e]); + e = t->e; + } + + ctx->bb >>= t->b; + ctx->bk -= t->b; + + if(e == LIT_CODE) { arjz_output_char(ctx, &w, (uint8_t)t->v.n); } + else if(e == EOB_CODE) { break; } + else if(e == MM_CODE) + { + /* Flush before MM block */ + if(ctx->mmcount || ctx->exediff) arjz_flush(ctx, w); + + if(arjz_needbits(ctx, 2) < 0) return -1; + ctx->mmdist = 1 + ((unsigned)ctx->bb & 3); + ctx->bb >>= 2; + ctx->bk -= 2; + ctx->mmstart = w - ctx->mmdist; + + /* Decode MM count using distance table */ + if(arjz_needbits(ctx, (unsigned)bd) < 0) return -1; + t = td + ((unsigned)ctx->bb & md); + e = t->e; + while(e > TABLE_BASECODE) + { + if(e == NONSENSE_CODE) return -1; + ctx->bb >>= t->b; + ctx->bk -= t->b; + e -= TABLE_BASECODE; + if(arjz_needbits(ctx, e) < 0) return -1; + t = t->v.t + ((unsigned)ctx->bb & arjz_mask[e]); + e = t->e; + } + ctx->bb >>= t->b; + ctx->bk -= t->b; + + if(arjz_needbits(ctx, e) < 0) return -1; + ctx->mmcount = t->v.n + ((unsigned)ctx->bb & arjz_mask[e]); + ctx->bb >>= e; + ctx->bk -= e; + + ctx->mmtype = 1; + } + else + { + /* String/match codes */ + if(e >= STR2_CODE && e <= STR2_LASTCODE) + { + e -= STR2_CODE; + if(e > 0) + { + if(arjz_needbits(ctx, e) < 0) return -1; + } + o = t->v.n + (e > 0 ? ((unsigned)ctx->bb & arjz_mask[e]) : 0); + if(e > 0) + { + ctx->bb >>= e; + ctx->bk -= e; + } + n = 2; + } + else if(e >= REPDIST_CODE && e <= REPDIST_LASTCODE) + { + e -= REPDIST_CODE; + o = lastdist[(lastptr - e) % REPDIST_CODES]; + n = 3; + if(!e) goto skip_save; + } + else if(e >= STR3_CODE && e <= STR3_LASTCODE) + { + e -= STR3_CODE; + if(e > 0) + { + if(arjz_needbits(ctx, e) < 0) return -1; + } + o = t->v.n + (e > 0 ? ((unsigned)ctx->bb & arjz_mask[e]) : 0); + if(e > 0) + { + ctx->bb >>= e; + ctx->bk -= e; + } + n = 3; + } + else if(e == REPB_CODE) + { + /* Repeat both last length and offset */ + goto skip_save; + } + else + { + /* Standard length+distance */ + if(arjz_needbits(ctx, e) < 0) return -1; + n = t->v.n + ((unsigned)ctx->bb & arjz_mask[e]); + ctx->bb >>= e; + ctx->bk -= e; + + /* Decode distance */ + if(arjz_needbits(ctx, (unsigned)bd) < 0) return -1; + t = td + ((unsigned)ctx->bb & md); + e = t->e; + while(e > TABLE_BASECODE) + { + if(e == NONSENSE_CODE) return -1; + ctx->bb >>= t->b; + ctx->bk -= t->b; + e -= TABLE_BASECODE; + if(arjz_needbits(ctx, e) < 0) return -1; + t = t->v.t + ((unsigned)ctx->bb & arjz_mask[e]); + e = t->e; + } + ctx->bb >>= t->b; + ctx->bk -= t->b; + + if(e >= REPDIST_CODE) + { + e -= REPDIST_CODE; + o = lastdist[(lastptr - e) % REPDIST_CODES]; + if(!e) goto skip_save; + } + else + { + o = t->v.n; + if(e > 25) + { + if(arjz_needbits(ctx, 16) < 0) return -1; + o += ((unsigned)ctx->bb & 0xFFFF); + ctx->bb >>= 16; + ctx->bk -= 16; + e -= 16; + } + if(e > 0) + { + if(arjz_needbits(ctx, e) < 0) return -1; + if(e <= 16) + o += ((unsigned)ctx->bb & arjz_mask[e]); + else + o += ((uint32_t)ctx->bb & arjz_mask[e]); + ctx->bb >>= e; + ctx->bk -= e; + } + } + } + + lastptr = (lastptr + 1) % REPDIST_CODES; + lastdist[lastptr] = o; + + skip_save: + { + unsigned cnt = n; + if(o > ctx->md4) cnt++; + arjz_output_string(ctx, &w, o, cnt); + } + } + } + + ctx->wp = w; + return 0; +} + +/* ---- Inflate packed (dynamic or static Huffman) block ---- */ + +static int arjz_inflate_packed(arjz_ctx *ctx, int is_static) +{ + unsigned i, j, l, n; + unsigned m; + arjz_huft *tl = NULL; + arjz_huft *td = NULL; + unsigned lll[BL_CODES]; + int bl, bd; + unsigned nb, nl, nd; + int ret; + + arjz_pool_reset(ctx); + + if(is_static) + { + unsigned sl[512]; + for(i = 0; i < 512; i++) sl[i] = 9; + bl = 9; + ret = arjz_huft_build(ctx, sl, 512, LITERALS, arjz_cplens, arjz_cplext, &tl, &bl); + if(ret != 0 && ret != 1) return -1; + + for(i = 0; i < D_CODES; i++) sl[i] = D_CODES_LB; + bd = D_CODES_LB; + ret = arjz_huft_build(ctx, sl, D_CODES, 0, arjz_cpdist, arjz_cpdext, &td, &bd); + if(ret > 1) return -1; + + ret = arjz_inflate_codes(ctx, tl, td, bl, bd); + return ret; + } + + nl = LITERALS + SPEC_CODES + arjz_readbits(ctx, 5); + nd = 1 + arjz_readbits(ctx, D_CODES_LB); + nb = 4 + arjz_readbits(ctx, 4); + + if(nl > L_CODES || nd > D_CODES) return -1; + + memset(lll, 0, sizeof(lll)); + for(j = 0; j < nb; j++) lll[arjz_border[j]] = arjz_readbits(ctx, 3); + + bl = 7; + ret = arjz_huft_build(ctx, lll, BL_CODES, BL_CODES, NULL, NULL, &tl, &bl); + if(ret != 0 && ret != 1) return -1; + + n = L_CODES + nd; + m = arjz_mask[bl]; + i = 0; + l = 0; + + while(i < n) + { + if(i == nl) + { + while(i < L_CODES) ctx->ll[i++] = 0; + } + + if(arjz_needbits(ctx, (unsigned)bl) < 0) return -1; + td = tl + ((unsigned)ctx->bb & m); + j = td->b; + ctx->bb >>= j; + ctx->bk -= j; + j = td->v.n; + + if(j < 16) { ctx->ll[i++] += l = j; } + else if(j == 16) + { + j = 3 + arjz_readbits(ctx, 2); + if(i + j > n) return -1; + while(j--) ctx->ll[i++] += l; + } + else if(j == 17) + { + j = 3 + arjz_readbits(ctx, 3); + if(i + j > n) return -1; + while(j--) ctx->ll[i++] += 0; + l = 0; + } + else + { + j = 11 + arjz_readbits(ctx, 7); + if(i + j > n) return -1; + while(j--) ctx->ll[i++] += 0; + l = 0; + } + } + + while(i < L_CODES + D_CODES) ctx->ll[i++] = 0; + + for(i = 0; i < L_CODES + D_CODES; i++) ctx->ll[i] &= 15; + + arjz_pool_reset(ctx); + + bl = 10; + ret = arjz_huft_build(ctx, ctx->ll, nl, LITERALS, arjz_cplens, arjz_cplext, &tl, &bl); + if(ret != 0 && ret != 1) return -1; + + bd = D_CODES_LB; + ret = arjz_huft_build(ctx, ctx->ll + L_CODES, nd, 0, arjz_cpdist, arjz_cpdext, &td, &bd); + if(ret != 0 && ret != 1) return -1; + + ret = arjz_inflate_codes(ctx, tl, td, bl, bd); + return ret; +} + +/* ---- Main inflate entry point ---- */ + +AARU_EXPORT int AARU_CALL arjz_decompress_buffer(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len, size_t orig_size) +{ + arjz_ctx ctx; + int last_block; + int byte0, byte1; + + if(!in_buf || !out_buf || !out_len || in_len == 0) return -1; + + memset(&ctx, 0, sizeof(ctx)); + ctx.in_buf = in_buf; + ctx.in_len = in_len; + ctx.out_buf = out_buf; + ctx.out_len = *out_len; + ctx.filesize = orig_size; + + /* Compute window size: smallest power-of-2 >= orig_size, minimum 64KB */ + { + size_t ws = ARJZ_MIN_WINDOW_SIZE; + while(ws < orig_size && ws < (size_t)1 << 30) ws <<= 1; + ctx.window_size = ws; + ctx.window_mask = ws - 1; + } + + ctx.window = (uint8_t *)calloc(ctx.window_size + MAX_MMDIST * 2, 1); + if(!ctx.window) return -1; + + ctx.pool_size = ARJZ_POOL_SIZE; + ctx.pool = (uint8_t *)malloc(ctx.pool_size); + if(!ctx.pool) + { + free(ctx.window); + return -1; + } + + /* Read stream header: md4 parameter and exediff flag */ + byte0 = (int)arjz_readbits(&ctx, 8); + byte1 = (int)arjz_readbits(&ctx, 8); + + if(--byte0 < 0) + ctx.md4 = (uint32_t)byte1; + else + ctx.md4 = (uint32_t)(byte1 + 256) << byte0; + + ctx.exediff = arjz_readbits(&ctx, 1); + + memset(ctx.ll, 0, sizeof(ctx.ll)); + + do + { + last_block = (int)arjz_readbits(&ctx, 1); + unsigned block_type = arjz_readbits(&ctx, 2); + int ret; + + switch(block_type) + { + case 0: + ret = arjz_inflate_stored(&ctx); + break; + case 1: + ret = arjz_inflate_packed(&ctx, 1); + break; + case 2: + ret = arjz_inflate_packed(&ctx, 0); + break; + default: + ret = -1; + break; + } + + if(ret != 0) + { + free(ctx.pool); + free(ctx.window); + return -1; + } + } while(!last_block); + + /* Flush remaining data */ + if(ctx.wp != ctx.lastwp) arjz_flush(&ctx, ctx.wp); + + *out_len = ctx.out_pos; + free(ctx.pool); + free(ctx.window); + return 0; +} diff --git a/arjz/arjz.h b/arjz/arjz.h new file mode 100644 index 0000000..5fc7b80 --- /dev/null +++ b/arjz/arjz.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__ARJZ_ARJZ_H_ +#define AARU_COMPRESSION_NATIVE__ARJZ_ARJZ_H_ + +#include +#include +#include "../library.h" + +AARU_EXPORT int AARU_CALL arjz_decompress_buffer(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, + size_t *out_len, size_t orig_size); + +#endif /* AARU_COMPRESSION_NATIVE__ARJZ_ARJZ_H_ */ diff --git a/library.h b/library.h index 2130d4e..3cd7d57 100644 --- a/library.h +++ b/library.h @@ -240,4 +240,29 @@ AARU_EXPORT int AARU_CALL ace_decompress_lz77(const uint8_t *in_buf, size_t in_l // ACE v2 (Blocked) decompression AARU_EXPORT int AARU_CALL ace_decompress_blocked(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len, int dic_bits); +// ARJ Method 1 (LZH, most compression) +AARU_EXPORT int AARU_CALL arj_decompress_method1(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// ARJ Method 2 (LZH, medium compression) +AARU_EXPORT int AARU_CALL arj_decompress_method2(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// ARJ Method 3 (LZH, fast compression) +AARU_EXPORT int AARU_CALL arj_decompress_method3(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// ARJ Method 4 (Fastest, variable-width LZSS) +AARU_EXPORT int AARU_CALL arj_decompress_fastest(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// ARJZ Method 1 (LZH, 64KB window) +AARU_EXPORT int AARU_CALL arjz_decompress_method1(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// ARJZ Method 2 (LZH, 64KB window) +AARU_EXPORT int AARU_CALL arjz_decompress_method2(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// ARJZ Method 3 (LZH, 64KB window) +AARU_EXPORT int AARU_CALL arjz_decompress_method3(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len); + +// ARJZ custom extended DEFLATE decompression +AARU_EXPORT int AARU_CALL arjz_decompress_buffer(const uint8_t *in_buf, size_t in_len, uint8_t *out_buf, size_t *out_len, + size_t orig_size); + #endif // AARU_COMPRESSION_NATIVE_LIBRARY_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index abb0848..53997d8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -96,11 +96,31 @@ file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/ace_v1_lz77.bin file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/ace_v2_blocked.bin DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/arj_m1.bin + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/arj_m2.bin + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/arj_m3.bin + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/arj_m4.bin + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/arjz_default.bin + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + +file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/data/arjz_v55_new.bin + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/data/) + # 'Google_Tests_run' is the target name # 'test1.cpp tests2.cpp' are source files with tests add_executable(tests_run apple_rle.cpp crc32.c crc32.h adc.cpp bzip2.cpp lzip.cpp lzfse.cpp zstd.cpp lzma.cpp flac.cpp lz4.cpp 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 lha/lh_static.cpp lha/lh1.cpp lha/larc.cpp lha/lh_old.cpp - ace/ace.cpp) + ace/ace.cpp + arj/arj.cpp + arjz/arjz.cpp) target_link_libraries(tests_run gtest gtest_main "Aaru.Compression.Native") diff --git a/tests/arj/arj.cpp b/tests/arj/arj.cpp new file mode 100644 index 0000000..af0fd08 --- /dev/null +++ b/tests/arj/arj.cpp @@ -0,0 +1,175 @@ +/* + * This file is part of the Aaru Data Preservation Suite. + * Copyright (c) 2019-2026 Natalia Portillo. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "../../library.h" +#include "../crc32.h" +#include "gtest/gtest.h" + +#define EXPECTED_CRC32 0x66007DBA +#define EXPECTED_ORIGSIZE 152089 + +/* ---- Method 1 ---- */ + +static const uint8_t *buffer_m1; + +class arj_m1Fixture : public ::testing::Test +{ +protected: + void SetUp() + { + char path[PATH_MAX]; + char filename[PATH_MAX]; + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/arj_m1.bin", path); + FILE *file = fopen(filename, "rb"); + buffer_m1 = (const uint8_t *)malloc(55125); + fread((void *)buffer_m1, 1, 55125, file); + fclose(file); + } + void TearDown() { free((void *)buffer_m1); } +}; + +TEST_F(arj_m1Fixture, arj_method1) +{ + size_t destLen = EXPECTED_ORIGSIZE; + auto *outBuf = (uint8_t *)malloc(EXPECTED_ORIGSIZE); + + auto err = arj_decompress_method1(buffer_m1, 55125, outBuf, &destLen); + + EXPECT_EQ(err, 0); + EXPECT_EQ(destLen, (size_t)EXPECTED_ORIGSIZE); + + auto crc = crc32_data(outBuf, EXPECTED_ORIGSIZE); + free(outBuf); + EXPECT_EQ(crc, EXPECTED_CRC32); +} + +/* ---- Method 2 ---- */ + +static const uint8_t *buffer_m2; + +class arj_m2Fixture : public ::testing::Test +{ +protected: + void SetUp() + { + char path[PATH_MAX]; + char filename[PATH_MAX]; + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/arj_m2.bin", path); + FILE *file = fopen(filename, "rb"); + buffer_m2 = (const uint8_t *)malloc(55721); + fread((void *)buffer_m2, 1, 55721, file); + fclose(file); + } + void TearDown() { free((void *)buffer_m2); } +}; + +TEST_F(arj_m2Fixture, arj_method2) +{ + size_t destLen = EXPECTED_ORIGSIZE; + auto *outBuf = (uint8_t *)malloc(EXPECTED_ORIGSIZE); + + auto err = arj_decompress_method2(buffer_m2, 55721, outBuf, &destLen); + + EXPECT_EQ(err, 0); + EXPECT_EQ(destLen, (size_t)EXPECTED_ORIGSIZE); + + auto crc = crc32_data(outBuf, EXPECTED_ORIGSIZE); + free(outBuf); + EXPECT_EQ(crc, EXPECTED_CRC32); +} + +/* ---- Method 3 ---- */ + +static const uint8_t *buffer_m3; + +class arj_m3Fixture : public ::testing::Test +{ +protected: + void SetUp() + { + char path[PATH_MAX]; + char filename[PATH_MAX]; + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/arj_m3.bin", path); + FILE *file = fopen(filename, "rb"); + buffer_m3 = (const uint8_t *)malloc(59190); + fread((void *)buffer_m3, 1, 59190, file); + fclose(file); + } + void TearDown() { free((void *)buffer_m3); } +}; + +TEST_F(arj_m3Fixture, arj_method3) +{ + size_t destLen = EXPECTED_ORIGSIZE; + auto *outBuf = (uint8_t *)malloc(EXPECTED_ORIGSIZE); + + auto err = arj_decompress_method3(buffer_m3, 59190, outBuf, &destLen); + + EXPECT_EQ(err, 0); + EXPECT_EQ(destLen, (size_t)EXPECTED_ORIGSIZE); + + auto crc = crc32_data(outBuf, EXPECTED_ORIGSIZE); + free(outBuf); + EXPECT_EQ(crc, EXPECTED_CRC32); +} + +/* ---- Method 4 (Fastest) ---- */ + +static const uint8_t *buffer_m4; + +class arj_m4Fixture : public ::testing::Test +{ +protected: + void SetUp() + { + char path[PATH_MAX]; + char filename[PATH_MAX]; + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/arj_m4.bin", path); + FILE *file = fopen(filename, "rb"); + buffer_m4 = (const uint8_t *)malloc(66519); + fread((void *)buffer_m4, 1, 66519, file); + fclose(file); + } + void TearDown() { free((void *)buffer_m4); } +}; + +TEST_F(arj_m4Fixture, arj_method4_fastest) +{ + size_t destLen = EXPECTED_ORIGSIZE; + auto *outBuf = (uint8_t *)malloc(EXPECTED_ORIGSIZE); + + auto err = arj_decompress_fastest(buffer_m4, 66519, outBuf, &destLen); + + EXPECT_EQ(err, 0); + EXPECT_EQ(destLen, (size_t)EXPECTED_ORIGSIZE); + + auto crc = crc32_data(outBuf, EXPECTED_ORIGSIZE); + free(outBuf); + EXPECT_EQ(crc, EXPECTED_CRC32); +} diff --git a/tests/arjz/arjz.cpp b/tests/arjz/arjz.cpp new file mode 100644 index 0000000..795e551 --- /dev/null +++ b/tests/arjz/arjz.cpp @@ -0,0 +1,101 @@ +/* + * This file is part of the Aaru Data Preservation Suite. + * Copyright (c) 2019-2026 Natalia Portillo. + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of the + * License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include "../../library.h" +#include "../crc32.h" +#include "gtest/gtest.h" + +#define EXPECTED_CRC32 0x66007DBA +#define EXPECTED_ORIGSIZE 152089 + +static const uint8_t *buffer_arjz; + +class arjz_defaultFixture : public ::testing::Test +{ +protected: + void SetUp() + { + char path[PATH_MAX]; + char filename[PATH_MAX]; + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/arjz_default.bin", path); + FILE *file = fopen(filename, "rb"); + buffer_arjz = (const uint8_t *)malloc(52577); + fread((void *)buffer_arjz, 1, 52577, file); + fclose(file); + } + void TearDown() { free((void *)buffer_arjz); } +}; + +TEST_F(arjz_defaultFixture, arjz_default) +{ + size_t destLen = EXPECTED_ORIGSIZE; + auto *outBuf = (uint8_t *)malloc(EXPECTED_ORIGSIZE); + + auto err = arjz_decompress_method1(buffer_arjz, 52577, outBuf, &destLen); + + EXPECT_EQ(err, 0); + EXPECT_EQ(destLen, (size_t)EXPECTED_ORIGSIZE); + + auto crc = crc32_data(outBuf, EXPECTED_ORIGSIZE); + free(outBuf); + EXPECT_EQ(crc, EXPECTED_CRC32); +} + +/* ---- ARJZ v55 (custom extended DEFLATE) ---- */ + +static const uint8_t *buffer_arjz_v55; + +class arjz_v55Fixture : public ::testing::Test +{ +protected: + void SetUp() + { + char path[PATH_MAX]; + char filename[PATH_MAX]; + getcwd(path, PATH_MAX); + snprintf(filename, PATH_MAX, "%s/data/arjz_v55_new.bin", path); + FILE *file = fopen(filename, "rb"); + buffer_arjz_v55 = (const uint8_t *)malloc(52010); + fread((void *)buffer_arjz_v55, 1, 52010, file); + fclose(file); + } + void TearDown() { free((void *)buffer_arjz_v55); } +}; + +TEST_F(arjz_v55Fixture, arjz_v55_deflatez) +{ + size_t destLen = EXPECTED_ORIGSIZE; + auto *outBuf = (uint8_t *)malloc(EXPECTED_ORIGSIZE); + + auto err = arjz_decompress_buffer(buffer_arjz_v55, 52010, outBuf, &destLen, EXPECTED_ORIGSIZE); + + EXPECT_EQ(err, 0); + EXPECT_EQ(destLen, (size_t)EXPECTED_ORIGSIZE); + + auto crc = crc32_data(outBuf, EXPECTED_ORIGSIZE); + free(outBuf); + EXPECT_EQ(crc, EXPECTED_CRC32); +} diff --git a/tests/data/arj_m1.bin b/tests/data/arj_m1.bin new file mode 100644 index 0000000..3bb0486 Binary files /dev/null and b/tests/data/arj_m1.bin differ diff --git a/tests/data/arj_m2.bin b/tests/data/arj_m2.bin new file mode 100644 index 0000000..ea67017 Binary files /dev/null and b/tests/data/arj_m2.bin differ diff --git a/tests/data/arj_m3.bin b/tests/data/arj_m3.bin new file mode 100644 index 0000000..601dc19 Binary files /dev/null and b/tests/data/arj_m3.bin differ diff --git a/tests/data/arj_m4.bin b/tests/data/arj_m4.bin new file mode 100644 index 0000000..0c0cbd2 Binary files /dev/null and b/tests/data/arj_m4.bin differ diff --git a/tests/data/arjz_default.bin b/tests/data/arjz_default.bin new file mode 100644 index 0000000..26b9650 Binary files /dev/null and b/tests/data/arjz_default.bin differ diff --git a/tests/data/arjz_v55_new.bin b/tests/data/arjz_v55_new.bin new file mode 100644 index 0000000..3dac71f Binary files /dev/null and b/tests/data/arjz_v55_new.bin differ diff --git a/tests/data/arjz_v55_old.bin b/tests/data/arjz_v55_old.bin new file mode 100644 index 0000000..a49d2a8 Binary files /dev/null and b/tests/data/arjz_v55_old.bin differ