From 15c05c65e71a9457bd2fab8670ec8fbdffbd9636 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 24 May 2022 16:52:41 -0700 Subject: [PATCH] Partial classes --- .../External/libmspack/CAB/Decompressor.cs | 2 +- .../External/libmspack/CHM/DecompressState.cs | 2 +- .../libmspack/Compression/BufferState.cs | 78 -- .../Compression/CompressionStream.ReadBits.cs | 293 +++++ .../Compression/CompressionStream.ReadHuff.cs | 342 ++++++ .../Compression/CompressionStream.cs | 652 +--------- .../Compression/LZHKWAJ.Decompress.cs | 293 +++++ .../libmspack/Compression/LZHKWAJ.ReadBits.cs | 64 + .../libmspack/Compression/LZHKWAJ.ReadHuff.cs | 17 + .../External/libmspack/Compression/LZHKWAJ.cs | 262 +---- .../libmspack/Compression/LZHKWAJStream.cs | 144 --- .../libmspack/Compression/LZX.Decompress.cs | 1045 +++++++++++++++++ .../libmspack/Compression/LZX.ReadBits.cs | 34 + .../libmspack/Compression/LZX.ReadHuff.cs | 20 + .../External/libmspack/Compression/LZX.cs | 590 ++-------- .../libmspack/Compression/LZXDStream.cs | 695 ----------- .../libmspack/Compression/MSZIP.Decompress.cs | 617 ++++++++++ .../libmspack/Compression/MSZIP.ReadBits.cs | 27 + .../libmspack/Compression/MSZIP.ReadHuff.cs | 20 + .../External/libmspack/Compression/MSZIP.cs | 631 +--------- .../libmspack/Compression/MSZIPDStream.cs | 70 -- .../libmspack/Compression/QTM.Decompress.cs | 546 +++++++++ .../libmspack/Compression/QTM.ReadBits.cs | 34 + .../libmspack/Compression/QTM.ReadHuff.cs | 22 + .../External/libmspack/Compression/QTM.cs | 580 ++------- .../libmspack/Compression/QTMDStream.cs | 149 --- .../External/libmspack/KWAJ/Decompressor.cs | 8 +- .../External/libmspack/OAB/Decompressor.cs | 6 +- 28 files changed, 3601 insertions(+), 3642 deletions(-) delete mode 100644 BurnOutSharp/External/libmspack/Compression/BufferState.cs create mode 100644 BurnOutSharp/External/libmspack/Compression/CompressionStream.ReadBits.cs create mode 100644 BurnOutSharp/External/libmspack/Compression/CompressionStream.ReadHuff.cs create mode 100644 BurnOutSharp/External/libmspack/Compression/LZHKWAJ.Decompress.cs create mode 100644 BurnOutSharp/External/libmspack/Compression/LZHKWAJ.ReadBits.cs create mode 100644 BurnOutSharp/External/libmspack/Compression/LZHKWAJ.ReadHuff.cs delete mode 100644 BurnOutSharp/External/libmspack/Compression/LZHKWAJStream.cs create mode 100644 BurnOutSharp/External/libmspack/Compression/LZX.Decompress.cs create mode 100644 BurnOutSharp/External/libmspack/Compression/LZX.ReadBits.cs create mode 100644 BurnOutSharp/External/libmspack/Compression/LZX.ReadHuff.cs delete mode 100644 BurnOutSharp/External/libmspack/Compression/LZXDStream.cs create mode 100644 BurnOutSharp/External/libmspack/Compression/MSZIP.Decompress.cs create mode 100644 BurnOutSharp/External/libmspack/Compression/MSZIP.ReadBits.cs create mode 100644 BurnOutSharp/External/libmspack/Compression/MSZIP.ReadHuff.cs delete mode 100644 BurnOutSharp/External/libmspack/Compression/MSZIPDStream.cs create mode 100644 BurnOutSharp/External/libmspack/Compression/QTM.Decompress.cs create mode 100644 BurnOutSharp/External/libmspack/Compression/QTM.ReadBits.cs create mode 100644 BurnOutSharp/External/libmspack/Compression/QTM.ReadHuff.cs delete mode 100644 BurnOutSharp/External/libmspack/Compression/QTMDStream.cs diff --git a/BurnOutSharp/External/libmspack/CAB/Decompressor.cs b/BurnOutSharp/External/libmspack/CAB/Decompressor.cs index 65e7c14e..90d88e41 100644 --- a/BurnOutSharp/External/libmspack/CAB/Decompressor.cs +++ b/BurnOutSharp/External/libmspack/CAB/Decompressor.cs @@ -599,7 +599,7 @@ namespace LibMSPackSharp.CAB { // Special LZX hack -- on the last block, inform LZX of the // size of the output data stream. - LZX.SetOutputLength(self.State.DecompressorState as LZXDStream, self.State.Outlen); + (self.State.DecompressorState as LZX).SetOutputLength(self.State.Outlen); } } } diff --git a/BurnOutSharp/External/libmspack/CHM/DecompressState.cs b/BurnOutSharp/External/libmspack/CHM/DecompressState.cs index ab76a42b..8330c5e2 100644 --- a/BurnOutSharp/External/libmspack/CHM/DecompressState.cs +++ b/BurnOutSharp/External/libmspack/CHM/DecompressState.cs @@ -31,6 +31,6 @@ namespace LibMSPackSharp.CHM /// /// LZX decompressor state /// - public LZXDStream State { get; set; } + public LZX State { get; set; } } } diff --git a/BurnOutSharp/External/libmspack/Compression/BufferState.cs b/BurnOutSharp/External/libmspack/Compression/BufferState.cs deleted file mode 100644 index 4d52c162..00000000 --- a/BurnOutSharp/External/libmspack/Compression/BufferState.cs +++ /dev/null @@ -1,78 +0,0 @@ -/* This file is part of libmspack. - * (C) 2003-2013 Stuart Caie. - * - * The LZX method was created by Jonathan Forbes and Tomi Poutanen, adapted - * by Microsoft Corporation. - * - * libmspack is free software { get; set; } you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License (LGPL) version 2.1 - * - * For further details, see the file COPYING.LIB distributed with libmspack - */ - -namespace LibMSPackSharp.Compression -{ - public class BufferState - { - /// - /// i_ptr - /// - public int InputPointer { get; set; } - - /// - /// i_end - /// - public int InputEnd { get; set; } - - /// - /// bit_buffer - /// - public uint BitBuffer { get; set; } - - /// - /// bits_left - /// - public int BitsLeft { get; set; } - - #region Common - - /// - /// Initialises bitstream state in state structure - /// - public void Init() - { - InputPointer = 0; - InputEnd = 0; - BitBuffer = 0; - BitsLeft = 0; - } - - #endregion - - #region MSB - - /// - /// Removes N bits from the bit buffer - /// - public void REMOVE_BITS_MSB(int nbits) - { - BitBuffer <<= nbits; - BitsLeft -= nbits; - } - - #endregion - - #region LSB - - /// - /// Removes N bits from the bit buffer - /// - public void REMOVE_BITS_LSB(int nbits) - { - BitBuffer >>= nbits; - BitsLeft -= nbits; - } - - #endregion - } -} diff --git a/BurnOutSharp/External/libmspack/Compression/CompressionStream.ReadBits.cs b/BurnOutSharp/External/libmspack/Compression/CompressionStream.ReadBits.cs new file mode 100644 index 00000000..ca26417c --- /dev/null +++ b/BurnOutSharp/External/libmspack/Compression/CompressionStream.ReadBits.cs @@ -0,0 +1,293 @@ +/* This file is part of libmspack. + * (C) 2003-2013 Stuart Caie. + * + * The LZX method was created by Jonathan Forbes and Tomi Poutanen, adapted + * by Microsoft Corporation. + * + * libmspack is free software { get; set; } you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License (LGPL) version 2.1 + * + * For further details, see the file COPYING.LIB distributed with libmspack + */ + +using System; +using static LibMSPackSharp.Compression.Constants; + +namespace LibMSPackSharp.Compression +{ + /* This header defines macros that read data streams by + * the individual bits + * + * INIT_BITS initialises bitstream state in state structure + * STORE_BITS stores bitstream state in state structure + * RESTORE_BITS restores bitstream state from state structure + * ENSURE_BITS(n) ensure there are at least N bits in the bit buffer + * READ_BITS(var,n) takes N bits from the buffer and puts them in var + * PEEK_BITS(n) extracts without removing N bits from the bit buffer + * REMOVE_BITS(n) removes N bits from the bit buffer + * + * READ_BITS simply calls ENSURE_BITS, PEEK_BITS and REMOVE_BITS, + * which means it's limited to reading the number of bits you can + * ensure at any one time. It also fails if asked to read zero bits. + * If you need to read zero bits, or more bits than can be ensured in + * one go, use READ_MANY_BITS instead. + * + * These macros have variable names baked into them, so to use them + * you have to define some macros: + * - BITS_TYPE: the type name of your state structure + * - BITS_VAR: the variable that points to your state structure + * - define BITS_ORDER_MSB if bits are read from the MSB, or + * define BITS_ORDER_LSB if bits are read from the LSB + * - READ_BYTES: some code that reads more data into the bit buffer, + * it should use READ_IF_NEEDED (calls read_input if the byte buffer + * is empty), then INJECT_BITS(data,n) to put data from the byte + * buffer into the bit buffer. + * + * You also need to define some variables and structure members: + * - byte[] i_ptr; // current position in the byte buffer + * - byte[] i_end; // end of the byte buffer + * - uint bit_buffer; // the bit buffer itself + * - uint bits_left; // number of bits remaining + * + * If you use read_input() and READ_IF_NEEDED, they also expect these + * structure members: + * - struct mspack_system *sys; // to access sys->read() + * - uint error; // to record/return read errors + * - byte input_end; // to mark reaching the EOF + * - byte[] inbuf; // the input byte buffer + * - uint inbuf_size; // the size of the input byte buffer + * + * Your READ_BYTES implementation should read data from *i_ptr and + * put them in the bit buffer. READ_IF_NEEDED will call read_input() + * if i_ptr reaches i_end, and will fill up inbuf and set i_ptr to + * the start of inbuf and i_end to the end of inbuf. + * + * If you're reading in MSB order, the routines work by using the area + * beyond the MSB and the LSB of the bit buffer as a free source of + * zeroes when shifting. This avoids having to mask any bits. So we + * have to know the bit width of the bit buffer variable. We use + * and CHAR_BIT to find the size of the bit buffer in bits. + * + * If you are reading in LSB order, bits need to be masked. Normally + * this is done by computing the mask: N bits are masked by the value + * (1< + /// Initialises bitstream state in state structure + /// + public void INIT_BITS() + { + InputPointer = 0; + InputEnd = 0; + BitBuffer = 0; + BitsLeft = 0; + EndOfInput = 0; + } + + /// + /// Ensure there are at least N bits in the bit buffer + /// + public void ENSURE_BITS(int nbits) + { + while (BitsLeft < nbits) + { + READ_BYTES(); + if (Error != Error.MSPACK_ERR_OK) + return; + } + } + + /// + /// Read from the input if the buffer is empty + /// + public void READ_IF_NEEDED() + { + if (InputPointer >= InputEnd) + ReadInput(); + } + + /// + /// Read bytes from the input into the bit buffer + /// + public abstract void READ_BYTES(); + + /// + /// Read an input stream and fill the buffer + /// + protected virtual void ReadInput() + { + int read = System.Read(InputFileHandle, InputBuffer, 0, (int)InputBufferSize); + if (read < 0) + { + Error = Error.MSPACK_ERR_READ; + return; + } + + // We might overrun the input stream by asking for bits we don't use, + // so fake 2 more bytes at the end of input + if (read == 0) + { + if (EndOfInput != 0) + { + Console.WriteLine("Out of input bytes"); + Error = Error.MSPACK_ERR_READ; + return; + } + else + { + read = 2; + InputBuffer[0] = InputBuffer[1] = 0; + EndOfInput = 1; + } + } + + // Update i_ptr and i_end + InputPointer = 0; + InputEnd = read; + } + + #endregion + + #region MSB + + /// + /// Inject data into the bit buffer + /// + public void INJECT_BITS_MSB(int bitdata, int nbits) + { + BitBuffer |= (uint)(bitdata << (BITBUF_WIDTH - nbits - BitsLeft)); + BitsLeft += nbits; + } + + /// + /// Extracts without removing N bits from the bit buffer + /// + public long PEEK_BITS_MSB(int nbits) => (BitBuffer >> (BITBUF_WIDTH - (nbits))); + + /// + /// Takes N bits from the buffer and puts them in var + /// + public long READ_BITS_MSB(int nbits) + { + ENSURE_BITS(nbits); + if (Error != Error.MSPACK_ERR_OK) + return -1; + + long temp = PEEK_BITS_MSB(nbits); + + REMOVE_BITS_MSB(nbits); + return temp; + } + + /// + /// Read multiple bits and put them in var + /// + public long READ_MANY_BITS_MSB(int nbits) + { + byte needed = (byte)(nbits), bitrun; + long temp = 0; + while (needed > 0) + { + if (BitsLeft <= (BITBUF_WIDTH - 16)) + { + READ_BYTES(); + if (Error != Error.MSPACK_ERR_OK) + return -1; + } + + bitrun = (byte)((BitsLeft < needed) ? BitsLeft : needed); + temp = (temp << bitrun) | PEEK_BITS_MSB(bitrun); + REMOVE_BITS_MSB(bitrun); + needed -= bitrun; + } + + return temp; + } + + /// + /// Removes N bits from the bit buffer + /// + public void REMOVE_BITS_MSB(int nbits) + { + BitBuffer <<= nbits; + BitsLeft -= nbits; + } + + #endregion + + #region LSB + + /// + /// Inject data into the bit buffer + /// + public void INJECT_BITS_LSB(int bitdata, int nbits) + { + BitBuffer |= (uint)(bitdata << BitsLeft); + BitsLeft += nbits; + } + + /// + /// Extracts without removing N bits from the bit buffer + /// + public long PEEK_BITS_LSB(int nbits) => (BitBuffer & ((1 << (nbits)) - 1)); + + /// + /// Extracts without removing N bits from the bit buffer using a bit mask + /// + public long PEEK_BITS_T_LSB(int nbits) => BitBuffer & LSBBitMask[(nbits)]; + + /// + /// Takes N bits from the buffer and puts them in var + /// + public long READ_BITS_LSB(int nbits) + { + ENSURE_BITS(nbits); + if (Error != Error.MSPACK_ERR_OK) + return -1; + + long temp = PEEK_BITS_LSB(nbits); + + REMOVE_BITS_LSB(nbits); + return temp; + } + + /// + /// Takes N bits from the buffer and puts them in var using a bit mask + /// + public long READ_BITS_T_LSB(int nbits) + { + ENSURE_BITS(nbits); + if (Error != Error.MSPACK_ERR_OK) + return -1; + + long temp = PEEK_BITS_T_LSB(nbits); + + REMOVE_BITS_LSB(nbits); + return temp; + } + + /// + /// Removes N bits from the bit buffer + /// + public void REMOVE_BITS_LSB(int nbits) + { + BitBuffer >>= nbits; + BitsLeft -= nbits; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libmspack/Compression/CompressionStream.ReadHuff.cs b/BurnOutSharp/External/libmspack/Compression/CompressionStream.ReadHuff.cs new file mode 100644 index 00000000..e8e73b6d --- /dev/null +++ b/BurnOutSharp/External/libmspack/Compression/CompressionStream.ReadHuff.cs @@ -0,0 +1,342 @@ +/* This file is part of libmspack. + * (C) 2003-2013 Stuart Caie. + * + * The LZX method was created by Jonathan Forbes and Tomi Poutanen, adapted + * by Microsoft Corporation. + * + * libmspack is free software { get; set; } you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License (LGPL) version 2.1 + * + * For further details, see the file COPYING.LIB distributed with libmspack + */ + +using static LibMSPackSharp.Compression.Constants; + +namespace LibMSPackSharp.Compression +{ + public abstract partial class CompressionStream : BaseDecompressState + { + #region Common + + /// + /// Per compression error code for decoding failure + /// + public abstract Error HUFF_ERROR(); + + #endregion + + #region MSB + + /// + /// Decodes the next huffman symbol from the input bitstream into var. + /// Do not use this macro on a table unless build_decode_table() succeeded. + /// + public long READ_HUFFSYM_MSB(ushort[] table, byte[] lengths, int tablebits, int maxsymbols) + { + ENSURE_BITS(HUFF_MAXBITS); + ushort sym = table[PEEK_BITS_MSB(tablebits)]; + if (sym >= maxsymbols) + HUFF_TRAVERSE_MSB(ref sym, table, tablebits, maxsymbols); + + REMOVE_BITS_MSB(lengths[sym]); + return sym; + } + + /// + /// Traverse for a single symbol + /// + private void HUFF_TRAVERSE_MSB(ref ushort sym, ushort[] table, int tablebits, int maxsymbols) + { + int i = 1 << (BITBUF_WIDTH - tablebits); + do + { + if ((i >>= 1) == 0) + { + Error = HUFF_ERROR(); + return; + } + + sym = table[(sym << 1) | ((BitBuffer & i) != 0 ? 1 : 0)]; + } while (sym >= maxsymbols); + } + + /// + /// This function was originally coded by David Tritscher. + /// + /// It builds a fast huffman decoding table from + /// a canonical huffman code lengths table. + /// + /// total number of symbols in this huffman tree. + /// any symbols with a code length of nbits or less can be decoded in one lookup of the table. + /// A table to get code lengths from [0 to nsyms-1] + /// + /// The table to fill up with decoded symbols and pointers. + /// Should be ((1< + /// True for OK or false for error + public static bool MakeDecodeTableMSB(int nsyms, int nbits, byte[] length, ushort[] table) + { + ushort sym, next_symbol; + uint leaf, fill; + byte bit_num; + uint pos = 0; // The current position in the decode table + uint table_mask = (uint)1 << nbits; + uint bit_mask = table_mask >> 1; // Don't do 0 length codes + + // Fill entries for codes short enough for a direct mapping + for (bit_num = 1; bit_num <= nbits; bit_num++) + { + for (sym = 0; sym < nsyms; sym++) + { + if (length[sym] != bit_num) + continue; + + leaf = pos; + if ((pos += bit_mask) > table_mask) + return false; // Table overrun + + // Fill all possible lookups of this symbol with the symbol itself + for (fill = bit_mask; fill-- > 0;) + { + table[leaf++] = sym; + } + } + + bit_mask >>= 1; + } + + // Exit with success if table is now complete + if (pos == table_mask) + return true; + + // Mark all remaining table entries as unused + for (sym = (ushort)pos; sym < table_mask; sym++) + { + table[sym] = 0xFFFF; + } + + // next_symbol = base of allocation for long codes + next_symbol = ((table_mask >> 1) < nsyms) ? (ushort)nsyms : (ushort)(table_mask >> 1); + + // Give ourselves room for codes to grow by up to 16 more bits. + // codes now start at bit nbits+16 and end at (nbits+16-codelength) + pos <<= 16; + table_mask <<= 16; + bit_mask = 1 << 15; + + for (bit_num = (byte)(nbits + 1); bit_num <= HUFF_MAXBITS; bit_num++) + { + for (sym = 0; sym < nsyms; sym++) + { + if (length[sym] != bit_num) + continue; + if (pos >= table_mask) + return false; // Table overflow + + leaf = pos >> 16; + for (fill = 0; fill < (bit_num - nbits); fill++) + { + // If this path hasn't been taken yet, 'allocate' two entries + if (table[leaf] == 0xFFFF) + { + table[(next_symbol << 1)] = 0xFFFF; + table[(next_symbol << 1) + 1] = 0xFFFF; + table[leaf] = next_symbol++; + } + + // Follow the path and select either left or right for next bit + leaf = (uint)(table[leaf] << 1); + if (((pos >> (15 - (int)fill)) & 1) != 0) + leaf++; + } + + table[leaf] = sym; + pos += bit_mask; + } + + bit_mask >>= 1; + } + + // Full table? + return pos == table_mask; + } + + #endregion + + #region LSB + + /// + /// Decodes the next huffman symbol from the input bitstream into var. + /// Do not use this macro on a table unless build_decode_table() succeeded. + /// + public long READ_HUFFSYM_LSB(ushort[] table, byte[] lengths, int tablebits, int maxsymbols) + { + ENSURE_BITS(HUFF_MAXBITS); + ushort sym = table[PEEK_BITS_LSB(tablebits)]; + if (sym >= maxsymbols) + HUFF_TRAVERSE_LSB(ref sym, table, tablebits, maxsymbols); + + REMOVE_BITS_LSB(lengths[sym]); + return sym; + } + + /// + /// Traverse for a single symbol + /// + private void HUFF_TRAVERSE_LSB(ref ushort sym, ushort[] table, int tablebits, int maxsymbols) + { + int i = tablebits - 1; + do + { + if (i++ > HUFF_MAXBITS) + { + Error = HUFF_ERROR(); + return; + } + + sym = table[(sym << 1) | ((BitBuffer >> i) & 1)]; + } while (sym >= maxsymbols); + } + + /// + /// This function was originally coded by David Tritscher. + /// + /// It builds a fast huffman decoding table from + /// a canonical huffman code lengths table. + /// + /// total number of symbols in this huffman tree. + /// any symbols with a code length of nbits or less can be decoded in one lookup of the table. + /// A table to get code lengths from [0 to nsyms-1] + /// + /// The table to fill up with decoded symbols and pointers. + /// Should be ((1< + /// True for OK or false for error + public static bool MakeDecodeTableLSB(int nsyms, int nbits, byte[] length, ushort[] table) + { + ushort sym, next_symbol; + uint leaf, fill; + uint reverse; + byte bit_num; + uint pos = 0; // The current position in the decode table + uint table_mask = (uint)1 << nbits; + uint bit_mask = table_mask >> 1; // Don't do 0 length codes + + // Fill entries for codes short enough for a direct mapping + for (bit_num = 1; bit_num <= nbits; bit_num++) + { + for (sym = 0; sym < nsyms; sym++) + { + if (length[sym] != bit_num) + continue; + + // Reverse the significant bits + fill = length[sym]; + reverse = pos >> (int)(nbits - fill); + leaf = 0; + + do + { + leaf <<= 1; + leaf |= reverse & 1; + reverse >>= 1; + } while (--fill != 0); + + if ((pos += bit_mask) > table_mask) + return false; // Table overrun + + // Fill all possible lookups of this symbol with the symbol itself + fill = bit_mask; + next_symbol = (ushort)(1 << bit_num); + + do + { + table[leaf] = sym; + leaf += next_symbol; + } while (--fill != 0); + } + + bit_mask >>= 1; + } + + // Exit with success if table is now complete + if (pos == table_mask) + return true; + + // Mark all remaining table entries as unused + for (sym = (ushort)pos; sym < table_mask; sym++) + { + reverse = sym; + leaf = 0; + fill = (uint)nbits; + + do + { + leaf <<= 1; + leaf |= reverse & 1; + reverse >>= 1; + } while (--fill != 0); + + table[leaf] = 0xFFFF; + } + + // next_symbol = base of allocation for long codes + next_symbol = ((table_mask >> 1) < nsyms) ? (ushort)nsyms : (ushort)(table_mask >> 1); + + // Give ourselves room for codes to grow by up to 16 more bits. + // codes now start at bit nbits+16 and end at (nbits+16-codelength) + pos <<= 16; + table_mask <<= 16; + bit_mask = 1 << 15; + + for (bit_num = (byte)(nbits + 1); bit_num <= HUFF_MAXBITS; bit_num++) + { + for (sym = 0; sym < nsyms; sym++) + { + if (length[sym] != bit_num) + continue; + if (pos >= table_mask) + return false; // Table overflow + + // leaf = the first nbits of the code, reversed + reverse = pos >> 16; + leaf = 0; + fill = (uint)nbits; + + do + { + leaf <<= 1; + leaf |= reverse & 1; + reverse >>= 1; + } while (--fill != 0); + + for (fill = 0; fill < (bit_num - nbits); fill++) + { + // If this path hasn't been taken yet, 'allocate' two entries + if (table[leaf] == 0xFFFF) + { + table[(next_symbol << 1)] = 0xFFFF; + table[(next_symbol << 1) + 1] = 0xFFFF; + table[leaf] = (ushort)next_symbol++; + } + + // Follow the path and select either left or right for next bit + leaf = (uint)(table[leaf] << 1); + if (((pos >> (15 - (int)fill)) & 1) != 0) + leaf++; + } + + table[leaf] = sym; + pos += bit_mask; + } + + bit_mask >>= 1; + } + + // Full table? + return pos == table_mask; + } + + #endregion + } +} diff --git a/BurnOutSharp/External/libmspack/Compression/CompressionStream.cs b/BurnOutSharp/External/libmspack/Compression/CompressionStream.cs index 1ddeac7d..c26e200f 100644 --- a/BurnOutSharp/External/libmspack/Compression/CompressionStream.cs +++ b/BurnOutSharp/External/libmspack/Compression/CompressionStream.cs @@ -10,647 +10,47 @@ * For further details, see the file COPYING.LIB distributed with libmspack */ -using System; -using static LibMSPackSharp.Compression.Constants; - namespace LibMSPackSharp.Compression { - public abstract class CompressionStream : BaseDecompressState + public abstract partial class CompressionStream : BaseDecompressState { - #region I/O buffering - public byte[] InputBuffer { get; set; } public uint InputBufferSize { get; set; } + /// + /// i_ptr + /// + public int InputPointer { get; set; } + + /// + /// i_end + /// + public int InputEnd { get; set; } + + /// + /// o_ptr + /// public int OutputPointer { get; set; } + /// + /// o_end + /// public int OutputEnd { get; set; } - internal BufferState BufferState { get; set; } + /// + /// bit_buffer + /// + public uint BitBuffer { get; set; } + + /// + /// bits_left + /// + public int BitsLeft { get; set; } /// /// Have we reached the end of input? /// public int EndOfInput { get; set; } - - #endregion - - #region ReadBits Methods - - /* This header defines macros that read data streams by - * the individual bits - * - * INIT_BITS initialises bitstream state in state structure - * STORE_BITS stores bitstream state in state structure - * RESTORE_BITS restores bitstream state from state structure - * ENSURE_BITS(n) ensure there are at least N bits in the bit buffer - * READ_BITS(var,n) takes N bits from the buffer and puts them in var - * PEEK_BITS(n) extracts without removing N bits from the bit buffer - * REMOVE_BITS(n) removes N bits from the bit buffer - * - * READ_BITS simply calls ENSURE_BITS, PEEK_BITS and REMOVE_BITS, - * which means it's limited to reading the number of bits you can - * ensure at any one time. It also fails if asked to read zero bits. - * If you need to read zero bits, or more bits than can be ensured in - * one go, use READ_MANY_BITS instead. - * - * These macros have variable names baked into them, so to use them - * you have to define some macros: - * - BITS_TYPE: the type name of your state structure - * - BITS_VAR: the variable that points to your state structure - * - define BITS_ORDER_MSB if bits are read from the MSB, or - * define BITS_ORDER_LSB if bits are read from the LSB - * - READ_BYTES: some code that reads more data into the bit buffer, - * it should use READ_IF_NEEDED (calls read_input if the byte buffer - * is empty), then INJECT_BITS(data,n) to put data from the byte - * buffer into the bit buffer. - * - * You also need to define some variables and structure members: - * - byte[] i_ptr; // current position in the byte buffer - * - byte[] i_end; // end of the byte buffer - * - uint bit_buffer; // the bit buffer itself - * - uint bits_left; // number of bits remaining - * - * If you use read_input() and READ_IF_NEEDED, they also expect these - * structure members: - * - struct mspack_system *sys; // to access sys->read() - * - uint error; // to record/return read errors - * - byte input_end; // to mark reaching the EOF - * - byte[] inbuf; // the input byte buffer - * - uint inbuf_size; // the size of the input byte buffer - * - * Your READ_BYTES implementation should read data from *i_ptr and - * put them in the bit buffer. READ_IF_NEEDED will call read_input() - * if i_ptr reaches i_end, and will fill up inbuf and set i_ptr to - * the start of inbuf and i_end to the end of inbuf. - * - * If you're reading in MSB order, the routines work by using the area - * beyond the MSB and the LSB of the bit buffer as a free source of - * zeroes when shifting. This avoids having to mask any bits. So we - * have to know the bit width of the bit buffer variable. We use - * and CHAR_BIT to find the size of the bit buffer in bits. - * - * If you are reading in LSB order, bits need to be masked. Normally - * this is done by computing the mask: N bits are masked by the value - * (1< - /// Initialises bitstream state in state structure - /// - public void INIT_BITS() - { - BufferState = new BufferState(); - BufferState.Init(); - EndOfInput = 0; - } - - /// - /// Stores bitstream state in state structure - /// - public void STORE_BITS(BufferState state) - { - BufferState.InputPointer = state.InputPointer; - BufferState.InputEnd = state.InputEnd; - BufferState.BitBuffer = state.BitBuffer; - BufferState.BitsLeft = state.BitsLeft; - } - - /// - /// Restores bitstream state from state structure - /// - public BufferState RESTORE_BITS() - { - return new BufferState() - { - InputPointer = BufferState.InputPointer, - InputEnd = BufferState.InputEnd, - BitBuffer = BufferState.BitBuffer, - BitsLeft = BufferState.BitsLeft, - }; - } - - /// - /// Ensure there are at least N bits in the bit buffer - /// - public void ENSURE_BITS(int nbits, BufferState state) - { - while (state.BitsLeft < nbits) - { - READ_BYTES(state); - if (Error != Error.MSPACK_ERR_OK) - return; - } - } - - /// - /// Read from the input if the buffer is empty - /// - public void READ_IF_NEEDED(BufferState state) - { - if (state.InputPointer >= state.InputEnd) - { - ReadInput(); - if (Error != Error.MSPACK_ERR_OK) - return; - - state.InputPointer = BufferState.InputPointer; - state.InputEnd = BufferState.InputEnd; - } - } - - /// - /// Read bytes from the input into the bit buffer - /// - public abstract void READ_BYTES(BufferState state); - - /// - /// Read an input stream and fill the buffer - /// - protected virtual void ReadInput() - { - int read = System.Read(InputFileHandle, InputBuffer, 0, (int)InputBufferSize); - if (read < 0) - { - Error = Error.MSPACK_ERR_READ; - return; - } - - // We might overrun the input stream by asking for bits we don't use, - // so fake 2 more bytes at the end of input - if (read == 0) - { - if (EndOfInput != 0) - { - Console.WriteLine("Out of input bytes"); - Error = Error.MSPACK_ERR_READ; - return; - } - else - { - read = 2; - InputBuffer[0] = InputBuffer[1] = 0; - EndOfInput = 1; - } - } - - // Update i_ptr and i_end - BufferState.InputPointer = 0; - BufferState.InputEnd = read; - } - - #endregion - - #region MSB - - /// - /// Inject data into the bit buffer - /// - public void INJECT_BITS_MSB(int bitdata, int nbits, BufferState state) - { - state.BitBuffer |= (uint)(bitdata << (BITBUF_WIDTH - nbits - state.BitsLeft)); - state.BitsLeft += nbits; - } - - /// - /// Extracts without removing N bits from the bit buffer - /// - public long PEEK_BITS_MSB(int nbits, uint bit_buffer) => (bit_buffer >> (BITBUF_WIDTH - (nbits))); - - /// - /// Takes N bits from the buffer and puts them in var - /// - public long READ_BITS_MSB(int nbits, BufferState state) - { - ENSURE_BITS(nbits, state); - if (Error != Error.MSPACK_ERR_OK) - return -1; - - long temp = PEEK_BITS_MSB(nbits, state.BitBuffer); - - state.REMOVE_BITS_MSB(nbits); - return temp; - } - - /// - /// Read multiple bits and put them in var - /// - public long READ_MANY_BITS_MSB(int nbits, BufferState state) - { - byte needed = (byte)(nbits), bitrun; - long temp = 0; - while (needed > 0) - { - if (state.BitsLeft <= (BITBUF_WIDTH - 16)) - { - READ_BYTES(state); - if (Error != Error.MSPACK_ERR_OK) - return -1; - } - - bitrun = (byte)((state.BitsLeft < needed) ? state.BitsLeft : needed); - temp = (temp << bitrun) | PEEK_BITS_MSB(bitrun, state.BitBuffer); - state.REMOVE_BITS_MSB(bitrun); - needed -= bitrun; - } - - return temp; - } - - #endregion - - #region LSB - - /// - /// Inject data into the bit buffer - /// - public void INJECT_BITS_LSB(int bitdata, int nbits, BufferState state) - { - state.BitBuffer |= (uint)(bitdata << state.BitsLeft); - state.BitsLeft += nbits; - } - - /// - /// Extracts without removing N bits from the bit buffer - /// - public long PEEK_BITS_LSB(int nbits, uint bit_buffer) => (bit_buffer & ((1 << (nbits)) - 1)); - - /// - /// Extracts without removing N bits from the bit buffer using a bit mask - /// - public long PEEK_BITS_T_LSB(int nbits, uint bit_buffer) => bit_buffer & LSBBitMask[(nbits)]; - - /// - /// Takes N bits from the buffer and puts them in var - /// - public long READ_BITS_LSB(int nbits, BufferState state) - { - ENSURE_BITS(nbits, state); - if (Error != Error.MSPACK_ERR_OK) - return -1; - - long temp = PEEK_BITS_LSB(nbits, state.BitBuffer); - - state.REMOVE_BITS_LSB(nbits); - return temp; - } - - /// - /// Takes N bits from the buffer and puts them in var using a bit mask - /// - public long READ_BITS_T_LSB(int nbits, BufferState state) - { - ENSURE_BITS(nbits, state); - if (Error != Error.MSPACK_ERR_OK) - return -1; - - long temp = PEEK_BITS_T_LSB(nbits, state.BitBuffer); - - state.REMOVE_BITS_LSB(nbits); - return temp; - } - - #endregion - - #endregion - - #region ReadHuff Methods - - #region Common - - /// - /// Per compression error code for decoding failure - /// - public abstract Error HUFF_ERROR(); - - #endregion - - #region MSB - - /// - /// Decodes the next huffman symbol from the input bitstream into var. - /// Do not use this macro on a table unless build_decode_table() succeeded. - /// - public long READ_HUFFSYM_MSB(ushort[] table, byte[] lengths, int tablebits, int maxsymbols, BufferState state) - { - ENSURE_BITS(HUFF_MAXBITS, state); - ushort sym = table[PEEK_BITS_MSB(tablebits, state.BitBuffer)]; - if (sym >= maxsymbols) - HUFF_TRAVERSE_MSB(ref sym, table, tablebits, maxsymbols, state.BitBuffer); - - state.REMOVE_BITS_MSB(lengths[sym]); - return sym; - } - - /// - /// Traverse for a single symbol - /// - private void HUFF_TRAVERSE_MSB(ref ushort sym, ushort[] table, int tablebits, int maxsymbols, uint bit_buffer) - { - int i = 1 << (BITBUF_WIDTH - tablebits); - do - { - if ((i >>= 1) == 0) - { - Error = HUFF_ERROR(); - return; - } - - sym = table[(sym << 1) | ((bit_buffer & i) != 0 ? 1 : 0)]; - } while (sym >= maxsymbols); - } - - /// - /// This function was originally coded by David Tritscher. - /// - /// It builds a fast huffman decoding table from - /// a canonical huffman code lengths table. - /// - /// total number of symbols in this huffman tree. - /// any symbols with a code length of nbits or less can be decoded in one lookup of the table. - /// A table to get code lengths from [0 to nsyms-1] - /// - /// The table to fill up with decoded symbols and pointers. - /// Should be ((1< - /// True for OK or false for error - public static bool MakeDecodeTableMSB(int nsyms, int nbits, byte[] length, ushort[] table) - { - ushort sym, next_symbol; - uint leaf, fill; - byte bit_num; - uint pos = 0; // The current position in the decode table - uint table_mask = (uint)1 << nbits; - uint bit_mask = table_mask >> 1; // Don't do 0 length codes - - // Fill entries for codes short enough for a direct mapping - for (bit_num = 1; bit_num <= nbits; bit_num++) - { - for (sym = 0; sym < nsyms; sym++) - { - if (length[sym] != bit_num) - continue; - - leaf = pos; - if ((pos += bit_mask) > table_mask) - return false; // Table overrun - - // Fill all possible lookups of this symbol with the symbol itself - for (fill = bit_mask; fill-- > 0;) - { - table[leaf++] = sym; - } - } - - bit_mask >>= 1; - } - - // Exit with success if table is now complete - if (pos == table_mask) - return true; - - // Mark all remaining table entries as unused - for (sym = (ushort)pos; sym < table_mask; sym++) - { - table[sym] = 0xFFFF; - } - - // next_symbol = base of allocation for long codes - next_symbol = ((table_mask >> 1) < nsyms) ? (ushort)nsyms : (ushort)(table_mask >> 1); - - // Give ourselves room for codes to grow by up to 16 more bits. - // codes now start at bit nbits+16 and end at (nbits+16-codelength) - pos <<= 16; - table_mask <<= 16; - bit_mask = 1 << 15; - - for (bit_num = (byte)(nbits + 1); bit_num <= HUFF_MAXBITS; bit_num++) - { - for (sym = 0; sym < nsyms; sym++) - { - if (length[sym] != bit_num) - continue; - if (pos >= table_mask) - return false; // Table overflow - - leaf = pos >> 16; - for (fill = 0; fill < (bit_num - nbits); fill++) - { - // If this path hasn't been taken yet, 'allocate' two entries - if (table[leaf] == 0xFFFF) - { - table[(next_symbol << 1)] = 0xFFFF; - table[(next_symbol << 1) + 1] = 0xFFFF; - table[leaf] = next_symbol++; - } - - // Follow the path and select either left or right for next bit - leaf = (uint)(table[leaf] << 1); - if (((pos >> (15 - (int)fill)) & 1) != 0) - leaf++; - } - - table[leaf] = sym; - pos += bit_mask; - } - - bit_mask >>= 1; - } - - // Full table? - return pos == table_mask; - } - - #endregion - - #region LSB - - /// - /// Decodes the next huffman symbol from the input bitstream into var. - /// Do not use this macro on a table unless build_decode_table() succeeded. - /// - public long READ_HUFFSYM_LSB(ushort[] table, byte[] lengths, int tablebits, int maxsymbols, BufferState state) - { - ENSURE_BITS(HUFF_MAXBITS, state); - ushort sym = table[PEEK_BITS_LSB(tablebits, state.BitBuffer)]; - if (sym >= maxsymbols) - HUFF_TRAVERSE_LSB(ref sym, table, tablebits, maxsymbols, state.BitBuffer); - - state.REMOVE_BITS_LSB(lengths[sym]); - return sym; - } - - /// - /// Traverse for a single symbol - /// - private void HUFF_TRAVERSE_LSB(ref ushort sym, ushort[] table, int tablebits, int maxsymbols, uint bit_buffer) - { - int i = tablebits - 1; - do - { - if (i++ > HUFF_MAXBITS) - { - Error = HUFF_ERROR(); - return; - } - - sym = table[(sym << 1) | ((bit_buffer >> i) & 1)]; - } while (sym >= maxsymbols); - } - - /// - /// This function was originally coded by David Tritscher. - /// - /// It builds a fast huffman decoding table from - /// a canonical huffman code lengths table. - /// - /// total number of symbols in this huffman tree. - /// any symbols with a code length of nbits or less can be decoded in one lookup of the table. - /// A table to get code lengths from [0 to nsyms-1] - /// - /// The table to fill up with decoded symbols and pointers. - /// Should be ((1< - /// True for OK or false for error - public static bool MakeDecodeTableLSB(int nsyms, int nbits, byte[] length, ushort[] table) - { - ushort sym, next_symbol; - uint leaf, fill; - uint reverse; - byte bit_num; - uint pos = 0; // The current position in the decode table - uint table_mask = (uint)1 << nbits; - uint bit_mask = table_mask >> 1; // Don't do 0 length codes - - // Fill entries for codes short enough for a direct mapping - for (bit_num = 1; bit_num <= nbits; bit_num++) - { - for (sym = 0; sym < nsyms; sym++) - { - if (length[sym] != bit_num) - continue; - - // Reverse the significant bits - fill = length[sym]; - reverse = pos >> (int)(nbits - fill); - leaf = 0; - - do - { - leaf <<= 1; - leaf |= reverse & 1; - reverse >>= 1; - } while (--fill != 0); - - if ((pos += bit_mask) > table_mask) - return false; // Table overrun - - // Fill all possible lookups of this symbol with the symbol itself - fill = bit_mask; - next_symbol = (ushort)(1 << bit_num); - - do - { - table[leaf] = sym; - leaf += next_symbol; - } while (--fill != 0); - } - - bit_mask >>= 1; - } - - // Exit with success if table is now complete - if (pos == table_mask) - return true; - - // Mark all remaining table entries as unused - for (sym = (ushort)pos; sym < table_mask; sym++) - { - reverse = sym; - leaf = 0; - fill = (uint)nbits; - - do - { - leaf <<= 1; - leaf |= reverse & 1; - reverse >>= 1; - } while (--fill != 0); - - table[leaf] = 0xFFFF; - } - - // next_symbol = base of allocation for long codes - next_symbol = ((table_mask >> 1) < nsyms) ? (ushort)nsyms : (ushort)(table_mask >> 1); - - // Give ourselves room for codes to grow by up to 16 more bits. - // codes now start at bit nbits+16 and end at (nbits+16-codelength) - pos <<= 16; - table_mask <<= 16; - bit_mask = 1 << 15; - - for (bit_num = (byte)(nbits + 1); bit_num <= HUFF_MAXBITS; bit_num++) - { - for (sym = 0; sym < nsyms; sym++) - { - if (length[sym] != bit_num) - continue; - if (pos >= table_mask) - return false; // Table overflow - - // leaf = the first nbits of the code, reversed - reverse = pos >> 16; - leaf = 0; - fill = (uint)nbits; - - do - { - leaf <<= 1; - leaf |= reverse & 1; - reverse >>= 1; - } while (--fill != 0); - - for (fill = 0; fill < (bit_num - nbits); fill++) - { - // If this path hasn't been taken yet, 'allocate' two entries - if (table[leaf] == 0xFFFF) - { - table[(next_symbol << 1)] = 0xFFFF; - table[(next_symbol << 1) + 1] = 0xFFFF; - table[leaf] = (ushort)next_symbol++; - } - - // Follow the path and select either left or right for next bit - leaf = (uint)(table[leaf] << 1); - if (((pos >> (15 - (int)fill)) & 1) != 0) - leaf++; - } - - table[leaf] = sym; - pos += bit_mask; - } - - bit_mask >>= 1; - } - - // Full table? - return pos == table_mask; - } - - #endregion - - #endregion } } diff --git a/BurnOutSharp/External/libmspack/Compression/LZHKWAJ.Decompress.cs b/BurnOutSharp/External/libmspack/Compression/LZHKWAJ.Decompress.cs new file mode 100644 index 00000000..59ba4297 --- /dev/null +++ b/BurnOutSharp/External/libmspack/Compression/LZHKWAJ.Decompress.cs @@ -0,0 +1,293 @@ +/* This file is part of libmspack. + * (C) 2003-2010 Stuart Caie. + * + * libmspack is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License (LGPL) version 2.1 + * + * For further details, see the file COPYING.LIB distributed with libmspack + */ + +using System.IO; +using static LibMSPackSharp.Constants; +using static LibMSPackSharp.Compression.Constants; + +namespace LibMSPackSharp.Compression +{ + /// + /// In the KWAJ LZH format, there is no special 'eof' marker, it just + /// ends. Depending on how many bits are left in the final byte when + /// the stream ends, that might be enough to start another literal or + /// match. The only easy way to detect that we've come to an end is to + /// guard all bit-reading. We allow fake bits to be read once we reach + /// the end of the stream, but we check if we then consumed any of + /// those fake bits, after doing the READ_BITS / READ_HUFFSYM. This + /// isn't how the default ReadInput works (it simply lets + /// 2 fake bytes in then stops), so we implement our own. + /// + public partial class LZHKWAJ + { + public static LZHKWAJ Init(SystemImpl sys, FileStream input, FileStream output) + { + if (sys == null || input == null || output == null) + return null; + + return new LZHKWAJ() + { + System = sys, + InputFileHandle = input, + OutputFileHandle = output, + }; + } + + public Error Decompress() + { + int i; + int lit_run = 0; + int j, pos = 0, len, offset; + uint[] types = new uint[6]; + + // Reset global state + INIT_BITS(); + + for (i = 0; i < LZSS_WINDOW_SIZE; i++) + { + Window[i] = LZSS_WINDOW_FILL; + } + + // Read 6 encoding types (for byte alignment) but only 5 are needed + for (i = 0; i < 6; i++) + { + types[i] = (uint)READ_BITS_SAFE(4); + if (Error == Error.MSPACK_ERR_NOMEMORY) + return Error.MSPACK_ERR_OK; + } + + // Read huffman table symbol lengths and build huffman trees + BUILD_TREE(types[0], MATCHLEN1_table, MATCHLEN1_len, KWAJ_TABLEBITS, KWAJ_MATCHLEN1_SYMS); + BUILD_TREE(types[1], MATCHLEN2_table, MATCHLEN2_len, KWAJ_TABLEBITS, KWAJ_MATCHLEN2_SYMS); + BUILD_TREE(types[2], LITLEN_table, LITLEN_len, KWAJ_TABLEBITS, KWAJ_LITLEN_SYMS); + BUILD_TREE(types[3], OFFSET_table, OFFSET_len, KWAJ_TABLEBITS, KWAJ_OFFSET_SYMS); + BUILD_TREE(types[4], LITERAL_table, LITERAL_len, KWAJ_TABLEBITS, KWAJ_LITERAL_SYMS); + + while (EndOfInput == 0) + { + if (lit_run != 0) + { + len = (int)READ_HUFFSYM_SAFE(MATCHLEN2_table, MATCHLEN2_len, KWAJ_MATCHLEN2_TBLSIZE, KWAJ_MATCHLEN2_SYMS); + if (Error == Error.MSPACK_ERR_NOMEMORY) + return Error.MSPACK_ERR_OK; + } + else + { + len = (int)READ_HUFFSYM_SAFE(MATCHLEN1_table, MATCHLEN1_len, KWAJ_MATCHLEN1_TBLSIZE, KWAJ_MATCHLEN1_SYMS); + if (Error == Error.MSPACK_ERR_NOMEMORY) + return Error.MSPACK_ERR_OK; + } + + if (len > 0) + { + len += 2; + lit_run = 0; // Not the end of a literal run + + j = (int)READ_HUFFSYM_SAFE(OFFSET_table, OFFSET_len, KWAJ_OFFSET_TBLSIZE, KWAJ_OFFSET_SYMS); + if (Error == Error.MSPACK_ERR_NOMEMORY) + return Error.MSPACK_ERR_OK; + + offset = j << 6; + + j = (int)READ_BITS_SAFE(6); + if (Error == Error.MSPACK_ERR_NOMEMORY) + return Error.MSPACK_ERR_OK; + + offset |= j; + + // Copy match as output and into the ring buffer + while (len-- > 0) + { + Window[pos] = Window[(pos + 4096 - offset) & 4095]; + WRITE_BYTE(pos); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + pos++; + pos &= 4095; + } + } + else + { + len = (int)READ_HUFFSYM_SAFE(LITLEN_table, LITLEN_len, KWAJ_LITLEN_TBLSIZE, KWAJ_LITLEN_SYMS); + if (Error == Error.MSPACK_ERR_NOMEMORY) + return Error.MSPACK_ERR_OK; + + len++; + lit_run = (len == 32) ? 0 : 1; // End of a literal run? + while (len-- > 0) + { + j = (int)READ_HUFFSYM_SAFE(LITERAL_table, LITERAL_len, KWAJ_LITERAL_TBLSIZE, KWAJ_LITERAL_SYMS); + if (Error == Error.MSPACK_ERR_NOMEMORY) + return Error.MSPACK_ERR_OK; + + // Copy as output and into the ring buffer + Window[pos] = (byte)j; + + WRITE_BYTE(pos); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + pos++; pos &= 4095; + } + } + } + + return Error.MSPACK_ERR_OK; + } + + private Error BUILD_TREE(uint type, ushort[] table, byte[] lengths, int tablebits, int maxsymbols) + { + Error err = ReadLens(type, (uint)maxsymbols, MATCHLEN1_len); + if (err != Error.MSPACK_ERR_OK) + return err; + + if (!CompressionStream.MakeDecodeTableMSB(maxsymbols, tablebits, lengths, table)) + return Error.MSPACK_ERR_DATAFORMAT; + + return Error.MSPACK_ERR_OK; + } + + /// + /// Safely read bits from the buffer + /// + private long READ_BITS_SAFE(int nbits) + { + long val = READ_BITS_MSB(nbits); + if (EndOfInput != 0 && BitsLeft < EndOfInput) + Error = Error.MSPACK_ERR_NOMEMORY; + else + Error = Error.MSPACK_ERR_OK; + + return val; + } + + /// + /// Safely read a symbol from a Huffman tree + /// + private long READ_HUFFSYM_SAFE(ushort[] table, byte[] lengths, int tablebits, int maxsymbols) + { + long val = READ_HUFFSYM_MSB(table, lengths, tablebits, maxsymbols); + if (EndOfInput != 0 && BitsLeft < EndOfInput) + Error = Error.MSPACK_ERR_NOMEMORY; + else + Error = Error.MSPACK_ERR_OK; + + return val; + } + + /// + /// Write a single byte to the output stream + /// + private void WRITE_BYTE(int pos) + { + if (System.Write(OutputFileHandle, Window, pos, 1) != 1) + Error = Error.MSPACK_ERR_WRITE; + } + + private Error ReadLens(uint type, uint numsyms, byte[] lens) + { + uint i, c, sel; + + switch (type) + { + case 0: + i = numsyms; + c = (uint)((i == 16) ? 4 : (i == 32) ? 5 : (i == 64) ? 6 : (i == 256) ? 8 : 0); + for (i = 0; i < numsyms; i++) + { + lens[i] = (byte)c; + } + + break; + + case 1: + c = (uint)READ_BITS_SAFE(4); + if (Error == Error.MSPACK_ERR_NOMEMORY) + return Error.MSPACK_ERR_OK; + + lens[0] = (byte)c; + for (i = 1; i < numsyms; i++) + { + sel = (uint)READ_BITS_SAFE(1); + if (Error == Error.MSPACK_ERR_NOMEMORY) + return Error.MSPACK_ERR_OK; + + if (sel == 0) + { + lens[i] = (byte)c; + } + else + { + sel = (uint)READ_BITS_SAFE(1); + if (Error == Error.MSPACK_ERR_NOMEMORY) + return Error.MSPACK_ERR_OK; + + if (sel == 0) + { + lens[i] = (byte)++c; + } + else + { + c = (uint)READ_BITS_SAFE(4); + if (Error == Error.MSPACK_ERR_NOMEMORY) + return Error.MSPACK_ERR_OK; + + lens[i] = (byte)c; + } + } + } + + break; + + case 2: + c = (uint)READ_BITS_SAFE(4); + if (Error == Error.MSPACK_ERR_NOMEMORY) + return Error.MSPACK_ERR_OK; + + lens[0] = (byte)c; + for (i = 1; i < numsyms; i++) + { + sel = (uint)READ_BITS_SAFE(2); + if (Error == Error.MSPACK_ERR_NOMEMORY) + return Error.MSPACK_ERR_OK; + + if (sel == 3) + { + c = (uint)READ_BITS_SAFE(4); + if (Error == Error.MSPACK_ERR_NOMEMORY) + return Error.MSPACK_ERR_OK; + } + else + { + c += (uint)((byte)sel - 1); + } + + lens[i] = (byte)c; + } + + break; + + case 3: + for (i = 0; i < numsyms; i++) + { + c = (uint)READ_BITS_SAFE(4); + if (Error == Error.MSPACK_ERR_NOMEMORY) + return Error.MSPACK_ERR_OK; + + lens[i] = (byte)c; + } + + break; + } + + return Error.MSPACK_ERR_OK; + } + } +} diff --git a/BurnOutSharp/External/libmspack/Compression/LZHKWAJ.ReadBits.cs b/BurnOutSharp/External/libmspack/Compression/LZHKWAJ.ReadBits.cs new file mode 100644 index 00000000..138ae39c --- /dev/null +++ b/BurnOutSharp/External/libmspack/Compression/LZHKWAJ.ReadBits.cs @@ -0,0 +1,64 @@ +/* This file is part of libmspack. + * (C) 2003-2010 Stuart Caie. + * + * libmspack is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License (LGPL) version 2.1 + * + * For further details, see the file COPYING.LIB distributed with libmspack + */ + +using static LibMSPackSharp.Constants; + +namespace LibMSPackSharp.Compression +{ + public partial class LZHKWAJ : CompressionStream + { + /// + public override void READ_BYTES() + { + if (InputPointer >= InputEnd) + { + ReadInput(); + if (Error != Error.MSPACK_ERR_OK) + return; + + InputPointer = InputPointer; + InputEnd = InputEnd; + } + + INJECT_BITS_MSB(InputBuffer[InputPointer++], 8); + } + + /// + protected override void ReadInput() + { + int read; + if (EndOfInput != 0) + { + EndOfInput += 8; + InputBuffer[0] = 0; + read = 1; + } + else + { + read = System.Read(InputFileHandle, InputBuffer, 0, KWAJ_INPUT_SIZE); + if (read < 0) + { + Error = Error.MSPACK_ERR_READ; + return; + } + + if (read == 0) + { + InputEnd = 8; + InputBuffer[0] = 0; + read = 1; + } + } + + // Update InputPointer and InputLength + InputPointer = 0; + InputEnd = read; + } + } +} diff --git a/BurnOutSharp/External/libmspack/Compression/LZHKWAJ.ReadHuff.cs b/BurnOutSharp/External/libmspack/Compression/LZHKWAJ.ReadHuff.cs new file mode 100644 index 00000000..78c1e026 --- /dev/null +++ b/BurnOutSharp/External/libmspack/Compression/LZHKWAJ.ReadHuff.cs @@ -0,0 +1,17 @@ +/* This file is part of libmspack. + * (C) 2003-2010 Stuart Caie. + * + * libmspack is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License (LGPL) version 2.1 + * + * For further details, see the file COPYING.LIB distributed with libmspack + */ + +namespace LibMSPackSharp.Compression +{ + public partial class LZHKWAJ : CompressionStream + { + /// + public override Error HUFF_ERROR() => Error.MSPACK_ERR_DATAFORMAT; + } +} diff --git a/BurnOutSharp/External/libmspack/Compression/LZHKWAJ.cs b/BurnOutSharp/External/libmspack/Compression/LZHKWAJ.cs index 13a0004c..442fd5a8 100644 --- a/BurnOutSharp/External/libmspack/Compression/LZHKWAJ.cs +++ b/BurnOutSharp/External/libmspack/Compression/LZHKWAJ.cs @@ -7,263 +7,31 @@ * For further details, see the file COPYING.LIB distributed with libmspack */ -using System.IO; using static LibMSPackSharp.Constants; using static LibMSPackSharp.Compression.Constants; namespace LibMSPackSharp.Compression { - /// - /// In the KWAJ LZH format, there is no special 'eof' marker, it just - /// ends. Depending on how many bits are left in the final byte when - /// the stream ends, that might be enough to start another literal or - /// match. The only easy way to detect that we've come to an end is to - /// guard all bit-reading. We allow fake bits to be read once we reach - /// the end of the stream, but we check if we then consumed any of - /// those fake bits, after doing the READ_BITS / READ_HUFFSYM. This - /// isn't how the default ReadInput works (it simply lets - /// 2 fake bytes in then stops), so we implement our own. - /// - public class LZHKWAJ + public partial class LZHKWAJ : CompressionStream { - public static LZHKWAJStream Init(SystemImpl sys, FileStream input, FileStream output) - { - if (sys == null || input == null || output == null) - return null; + // Huffman code lengths - return new LZHKWAJStream() - { - System = sys, - InputFileHandle = input, - OutputFileHandle = output, - }; - } + public byte[] MATCHLEN1_len { get; set; } = new byte[KWAJ_MATCHLEN1_SYMS]; + public byte[] MATCHLEN2_len { get; set; } = new byte[KWAJ_MATCHLEN2_SYMS]; + public byte[] LITLEN_len { get; set; } = new byte[KWAJ_LITLEN_SYMS]; + public byte[] OFFSET_len { get; set; } = new byte[KWAJ_OFFSET_SYMS]; + public byte[] LITERAL_len { get; set; } = new byte[KWAJ_LITERAL_SYMS]; - public static Error Decompress(LZHKWAJStream lzh) - { - int i; - int lit_run = 0; - int j, pos = 0, len, offset; - uint[] types = new uint[6]; + // Huffman decoding tables - // Reset global state - lzh.INIT_BITS(); - BufferState state = lzh.RESTORE_BITS(); + public ushort[] MATCHLEN1_table { get; set; } = new ushort[KWAJ_MATCHLEN1_TBLSIZE]; + public ushort[] MATCHLEN2_table { get; set; } = new ushort[KWAJ_MATCHLEN2_TBLSIZE]; + public ushort[] LITLEN_table { get; set; } = new ushort[KWAJ_LITLEN_TBLSIZE]; + public ushort[] OFFSET_table { get; set; } = new ushort[KWAJ_OFFSET_TBLSIZE]; + public ushort[] LITERAL_table { get; set; } = new ushort[KWAJ_LITERAL_TBLSIZE]; - for (i = 0; i < LZSS_WINDOW_SIZE; i++) - { - lzh.Window[i] = LZSS_WINDOW_FILL; - } + // History window - // Read 6 encoding types (for byte alignment) but only 5 are needed - for (i = 0; i < 6; i++) - { - types[i] = (uint)lzh.READ_BITS_SAFE(4, state); - if (lzh.Error == Error.MSPACK_ERR_NOMEMORY) - return Error.MSPACK_ERR_OK; - } - - // Read huffman table symbol lengths and build huffman trees - BUILD_TREE(lzh, types[0], lzh.MATCHLEN1_table, lzh.MATCHLEN1_len, KWAJ_TABLEBITS, KWAJ_MATCHLEN1_SYMS, state); - BUILD_TREE(lzh, types[1], lzh.MATCHLEN2_table, lzh.MATCHLEN2_len, KWAJ_TABLEBITS, KWAJ_MATCHLEN2_SYMS, state); - BUILD_TREE(lzh, types[2], lzh.LITLEN_table, lzh.LITLEN_len, KWAJ_TABLEBITS, KWAJ_LITLEN_SYMS, state); - BUILD_TREE(lzh, types[3], lzh.OFFSET_table, lzh.OFFSET_len, KWAJ_TABLEBITS, KWAJ_OFFSET_SYMS, state); - BUILD_TREE(lzh, types[4], lzh.LITERAL_table, lzh.LITERAL_len, KWAJ_TABLEBITS, KWAJ_LITERAL_SYMS, state); - - while (lzh.EndOfInput == 0) - { - if (lit_run != 0) - { - len = (int)lzh.READ_HUFFSYM_SAFE(lzh.MATCHLEN2_table, lzh.MATCHLEN2_len, KWAJ_MATCHLEN2_TBLSIZE, KWAJ_MATCHLEN2_SYMS, state); - if (lzh.Error == Error.MSPACK_ERR_NOMEMORY) - return Error.MSPACK_ERR_OK; - } - else - { - len = (int)lzh.READ_HUFFSYM_SAFE(lzh.MATCHLEN1_table, lzh.MATCHLEN1_len, KWAJ_MATCHLEN1_TBLSIZE, KWAJ_MATCHLEN1_SYMS, state); - if (lzh.Error == Error.MSPACK_ERR_NOMEMORY) - return Error.MSPACK_ERR_OK; - } - - if (len > 0) - { - len += 2; - lit_run = 0; // Not the end of a literal run - - j = (int)lzh.READ_HUFFSYM_SAFE(lzh.OFFSET_table, lzh.OFFSET_len, KWAJ_OFFSET_TBLSIZE, KWAJ_OFFSET_SYMS, state); - if (lzh.Error == Error.MSPACK_ERR_NOMEMORY) - return Error.MSPACK_ERR_OK; - - offset = j << 6; - - j = (int)lzh.READ_BITS_SAFE(6, state); - if (lzh.Error == Error.MSPACK_ERR_NOMEMORY) - return Error.MSPACK_ERR_OK; - - offset |= j; - - // Copy match as output and into the ring buffer - while (len-- > 0) - { - lzh.Window[pos] = lzh.Window[(pos + 4096 - offset) & 4095]; - lzh.WRITE_BYTE(pos); - if (lzh.Error != Error.MSPACK_ERR_OK) - return lzh.Error; - - pos++; - pos &= 4095; - } - } - else - { - len = (int)lzh.READ_HUFFSYM_SAFE(lzh.LITLEN_table, lzh.LITLEN_len, KWAJ_LITLEN_TBLSIZE, KWAJ_LITLEN_SYMS, state); - if (lzh.Error == Error.MSPACK_ERR_NOMEMORY) - return Error.MSPACK_ERR_OK; - - len++; - lit_run = (len == 32) ? 0 : 1; // End of a literal run? - while (len-- > 0) - { - j = (int)lzh.READ_HUFFSYM_SAFE(lzh.LITERAL_table, lzh.LITERAL_len, KWAJ_LITERAL_TBLSIZE, KWAJ_LITERAL_SYMS, state); - if (lzh.Error == Error.MSPACK_ERR_NOMEMORY) - return Error.MSPACK_ERR_OK; - - // Copy as output and into the ring buffer - lzh.Window[pos] = (byte)j; - - lzh.WRITE_BYTE(pos); - if (lzh.Error != Error.MSPACK_ERR_OK) - return lzh.Error; - - pos++; pos &= 4095; - } - } - } - - return Error.MSPACK_ERR_OK; - } - - private static Error BUILD_TREE(LZHKWAJStream lzh, uint type, ushort[] table, byte[] lengths, int tablebits, int maxsymbols, BufferState state) - { - lzh.STORE_BITS(state); - - Error err = ReadLens(lzh, type, (uint)maxsymbols, lzh.MATCHLEN1_len); - if (err != Error.MSPACK_ERR_OK) - return err; - - BufferState temp = lzh.RESTORE_BITS(); - state.InputPointer = temp.InputPointer; - state.InputEnd = temp.InputEnd; - state.BitBuffer = temp.BitBuffer; - state.BitsLeft = temp.BitsLeft; - - if (!CompressionStream.MakeDecodeTableMSB(maxsymbols, tablebits, lengths, table)) - return Error.MSPACK_ERR_DATAFORMAT; - - return Error.MSPACK_ERR_OK; - } - - private static Error ReadLens(LZHKWAJStream lzh, uint type, uint numsyms, byte[] lens) - { - uint i, c, sel; - - BufferState state = lzh.RESTORE_BITS(); - - switch (type) - { - case 0: - i = numsyms; - c = (uint)((i == 16) ? 4 : (i == 32) ? 5 : (i == 64) ? 6 : (i == 256) ? 8 : 0); - for (i = 0; i < numsyms; i++) - { - lens[i] = (byte)c; - } - - break; - - case 1: - c = (uint)lzh.READ_BITS_SAFE(4, state); - if (lzh.Error == Error.MSPACK_ERR_NOMEMORY) - return Error.MSPACK_ERR_OK; - - lens[0] = (byte)c; - for (i = 1; i < numsyms; i++) - { - sel = (uint)lzh.READ_BITS_SAFE(1, state); - if (lzh.Error == Error.MSPACK_ERR_NOMEMORY) - return Error.MSPACK_ERR_OK; - - if (sel == 0) - { - lens[i] = (byte)c; - } - else - { - sel = (uint)lzh.READ_BITS_SAFE(1, state); - if (lzh.Error == Error.MSPACK_ERR_NOMEMORY) - return Error.MSPACK_ERR_OK; - - if (sel == 0) - { - lens[i] = (byte)++c; - } - else - { - c = (uint)lzh.READ_BITS_SAFE(4, state); - if (lzh.Error == Error.MSPACK_ERR_NOMEMORY) - return Error.MSPACK_ERR_OK; - - lens[i] = (byte)c; - } - } - } - - break; - - case 2: - c = (uint)lzh.READ_BITS_SAFE(4, state); - if (lzh.Error == Error.MSPACK_ERR_NOMEMORY) - return Error.MSPACK_ERR_OK; - - lens[0] = (byte)c; - for (i = 1; i < numsyms; i++) - { - sel = (uint)lzh.READ_BITS_SAFE(2, state); - if (lzh.Error == Error.MSPACK_ERR_NOMEMORY) - return Error.MSPACK_ERR_OK; - - if (sel == 3) - { - c = (uint)lzh.READ_BITS_SAFE(4, state); - if (lzh.Error == Error.MSPACK_ERR_NOMEMORY) - return Error.MSPACK_ERR_OK; - } - else - { - c += (uint)((byte)sel - 1); - } - - lens[i] = (byte)c; - } - - break; - - case 3: - for (i = 0; i < numsyms; i++) - { - c = (uint)lzh.READ_BITS_SAFE(4, state); - if (lzh.Error == Error.MSPACK_ERR_NOMEMORY) - return Error.MSPACK_ERR_OK; - - lens[i] = (byte)c; - } - - break; - } - - lzh.STORE_BITS(state); - - return Error.MSPACK_ERR_OK; - } + public byte[] Window { get; set; } = new byte[LZSS_WINDOW_SIZE]; } } diff --git a/BurnOutSharp/External/libmspack/Compression/LZHKWAJStream.cs b/BurnOutSharp/External/libmspack/Compression/LZHKWAJStream.cs deleted file mode 100644 index 094c33e1..00000000 --- a/BurnOutSharp/External/libmspack/Compression/LZHKWAJStream.cs +++ /dev/null @@ -1,144 +0,0 @@ -/* This file is part of libmspack. - * (C) 2003-2010 Stuart Caie. - * - * libmspack is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License (LGPL) version 2.1 - * - * For further details, see the file COPYING.LIB distributed with libmspack - */ - -using static LibMSPackSharp.Constants; -using static LibMSPackSharp.Compression.Constants; - -namespace LibMSPackSharp.Compression -{ - public class LZHKWAJStream : CompressionStream - { - #region Fields - - // Huffman code lengths - - public byte[] MATCHLEN1_len { get; set; } = new byte[KWAJ_MATCHLEN1_SYMS]; - public byte[] MATCHLEN2_len { get; set; } = new byte[KWAJ_MATCHLEN2_SYMS]; - public byte[] LITLEN_len { get; set; } = new byte[KWAJ_LITLEN_SYMS]; - public byte[] OFFSET_len { get; set; } = new byte[KWAJ_OFFSET_SYMS]; - public byte[] LITERAL_len { get; set; } = new byte[KWAJ_LITERAL_SYMS]; - - // Huffman decoding tables - - public ushort[] MATCHLEN1_table { get; set; } = new ushort[KWAJ_MATCHLEN1_TBLSIZE]; - public ushort[] MATCHLEN2_table { get; set; } = new ushort[KWAJ_MATCHLEN2_TBLSIZE]; - public ushort[] LITLEN_table { get; set; } = new ushort[KWAJ_LITLEN_TBLSIZE]; - public ushort[] OFFSET_table { get; set; } = new ushort[KWAJ_OFFSET_TBLSIZE]; - public ushort[] LITERAL_table { get; set; } = new ushort[KWAJ_LITERAL_TBLSIZE]; - - // History window - - public byte[] Window { get; set; } = new byte[LZSS_WINDOW_SIZE]; - - #endregion - - #region Specialty Methods - - /* In the KWAJ LZH format, there is no special 'eof' marker, it just - * ends. Depending on how many bits are left in the final byte when - * the stream ends, that might be enough to start another literal or - * match. The only easy way to detect that we've come to an end is to - * guard all bit-reading. We allow fake bits to be read once we reach - * the end of the stream, but we check if we then consumed any of - * those fake bits, after doing the READ_BITS / READ_HUFFSYM. This - * isn't how the default readbits.h read_input() works (it simply lets - * 2 fake bytes in then stops), so we implement our own. - */ - - /// - /// Safely read bits from the buffer - /// - public long READ_BITS_SAFE(int nbits, BufferState state) - { - long val = READ_BITS_MSB(nbits, state); - if (EndOfInput != 0 && BufferState.BitsLeft < EndOfInput) - Error = Error.MSPACK_ERR_NOMEMORY; - else - Error = Error.MSPACK_ERR_OK; - - return val; - } - - /// - /// Safely read a symbol from a Huffman tree - /// - public long READ_HUFFSYM_SAFE(ushort[] table, byte[] lengths, int tablebits, int maxsymbols, BufferState state) - { - long val = READ_HUFFSYM_MSB(table, lengths, tablebits, maxsymbols, state); - if (EndOfInput != 0 && BufferState.BitsLeft < EndOfInput) - Error = Error.MSPACK_ERR_NOMEMORY; - else - Error = Error.MSPACK_ERR_OK; - - return val; - } - - /// - /// Write a single byte to the output stream - /// - public void WRITE_BYTE(int pos) - { - if (System.Write(OutputFileHandle, Window, pos, 1) != 1) - Error = Error.MSPACK_ERR_WRITE; - } - - #endregion - - /// - public override Error HUFF_ERROR() => Error.MSPACK_ERR_DATAFORMAT; - - /// - public override void READ_BYTES(BufferState state) - { - if (state.InputPointer >= state.InputEnd) - { - ReadInput(); - if (Error != Error.MSPACK_ERR_OK) - return; - - state.InputPointer = BufferState.InputPointer; - state.InputEnd = BufferState.InputEnd; - } - - INJECT_BITS_MSB(InputBuffer[state.InputPointer++], 8, state); - } - - /// - protected override void ReadInput() - { - int read; - if (EndOfInput != 0) - { - EndOfInput += 8; - InputBuffer[0] = 0; - read = 1; - } - else - { - read = System.Read(InputFileHandle, InputBuffer, 0, KWAJ_INPUT_SIZE); - if (read < 0) - { - Error = Error.MSPACK_ERR_READ; - return; - } - - if (read == 0) - { - BufferState.InputEnd = 8; - InputBuffer[0] = 0; - read = 1; - } - } - - // Update InputPointer and InputLength - BufferState.InputPointer = 0; - BufferState.InputEnd = read; - } - } -} diff --git a/BurnOutSharp/External/libmspack/Compression/LZX.Decompress.cs b/BurnOutSharp/External/libmspack/Compression/LZX.Decompress.cs new file mode 100644 index 00000000..23ab2ca6 --- /dev/null +++ b/BurnOutSharp/External/libmspack/Compression/LZX.Decompress.cs @@ -0,0 +1,1045 @@ +/* This file is part of libmspack. + * (C) 2003-2013 Stuart Caie. + * + * The LZX method was created by Jonathan Forbes and Tomi Poutanen, adapted + * by Microsoft Corporation. + * + * libmspack is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License (LGPL) version 2.1 + * + * For further details, see the file COPYING.LIB distributed with libmspack + */ + +/* Microsoft's LZX document (in cab-sdk.exe) and their implementation + * of the com.ms.util.cab Java package do not concur. + * + * In the LZX document, there is a table showing the correlation between + * window size and the number of position slots. It states that the 1MB + * window = 40 slots and the 2MB window = 42 slots. In the implementation, + * 1MB = 42 slots, 2MB = 50 slots. The actual calculation is 'find the + * first slot whose position base is equal to or more than the required + * window size'. This would explain why other tables in the document refer + * to 50 slots rather than 42. + * + * The constant NUM_PRIMARY_LENGTHS used in the decompression pseudocode + * is not defined in the specification. + * + * The LZX document does not state the uncompressed block has an + * uncompressed length field. Where does this length field come from, so + * we can know how large the block is? The implementation has it as the 24 + * bits following after the 3 blocktype bits, before the alignment + * padding. + * + * The LZX document states that aligned offset blocks have their aligned + * offset huffman tree AFTER the main and length trees. The implementation + * suggests that the aligned offset tree is BEFORE the main and length + * trees. + * + * The LZX document decoding algorithm states that, in an aligned offset + * block, if an extra_bits value is 1, 2 or 3, then that number of bits + * should be read and the result added to the match offset. This is + * correct for 1 and 2, but not 3, where just a huffman symbol (using the + * aligned tree) should be read. + * + * Regarding the E8 preprocessing, the LZX document states 'No translation + * may be performed on the last 6 bytes of the input block'. This is + * correct. However, the pseudocode provided checks for the *E8 leader* + * up to the last 6 bytes. If the leader appears between -10 and -7 bytes + * from the end, this would cause the next four bytes to be modified, at + * least one of which would be in the last 6 bytes, which is not allowed + * according to the spec. + * + * The specification states that the huffman trees must always contain at + * least one element. However, many CAB files contain blocks where the + * length tree is completely empty (because there are no matches), and + * this is expected to succeed. + * + * The errors in LZX documentation appear have been corrected in the + * new documentation for the LZX DELTA format. + * + * http://msdn.microsoft.com/en-us/library/cc483133.aspx + * + * However, this is a different format, an extension of regular LZX. + * I have noticed the following differences, there may be more: + * + * The maximum window size has increased from 2MB to 32MB. This also + * increases the maximum number of position slots, etc. + * + * If the match length is 257 (the maximum possible), this signals + * a further length decoding step, that allows for matches up to + * 33024 bytes long. + * + * The format now allows for "reference data", supplied by the caller. + * If match offsets go further back than the number of bytes + * decompressed so far, that is them accessing the reference data. + */ + +using System; +using System.IO; +using static LibMSPackSharp.Compression.Constants; + +namespace LibMSPackSharp.Compression +{ + public partial class LZX + { + /// + /// Allocates and initialises LZX decompression state for decoding an LZX + /// stream. + /// + /// This routine uses system.alloc() to allocate memory. If memory + /// allocation fails, or the parameters to this function are invalid, + /// null is returned. + /// + /// A SystemImpl structure used to read from the input stream and write to the output stream, also to allocate and free memory. + /// an input stream with the LZX data. + /// an output stream to write the decoded data to. + /// + /// the size of the decoding window, which must be + /// between 15 and 21 inclusive for regular LZX + /// data, or between 17 and 25 inclusive for + /// LZX DELTA data. + /// + /// the interval at which the LZX bitstream is + /// reset, in multiples of LZX frames (32678 + /// bytes), e.g. a value of 2 indicates the input + /// stream resets after every 65536 output bytes. + /// A value of 0 indicates that the bitstream never + /// resets, such as in CAB LZX streams. + /// + /// + /// the number of bytes to use as an input + /// bitstream buffer. + /// + /// + /// the length in bytes of the entirely + /// decompressed output stream, if known in + /// advance. It is used to correctly perform the + /// Intel E8 transformation, which must stop 6 + /// bytes before the very end of the + /// decompressed stream. It is not otherwise used + /// or adhered to. If the full decompressed + /// length is known in advance, set it here. + /// If it is NOT known, use the value 0, and call + /// lzxd_set_outputLength() once it is + /// known. If never set, 4 of the final 6 bytes + /// of the output stream may be incorrect. + /// + /// + /// should be zero for all regular LZX data, + /// non-zero for LZX DELTA encoded data. + /// + /// + /// a pointer to an initialised LZX structure, or null if + /// there was not enough memory or parameters to the function were wrong. + /// + public static LZX Init(SystemImpl system, FileStream input, FileStream output, int window_bits, int reset_interval, int input_buffer_size, long output_length, bool is_delta) + { + if (system == null) + return null; + + // LZX DELTA window sizes are between 2^17 (128KiB) and 2^25 (32MiB), + // regular LZX windows are between 2^15 (32KiB) and 2^21 (2MiB) + if (is_delta) + { + if (window_bits < 17 || window_bits > 25) + return null; + } + else + { + if (window_bits < 15 || window_bits > 21) + return null; + } + + if (reset_interval < 0 || output_length < 0) + { + Console.WriteLine("Reset interval or output length < 0"); + return null; + } + + // Round up input buffer size to multiple of two + input_buffer_size = (input_buffer_size + 1) & -2; + if (input_buffer_size < 2) + return null; + + // Allocate decompression state + LZX lzx = new LZX() + { + // Allocate decompression window and input buffer + Window = new byte[1 << window_bits], + InputBuffer = new byte[input_buffer_size], + + System = system, + InputFileHandle = input, + OutputFileHandle = output, + Offset = 0, + Length = output_length, + + InputBufferSize = (uint)input_buffer_size, + WindowSize = (uint)(1 << window_bits), + ReferenceDataSize = 0, + WindowPosition = 0, + FramePosition = 0, + Frame = 0, + ResetInterval = (uint)reset_interval, + IntelFileSize = 0, + IntelStarted = false, + Error = Error.MSPACK_ERR_OK, + NumOffsets = LZXPositionSlots[window_bits - 15] << 3, + IsDelta = is_delta, + + OutputPointer = 0, + OutputEnd = 0, + OutputIsE8 = true, + }; + + lzx.ResetState(); + lzx.INIT_BITS(); + + return lzx; + } + + /// + /// Reads LZX DELTA reference data into the window and allows + /// lzxd_decompress() to reference it. + /// + /// Call this before the first call to lzxd_decompress(). + /// + /// + /// an mspack_system implementation to use with the + /// input param. Only read() will be called. + /// + /// an input file handle to read reference data using system.read(). + /// The length of the reference data. Cannot be longer than the LZX window size. + /// An error code, or MSPACK_ERR_OK if successful + public Error SetReferenceData(SystemImpl system, FileStream input, uint length) + { + if (!IsDelta) + { + Console.WriteLine("Only LZX DELTA streams support reference data"); + return Error.MSPACK_ERR_ARGS; + } + + if (Offset != 0) + { + Console.WriteLine("Too late to set reference data after decoding starts"); + return Error.MSPACK_ERR_ARGS; + } + + if (length > WindowSize) + { + Console.WriteLine($"Reference length ({length}) is longer than the window"); + return Error.MSPACK_ERR_ARGS; + } + + if (length > 0 && (system == null || input == null)) + { + Console.WriteLine("Length > 0 but no system or input"); + return Error.MSPACK_ERR_ARGS; + } + + ReferenceDataSize = length; + if (length > 0) + { + // Copy reference data + int pos = (int)(WindowSize - length); + int bytes = system.Read(input, Window, pos, (int)length); + + // Length can't be more than 2^25, so no signedness problem + if (bytes < (int)length) + return Error.MSPACK_ERR_READ; + } + + ReferenceDataSize = length; + return Error.MSPACK_ERR_OK; + } + + // See description of outputLength in Init() + public void SetOutputLength(long outputLength) + { + if (outputLength > 0) + Length = outputLength; + } + + /// + /// Decompresses entire or partial LZX streams. + /// + /// The number of bytes of data that should be decompressed is given as the + /// out_bytes parameter. If more bytes are decoded than are needed, they + /// will be kept over for a later invocation. + /// + /// The output bytes will be passed to the system.write() function given in + /// lzxd_init(), using the output file handle given in lzxd_init(). More than + /// one call may be made to system.write(). + /// Input bytes will be read in as necessary using the system.read() + /// function given in lzxd_init(), using the input file handle given in + /// lzxd_init(). This will continue until system.read() returns 0 bytes, + /// or an error. Errors will be passed out of the function as + /// MSPACK_ERR_READ errors. Input streams should convey an "end of input + /// stream" by refusing to supply all the bytes that LZX asks for when they + /// reach the end of the stream, rather than return an error code. + /// + /// If any error code other than MSPACK_ERR_OK is returned, the stream + /// should be considered unusable and lzxd_decompress() should not be + /// called again on this stream. + /// + /// LZX decompression state, as allocated by lzxd_init(). + /// the number of bytes of data to decompress. + /// an error code, or MSPACK_ERR_OK if successful + public static Error Decompress(object o, long out_bytes) + { + LZX lzx = o as LZX; + if (lzx == null) + return Error.MSPACK_ERR_ARGS; + + int warned = 0; + byte[] buf = new byte[12]; + + // Easy answers + if (lzx == null || (out_bytes < 0)) + return Error.MSPACK_ERR_ARGS; + + if (lzx.Error != Error.MSPACK_ERR_OK) + return lzx.Error; + + // Flush out any stored-up bytes before we begin + int leftover_bytes = lzx.OutputEnd - lzx.OutputPointer; + if (leftover_bytes > out_bytes) + leftover_bytes = (int)out_bytes; + + if (leftover_bytes != 0) + { + try { lzx.System.Write(lzx.OutputFileHandle, lzx.OutputIsE8 ? lzx.E8Buffer : lzx.Window, lzx.OutputPointer, leftover_bytes); } + catch { return lzx.Error = Error.MSPACK_ERR_WRITE; } + + lzx.OutputPointer += leftover_bytes; + lzx.Offset += leftover_bytes; + out_bytes -= leftover_bytes; + } + + if (out_bytes == 0) + return Error.MSPACK_ERR_OK; + + uint end_frame = (uint)((lzx.Offset + out_bytes) / LZX_FRAME_SIZE) + 1; + + while (lzx.Frame < end_frame) + { + // Have we reached the reset interval? (if there is one?) + if (lzx.ResetInterval != 0 && ((lzx.Frame % lzx.ResetInterval) == 0)) + { + if (lzx.BlockRemaining != 0) + { + // This is a file format error, we can make a best effort to extract what we can + Console.WriteLine($"{lzx.BlockRemaining} bytes remaining at reset interval"); + if (warned == 0) + { + lzx.System.Message(null, "WARNING; invalid reset interval detected during LZX decompression"); + warned++; + } + } + + // Re-read the intel header and reset the huffman lengths + lzx.ResetState(); + } + + // LZX DELTA format has chunk_size, not present in LZX format + if (lzx.IsDelta) + { + lzx.ENSURE_BITS(16); + lzx.REMOVE_BITS_MSB(16); + } + + //// Read header if necessary + //if (lzx.HeaderRead == 0) + //{ + // // Read 1 bit. If bit=0, intel_filesize = 0. + // // If bit=1, read intel filesize (32 bits) + // int j = 0; + // int i = (int)lzx.READ_BITS_MSB(1, state); + + // if (i != 0) + // { + // i = (int)lzx.READ_BITS_MSB(16, state); + // j = (int)lzx.READ_BITS_MSB(16, state); + // } + + // lzx.IntelFileSize = (i << 16) | j; + // lzx.HeaderRead = 1; + //} + + // Calculate size of frame: all frames are 32k except the final frame + // which is 32kb or less. this can only be calculated when lzx.Length + // has been filled in. + uint frame_size = LZX_FRAME_SIZE; + if (lzx.Length != 0 && (lzx.Length - lzx.Offset) < frame_size) + frame_size = (uint)(lzx.Length - lzx.Offset); + + // Decode until one more frame is available + int bytes_todo = (int)(lzx.FramePosition + frame_size - lzx.WindowPosition); + while (bytes_todo > 0) + { + // Realign if previous block was an odd-sized UNCOMPRESSED block + if ((lzx.BlockType == LZXBlockType.LZX_BLOCKTYPE_UNCOMPRESSED) && (lzx.BlockLength & 1) != 0) + { + lzx.READ_IF_NEEDED(); + if (lzx.Error != Error.MSPACK_ERR_OK) + return lzx.Error; + + lzx.InputPointer++; + } + + lzx.ReadBlockHeader(buf); + if (lzx.Error != Error.MSPACK_ERR_OK) + return lzx.Error; + + // Decode more of the block: + int this_run = Math.Min(lzx.BlockRemaining, bytes_todo); + + // Assume we decode exactly this_run bytes, for now + bytes_todo -= this_run; + lzx.BlockRemaining -= this_run; + + // Decode at least this_run bytes + switch (lzx.BlockType) + { + case LZXBlockType.LZX_BLOCKTYPE_ALIGNED: + case LZXBlockType.LZX_BLOCKTYPE_VERBATIM: + lzx.DecompressBlock(ref this_run); + if (lzx.Error != Error.MSPACK_ERR_OK) + return lzx.Error; + + // If the literal 0xE8 is anywhere in the block... + if (lzx.MAINTREE_len[0xE8] != 0) + lzx.IntelStarted = true; + + break; + + case LZXBlockType.LZX_BLOCKTYPE_UNCOMPRESSED: + // As this_run is limited not to wrap a frame, this also means it + // won't wrap the window (as the window is a multiple of 32k) + int rundest = lzx.WindowPosition; + lzx.WindowPosition += this_run; + while (this_run > 0) + { + int i = lzx.InputEnd - lzx.InputPointer; + if (i == 0) + { + lzx.READ_IF_NEEDED(); + if (lzx.Error != Error.MSPACK_ERR_OK) + return lzx.Error; + } + else + { + if (i > this_run) + i = this_run; + + Array.Copy(lzx.InputBuffer, lzx.InputPointer, lzx.Window, rundest, i); + + rundest += i; + lzx.InputPointer += i; + this_run -= i; + } + } + + // Because we can't assume otherwise + lzx.IntelStarted = true; + + break; + + default: + return lzx.Error = Error.MSPACK_ERR_DECRUNCH; // Might as well + } + + // Did the final match overrun our desired this_run length? + if (this_run < 0) + { + if ((uint)(-this_run) > lzx.BlockRemaining) + { + Console.WriteLine($"Overrun went past end of block by {-this_run} ({lzx.BlockRemaining} remaining)"); + return lzx.Error = Error.MSPACK_ERR_DECRUNCH; + } + + lzx.BlockRemaining -= -this_run; + } + } + + // Streams don't extend over frame boundaries + if ((lzx.WindowPosition - lzx.FramePosition) != frame_size) + { + Console.WriteLine($"Decode beyond output frame limits! {lzx.WindowPosition - lzx.FramePosition} != {frame_size}"); + return lzx.Error = Error.MSPACK_ERR_DECRUNCH; + } + + // Re-align input bitstream + if (lzx.BitsLeft > 0) + lzx.ENSURE_BITS(16); + if ((lzx.BitsLeft & 15) != 0) + lzx.REMOVE_BITS_MSB(lzx.BitsLeft & 15); + + // Check that we've used all of the previous frame first + if (lzx.OutputPointer != lzx.OutputEnd) + { + Console.WriteLine($"{lzx.OutputEnd - lzx.OutputPointer} avail bytes, new {frame_size} frame"); + return lzx.Error = Error.MSPACK_ERR_DECRUNCH; + } + + // Does this intel block _really_ need decoding? + if (lzx.IntelStarted && lzx.IntelFileSize != 0 && (lzx.Frame < 32768) && (frame_size > 10)) + { + lzx.UndoE8Preprocessing(frame_size); + } + else + { + lzx.OutputIsE8 = false; + lzx.OutputPointer = (int)lzx.FramePosition; + } + + lzx.OutputEnd = (int)(lzx.OutputPointer + frame_size); + + // Write a frame + int new_out_bytes = (int)((out_bytes < frame_size) ? out_bytes : frame_size); + try { lzx.System.Write(lzx.OutputFileHandle, lzx.OutputIsE8 ? lzx.E8Buffer : lzx.Window, lzx.OutputPointer, new_out_bytes); } + catch { return lzx.Error = Error.MSPACK_ERR_WRITE; } + + lzx.OutputPointer += new_out_bytes; + lzx.Offset += new_out_bytes; + out_bytes -= new_out_bytes; + + // Advance frame start position + lzx.FramePosition += frame_size; + lzx.Frame++; + + // Wrap window / frame position pointers + if (lzx.WindowPosition == lzx.WindowSize) + lzx.WindowPosition = 0; + if (lzx.FramePosition == lzx.WindowSize) + lzx.FramePosition = 0; + + } + + if (out_bytes != 0) + { + Console.WriteLine("Bytes left to output"); + return lzx.Error = Error.MSPACK_ERR_DECRUNCH; + } + + return Error.MSPACK_ERR_OK; + } + + private Error BUILD_TABLE(ushort[] table, byte[] lengths, int tablebits, int maxsymbols) + { + if (!MakeDecodeTableMSB(maxsymbols, tablebits, lengths, table)) + { + Console.WriteLine($"Failed to build table"); + return Error = Error.MSPACK_ERR_DECRUNCH; + } + + return Error = Error.MSPACK_ERR_OK; + } + + private Error BUILD_TABLE_MAYBE_EMPTY() + { + LENGTH_empty = 0; + if (!MakeDecodeTableMSB(LZX_LENGTH_MAXSYMBOLS, LZX_LENGTH_TABLEBITS, LENGTH_len, LENGTH_table)) + { + for (int i = 0; i < LZX_LENGTH_MAXSYMBOLS; i++) + { + if (LENGTH_len[i] > 0) + { + Console.WriteLine("Failed to build table"); + return Error = Error.MSPACK_ERR_DECRUNCH; + } + } + + // Empty tree - allow it, but don't decode symbols with it + LENGTH_empty = 1; + } + + return Error = Error.MSPACK_ERR_OK; + } + + private Error READ_LENGTHS(byte[] lengths, uint first, uint last) + { + if (ReadLens(lengths, first, last) != Error.MSPACK_ERR_OK) + return Error; + + return Error = Error.MSPACK_ERR_OK; + } + + private Error DecompressBlock(ref int this_run) + { + while (this_run > 0) + { + int main_element = (int)READ_HUFFSYM_MSB(MAINTREE_table, MAINTREE_len, LZX_MAINTREE_TABLEBITS, LZX_MAINTREE_MAXSYMBOLS); + if (main_element < LZX_NUM_CHARS) + { + // Literal: 0 to LZX_NUM_CHARS-1 + Window[WindowPosition++] = (byte)main_element; + this_run--; + } + else + { + // Match: LZX_NUM_CHARS + ((slot<<3) | length_header (3 bits)) + main_element -= LZX_NUM_CHARS; + + // Get match length + int match_length = main_element & LZX_NUM_PRIMARY_LENGTHS; + if (match_length == LZX_NUM_PRIMARY_LENGTHS) + { + if (LENGTH_empty != 0) + { + Console.WriteLine("LENGTH symbol needed but tree is empty"); + return Error = Error.MSPACK_ERR_DECRUNCH; + } + + int length_footer = (int)READ_HUFFSYM_MSB(LENGTH_table, LENGTH_len, LZX_LENGTH_TABLEBITS, LZX_LENGTH_MAXSYMBOLS); + match_length += length_footer; + } + + match_length += LZX_MIN_MATCH; + + // Get match offset + uint match_offset = (uint)(main_element >> 3); + switch (match_offset) + { + case 0: + match_offset = R[0]; + break; + + case 1: + match_offset = R[1]; + R[1] = R[0]; + R[0] = match_offset; + break; + + case 2: + match_offset = R[2]; + R[2] = R[0]; + R[0] = match_offset; + break; + + default: + if (BlockType == LZXBlockType.LZX_BLOCKTYPE_VERBATIM) + { + if (match_offset == 3) + { + match_offset = 1; + } + else + { + int extra = (match_offset >= 36) ? 17 : LZXExtraBits[match_offset]; + int verbatim_bits = (int)READ_BITS_MSB(extra); + match_offset = (uint)(LZXPositionBase[match_offset] - 2 + verbatim_bits); + } + } + + // LZX_BLOCKTYPE_ALIGNED + else + { + int extra = (match_offset >= 36) ? 17 : LZXExtraBits[match_offset]; + match_offset = LZXPositionBase[match_offset] - 2; + + // >3: verbatim and aligned bits + if (extra > 3) + { + extra -= 3; + int verbatim_bits = (int)READ_BITS_MSB(extra); + match_offset += (uint)(verbatim_bits << 3); + + int aligned_bits = (int)READ_HUFFSYM_MSB(ALIGNED_table, ALIGNED_len, LZX_ALIGNED_TABLEBITS, LZX_ALIGNED_MAXSYMBOLS); + match_offset += (uint)aligned_bits; + } + + // 3: aligned bits only + else if (extra == 3) + { + int aligned_bits = (int)READ_HUFFSYM_MSB(ALIGNED_table, ALIGNED_len, LZX_ALIGNED_TABLEBITS, LZX_ALIGNED_MAXSYMBOLS); + match_offset += (uint)aligned_bits; + } + + // 1-2: verbatim bits only + else if (extra > 0) + { + int verbatim_bits = (int)READ_BITS_MSB(extra); + match_offset += (uint)verbatim_bits; + } + + // 0: not defined in LZX specification! + else + { + match_offset = 1; + } + } + + // Update repeated offset LRU queue + R[2] = R[1]; R[1] = R[0]; R[0] = match_offset; + break; + } + + // LZX DELTA uses max match length to signal even longer match + if (match_length == LZX_MAX_MATCH && IsDelta) + { + int extra_len; + + // 4 entry huffman tree + ENSURE_BITS(3); + + // '0' . 8 extra length bits + if (PEEK_BITS_MSB(1) == 0) + { + REMOVE_BITS_MSB(1); + extra_len = (int)READ_BITS_MSB(8); + } + + // '10' . 10 extra length bits + 0x100 + else if (PEEK_BITS_MSB(2) == 2) + { + REMOVE_BITS_MSB(2); + extra_len = (int)READ_BITS_MSB(10); + extra_len += 0x100; + } + + // '110' . 12 extra length bits + 0x500 + else if (PEEK_BITS_MSB(3) == 6) + { + REMOVE_BITS_MSB(3); + extra_len = (int)READ_BITS_MSB(12); + extra_len += 0x500; + } + + // '111' . 15 extra length bits + else + { + REMOVE_BITS_MSB(3); + extra_len = (int)READ_BITS_MSB(15); + } + + match_length += extra_len; + } + + if ((WindowPosition + match_length) > WindowSize) + { + Console.WriteLine("Match ran over window wrap"); + return Error = Error.MSPACK_ERR_DECRUNCH; + } + + // Copy match + int rundest = WindowPosition; + int i = match_length; + + // Does match offset wrap the window? + if (match_offset > WindowPosition) + { + if (match_offset > Offset && (match_offset - WindowPosition) > ReferenceDataSize) + { + Console.WriteLine("Match offset beyond LZX stream"); + return Error = Error.MSPACK_ERR_DECRUNCH; + } + + // j = length from match offset to end of window + int j = (int)(match_offset - WindowPosition); + if (j > (int)WindowSize) + { + Console.WriteLine("Match offset beyond window boundaries"); + return Error = Error.MSPACK_ERR_DECRUNCH; + } + + int runsrc = (int)(WindowSize - j); + if (j < i) + { + // If match goes over the window edge, do two copy runs + i -= j; + while (j-- > 0) + { + Window[rundest++] = Window[runsrc++]; + } + + runsrc = 0; + } + + while (i-- > 0) + { + Window[rundest++] = Window[runsrc++]; + } + } + else + { + int runsrc = (int)(rundest - match_offset); + while (i-- > 0) + { + Window[rundest++] = Window[runsrc++]; + } + } + + this_run -= match_length; + WindowPosition += match_length; + } + } + + return Error = Error.MSPACK_ERR_OK; + } + + private Error ReadBlockHeader(byte[] buffer) + { + ENSURE_BITS(4); + + // Read block type (3 bits) and block length (24 bits) + byte block_type = (byte)READ_BITS_MSB(3); + BlockType = (LZXBlockType)block_type; + + // Read the block size + int block_size; + if (READ_BITS_MSB(1) == 1) + { + block_size = LZX_FRAME_SIZE; + } + else + { + int tmp; + block_size = 0; + + tmp = (int)READ_BITS_MSB(8); + block_size |= tmp; + tmp = (int)READ_BITS_MSB(8); + block_size <<= 8; + block_size |= tmp; + + if (WindowSize >= 65536) + { + tmp = (int)READ_BITS_MSB(8); + block_size <<= 8; + block_size |= tmp; + } + } + + BlockRemaining = BlockLength = block_size; + Console.WriteLine($"New block t {BlockType} len {BlockLength}"); + + // Read individual block headers + switch (BlockType) + { + case LZXBlockType.LZX_BLOCKTYPE_ALIGNED: + // Read lengths of and build aligned huffman decoding tree + for (byte i = 0; i < 8; i++) + { + ALIGNED_len[i] = (byte)READ_BITS_MSB(3); + } + + BUILD_TABLE(ALIGNED_table, ALIGNED_len, LZX_ALIGNED_TABLEBITS, LZX_ALIGNED_MAXSYMBOLS); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + // Read lengths of and build main huffman decoding tree + READ_LENGTHS(MAINTREE_len, 0, 256); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + READ_LENGTHS(MAINTREE_len, 256, LZX_NUM_CHARS + NumOffsets); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + BUILD_TABLE(MAINTREE_table, MAINTREE_len, LZX_MAINTREE_TABLEBITS, LZX_MAINTREE_MAXSYMBOLS); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + // Read lengths of and build lengths huffman decoding tree + READ_LENGTHS(LENGTH_len, 0, LZX_NUM_SECONDARY_LENGTHS); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + BUILD_TABLE_MAYBE_EMPTY(); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + break; + + case LZXBlockType.LZX_BLOCKTYPE_VERBATIM: + // Read lengths of and build main huffman decoding tree + READ_LENGTHS(MAINTREE_len, 0, 256); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + READ_LENGTHS(MAINTREE_len, 256, LZX_NUM_CHARS + NumOffsets); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + BUILD_TABLE(MAINTREE_table, MAINTREE_len, LZX_MAINTREE_TABLEBITS, LZX_MAINTREE_MAXSYMBOLS); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + // If the literal 0xE8 is anywhere in the block... + if (MAINTREE_len[0xE8] != 0) + IntelStarted = true; + + // Read lengths of and build lengths huffman decoding tree + READ_LENGTHS(LENGTH_len, 0, LZX_NUM_SECONDARY_LENGTHS); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + BUILD_TABLE_MAYBE_EMPTY(); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + break; + + case LZXBlockType.LZX_BLOCKTYPE_UNCOMPRESSED: + // Read 1-16 (not 0-15) bits to align to bytes + if (BitsLeft == 0) + ENSURE_BITS(16); + + BitsLeft = 0; BitBuffer = 0; + + // Read 12 bytes of stored R[0] / R[1] / R[2] values + for (int rundest = 0, k = 0; k < 12; k++) + { + READ_IF_NEEDED(); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + buffer[rundest++] = InputBuffer[InputPointer++]; + } + + // TODO: uint[] R should be a part of a state object + R[0] = (uint)(buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24)); + R[1] = (uint)(buffer[4] | (buffer[5] << 8) | (buffer[6] << 16) | (buffer[7] << 24)); + R[2] = (uint)(buffer[8] | (buffer[9] << 8) | (buffer[10] << 16) | (buffer[11] << 24)); + + break; + + default: + Console.WriteLine($"Bad block type: {BlockType}"); + return Error = Error.MSPACK_ERR_DECRUNCH; + } + + return Error = Error.MSPACK_ERR_OK; + } + + private Error ReadLens(byte[] lens, uint first, uint last) + { + // Read lengths for pretree (20 symbols, lengths stored in fixed 4 bits) + for (int i = 0; i < LZX_PRETREE_MAXSYMBOLS; i++) + { + uint y = (uint)READ_BITS_MSB(4); + PRETREE_len[i] = (byte)y; + } + + BUILD_TABLE(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + for (uint lensPtr = first; lensPtr < last;) + { + uint num_zeroes, num_same; + int tree_code = (int)READ_HUFFSYM_MSB(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS); + switch (tree_code) + { + // Code = 17, run of ([read 4 bits]+4) zeros + case 17: + num_zeroes = (uint)READ_BITS_MSB(4); + num_zeroes += 4; + while (num_zeroes-- != 0) + { + lens[lensPtr++] = 0; + } + + break; + + // Code = 18, run of ([read 5 bits]+20) zeros + case 18: + num_zeroes = (uint)READ_BITS_MSB(5); + num_zeroes += 20; + while (num_zeroes-- != 0) + { + lens[lensPtr++] = 0; + } + + break; + + // Code = 19, run of ([read 1 bit]+4) [read huffman symbol] + case 19: + num_same = (uint)READ_BITS_MSB(1); + num_same += 4; + + tree_code = (int)READ_HUFFSYM_MSB(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS); + tree_code = lens[lensPtr] - tree_code; + if (tree_code < 0) + tree_code += 17; + + while (num_same-- != 0) + { + lens[lensPtr++] = (byte)tree_code; + } + + break; + + // Code = 0 to 16, delta current length entry + default: + tree_code = lens[lensPtr] - tree_code; + if (tree_code < 0) + tree_code += 17; + + lens[lensPtr++] = (byte)tree_code; + break; + } + } + + return Error.MSPACK_ERR_OK; + } + + private void ResetState() + { + R[0] = 1; + R[1] = 1; + R[2] = 1; + HeaderRead = 0; + BlockRemaining = 0; + BlockType = LZXBlockType.LZX_BLOCKTYPE_INVALID0; + + // Initialise tables to 0 (because deltas will be applied to them) + for (int i = 0; i < LZX_MAINTREE_MAXSYMBOLS; i++) + { + MAINTREE_len[i] = 0; + } + + for (int i = 0; i < LZX_LENGTH_MAXSYMBOLS; i++) + { + LENGTH_len[i] = 0; + } + } + + private void UndoE8Preprocessing(uint frame_size) + { + int data = 0; + int dataend = (int)(frame_size - 10); + int curpos = (int)Offset; + int filesize = IntelFileSize; + int abs_off, rel_off; + + // Copy e8 block to the e8 buffer and tweak if needed + OutputIsE8 = true; + OutputPointer = data; + Array.Copy(Window, FramePosition, E8Buffer, data, frame_size); + + while (data < dataend) + { + if (E8Buffer[data++] != 0xE8) + { + curpos++; + continue; + } + + abs_off = E8Buffer[data + 0] | (E8Buffer[data + 1] << 8) | (E8Buffer[data + 2] << 16) | (E8Buffer[data + 3] << 24); + if ((abs_off >= -curpos) && (abs_off < filesize)) + { + rel_off = (abs_off >= 0) ? abs_off - curpos : abs_off + filesize; + E8Buffer[data + 0] = (byte)rel_off; + E8Buffer[data + 1] = (byte)(rel_off >> 8); + E8Buffer[data + 2] = (byte)(rel_off >> 16); + E8Buffer[data + 3] = (byte)(rel_off >> 24); + } + + data += 4; + curpos += 5; + } + } + } +} diff --git a/BurnOutSharp/External/libmspack/Compression/LZX.ReadBits.cs b/BurnOutSharp/External/libmspack/Compression/LZX.ReadBits.cs new file mode 100644 index 00000000..370a3297 --- /dev/null +++ b/BurnOutSharp/External/libmspack/Compression/LZX.ReadBits.cs @@ -0,0 +1,34 @@ +/* This file is part of libmspack. + * (C) 2003-2013 Stuart Caie. + * + * The LZX method was created by Jonathan Forbes and Tomi Poutanen, adapted + * by Microsoft Corporation. + * + * libmspack is free software { get; set; } you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License (LGPL) version 2.1 + * + * For further details, see the file COPYING.LIB distributed with libmspack + */ + +namespace LibMSPackSharp.Compression +{ + public partial class LZX : CompressionStream + { + /// + public override void READ_BYTES() + { + READ_IF_NEEDED(); + if (Error != Error.MSPACK_ERR_OK) + return; + + byte b0 = InputBuffer[InputPointer++]; + + READ_IF_NEEDED(); + if (Error != Error.MSPACK_ERR_OK) + return; + + byte b1 = InputBuffer[InputPointer++]; + INJECT_BITS_MSB((b1 << 8) | b0, 16); + } + } +} diff --git a/BurnOutSharp/External/libmspack/Compression/LZX.ReadHuff.cs b/BurnOutSharp/External/libmspack/Compression/LZX.ReadHuff.cs new file mode 100644 index 00000000..12a42af9 --- /dev/null +++ b/BurnOutSharp/External/libmspack/Compression/LZX.ReadHuff.cs @@ -0,0 +1,20 @@ +/* This file is part of libmspack. + * (C) 2003-2013 Stuart Caie. + * + * The LZX method was created by Jonathan Forbes and Tomi Poutanen, adapted + * by Microsoft Corporation. + * + * libmspack is free software { get; set; } you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License (LGPL) version 2.1 + * + * For further details, see the file COPYING.LIB distributed with libmspack + */ + +namespace LibMSPackSharp.Compression +{ + public partial class LZX : CompressionStream + { + /// + public override Error HUFF_ERROR() => Error.MSPACK_ERR_DECRUNCH; + } +} diff --git a/BurnOutSharp/External/libmspack/Compression/LZX.cs b/BurnOutSharp/External/libmspack/Compression/LZX.cs index 680311a0..acc76f04 100644 --- a/BurnOutSharp/External/libmspack/Compression/LZX.cs +++ b/BurnOutSharp/External/libmspack/Compression/LZX.cs @@ -4,548 +4,134 @@ * The LZX method was created by Jonathan Forbes and Tomi Poutanen, adapted * by Microsoft Corporation. * - * libmspack is free software; you can redistribute it and/or modify it under + * libmspack is free software { get; set; } you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License (LGPL) version 2.1 * * For further details, see the file COPYING.LIB distributed with libmspack */ -/* Microsoft's LZX document (in cab-sdk.exe) and their implementation - * of the com.ms.util.cab Java package do not concur. - * - * In the LZX document, there is a table showing the correlation between - * window size and the number of position slots. It states that the 1MB - * window = 40 slots and the 2MB window = 42 slots. In the implementation, - * 1MB = 42 slots, 2MB = 50 slots. The actual calculation is 'find the - * first slot whose position base is equal to or more than the required - * window size'. This would explain why other tables in the document refer - * to 50 slots rather than 42. - * - * The constant NUM_PRIMARY_LENGTHS used in the decompression pseudocode - * is not defined in the specification. - * - * The LZX document does not state the uncompressed block has an - * uncompressed length field. Where does this length field come from, so - * we can know how large the block is? The implementation has it as the 24 - * bits following after the 3 blocktype bits, before the alignment - * padding. - * - * The LZX document states that aligned offset blocks have their aligned - * offset huffman tree AFTER the main and length trees. The implementation - * suggests that the aligned offset tree is BEFORE the main and length - * trees. - * - * The LZX document decoding algorithm states that, in an aligned offset - * block, if an extra_bits value is 1, 2 or 3, then that number of bits - * should be read and the result added to the match offset. This is - * correct for 1 and 2, but not 3, where just a huffman symbol (using the - * aligned tree) should be read. - * - * Regarding the E8 preprocessing, the LZX document states 'No translation - * may be performed on the last 6 bytes of the input block'. This is - * correct. However, the pseudocode provided checks for the *E8 leader* - * up to the last 6 bytes. If the leader appears between -10 and -7 bytes - * from the end, this would cause the next four bytes to be modified, at - * least one of which would be in the last 6 bytes, which is not allowed - * according to the spec. - * - * The specification states that the huffman trees must always contain at - * least one element. However, many CAB files contain blocks where the - * length tree is completely empty (because there are no matches), and - * this is expected to succeed. - * - * The errors in LZX documentation appear have been corrected in the - * new documentation for the LZX DELTA format. - * - * http://msdn.microsoft.com/en-us/library/cc483133.aspx - * - * However, this is a different format, an extension of regular LZX. - * I have noticed the following differences, there may be more: - * - * The maximum window size has increased from 2MB to 32MB. This also - * increases the maximum number of position slots, etc. - * - * If the match length is 257 (the maximum possible), this signals - * a further length decoding step, that allows for matches up to - * 33024 bytes long. - * - * The format now allows for "reference data", supplied by the caller. - * If match offsets go further back than the number of bytes - * decompressed so far, that is them accessing the reference data. - */ - -using System; -using System.IO; using static LibMSPackSharp.Compression.Constants; namespace LibMSPackSharp.Compression { - public class LZX + public partial class LZX : CompressionStream { /// - /// Allocates and initialises LZX decompression state for decoding an LZX - /// stream. - /// - /// This routine uses system.alloc() to allocate memory. If memory - /// allocation fails, or the parameters to this function are invalid, - /// null is returned. + /// Number of bytes actually output /// - /// - /// an mspack_system structure used to read from - /// the input stream and write to the output - /// stream, also to allocate and free memory. - /// - /// an input stream with the LZX data. - /// an output stream to write the decoded data to. - /// - /// the size of the decoding window, which must be - /// between 15 and 21 inclusive for regular LZX - /// data, or between 17 and 25 inclusive for - /// LZX DELTA data. - /// - /// the interval at which the LZX bitstream is - /// reset, in multiples of LZX frames (32678 - /// bytes), e.g. a value of 2 indicates the input - /// stream resets after every 65536 output bytes. - /// A value of 0 indicates that the bitstream never - /// resets, such as in CAB LZX streams. - /// - /// - /// the number of bytes to use as an input - /// bitstream buffer. - /// - /// - /// the length in bytes of the entirely - /// decompressed output stream, if known in - /// advance. It is used to correctly perform the - /// Intel E8 transformation, which must stop 6 - /// bytes before the very end of the - /// decompressed stream. It is not otherwise used - /// or adhered to. If the full decompressed - /// length is known in advance, set it here. - /// If it is NOT known, use the value 0, and call - /// lzxd_set_outputLength() once it is - /// known. If never set, 4 of the final 6 bytes - /// of the output stream may be incorrect. - /// - /// - /// should be zero for all regular LZX data, - /// non-zero for LZX DELTA encoded data. - /// - /// - /// a pointer to an initialised LZXDStream structure, or null if - /// there was not enough memory or parameters to the function were wrong. - /// - public static LZXDStream Init(SystemImpl system, FileStream input, FileStream output, int window_bits, int reset_interval, int input_buffer_size, long output_length, bool is_delta) - { - if (system == null) - return null; - - // LZX DELTA window sizes are between 2^17 (128KiB) and 2^25 (32MiB), - // regular LZX windows are between 2^15 (32KiB) and 2^21 (2MiB) - if (is_delta) - { - if (window_bits < 17 || window_bits > 25) - return null; - } - else - { - if (window_bits < 15 || window_bits > 21) - return null; - } - - if (reset_interval < 0 || output_length < 0) - { - Console.WriteLine("Reset interval or output length < 0"); - return null; - } - - // Round up input buffer size to multiple of two - input_buffer_size = (input_buffer_size + 1) & -2; - if (input_buffer_size < 2) - return null; - - // Allocate decompression state - LZXDStream lzx = new LZXDStream() - { - // Allocate decompression window and input buffer - Window = new byte[1 << window_bits], - InputBuffer = new byte[input_buffer_size], - - System = system, - InputFileHandle = input, - OutputFileHandle = output, - Offset = 0, - Length = output_length, - - InputBufferSize = (uint)input_buffer_size, - WindowSize = (uint)(1 << window_bits), - ReferenceDataSize = 0, - WindowPosition = 0, - FramePosition = 0, - Frame = 0, - ResetInterval = (uint)reset_interval, - IntelFileSize = 0, - IntelStarted = false, - Error = Error.MSPACK_ERR_OK, - NumOffsets = LZXPositionSlots[window_bits - 15] << 3, - IsDelta = is_delta, - - OutputPointer = 0, - OutputEnd = 0, - OutputIsE8 = true, - }; - - lzx.ResetState(); - lzx.INIT_BITS(); - - return lzx; - } + public long Offset { get; set; } /// - /// Reads LZX DELTA reference data into the window and allows - /// lzxd_decompress() to reference it. - /// - /// Call this before the first call to lzxd_decompress(). + /// Overall decompressed length of stream /// - /// the LZX stream to apply this reference data to - /// - /// an mspack_system implementation to use with the - /// input param. Only read() will be called. - /// - /// an input file handle to read reference data using system.read(). - /// - /// the length of the reference data. Cannot be longer - /// than the LZX window size. - /// - /// An error code, or MSPACK_ERR_OK if successful - public static Error SetReferenceData(LZXDStream lzx, SystemImpl system, FileStream input, uint length) - { - if (lzx == null) - return Error.MSPACK_ERR_ARGS; - - if (!lzx.IsDelta) - { - Console.WriteLine("Only LZX DELTA streams support reference data"); - return Error.MSPACK_ERR_ARGS; - } - - if (lzx.Offset != 0) - { - Console.WriteLine("Too late to set reference data after decoding starts"); - return Error.MSPACK_ERR_ARGS; - } - - if (length > lzx.WindowSize) - { - Console.WriteLine($"Reference length ({length}) is longer than the window"); - return Error.MSPACK_ERR_ARGS; - } - - if (length > 0 && (system == null || input == null)) - { - Console.WriteLine("Length > 0 but no system or input"); - return Error.MSPACK_ERR_ARGS; - } - - lzx.ReferenceDataSize = length; - if (length > 0) - { - // Copy reference data - int pos = (int)(lzx.WindowSize - length); - int bytes = system.Read(input, lzx.Window, pos, (int)length); - - // Length can't be more than 2^25, so no signedness problem - if (bytes < (int)length) - return Error.MSPACK_ERR_READ; - } - - lzx.ReferenceDataSize = length; - return Error.MSPACK_ERR_OK; - } - - // See description of outputLength in Init() - public static void SetOutputLength(LZXDStream lzx, long outputLength) - { - if (lzx != null && outputLength > 0) - lzx.Length = outputLength; - } + public long Length { get; set; } /// - /// Decompresses entire or partial LZX streams. - /// - /// The number of bytes of data that should be decompressed is given as the - /// out_bytes parameter. If more bytes are decoded than are needed, they - /// will be kept over for a later invocation. - /// - /// The output bytes will be passed to the system.write() function given in - /// lzxd_init(), using the output file handle given in lzxd_init(). More than - /// one call may be made to system.write(). - /// Input bytes will be read in as necessary using the system.read() - /// function given in lzxd_init(), using the input file handle given in - /// lzxd_init(). This will continue until system.read() returns 0 bytes, - /// or an error. Errors will be passed out of the function as - /// MSPACK_ERR_READ errors. Input streams should convey an "end of input - /// stream" by refusing to supply all the bytes that LZX asks for when they - /// reach the end of the stream, rather than return an error code. - /// - /// If any error code other than MSPACK_ERR_OK is returned, the stream - /// should be considered unusable and lzxd_decompress() should not be - /// called again on this stream. + /// Decoding window /// - /// LZX decompression state, as allocated by lzxd_init(). - /// the number of bytes of data to decompress. - /// an error code, or MSPACK_ERR_OK if successful - public static Error Decompress(object o, long out_bytes) - { - LZXDStream lzx = o as LZXDStream; - if (lzx == null) - return Error.MSPACK_ERR_ARGS; + public byte[] Window { get; set; } - int warned = 0; - byte[] buf = new byte[12]; + /// + /// Window size + /// + public uint WindowSize { get; set; } - // Easy answers - if (lzx == null || (out_bytes < 0)) - return Error.MSPACK_ERR_ARGS; + /// + /// LZX DELTA reference data size + /// + public uint ReferenceDataSize { get; set; } - if (lzx.Error != Error.MSPACK_ERR_OK) - return lzx.Error; + /// + /// Number of match_offset entries in table + /// + public uint NumOffsets { get; set; } - // Flush out any stored-up bytes before we begin - int leftover_bytes = lzx.OutputEnd - lzx.OutputPointer; - if (leftover_bytes > out_bytes) - leftover_bytes = (int)out_bytes; + /// + /// Decompression offset within window + /// + public int WindowPosition { get; set; } - if (leftover_bytes != 0) - { - try { lzx.System.Write(lzx.OutputFileHandle, lzx.OutputIsE8 ? lzx.E8Buffer : lzx.Window, lzx.OutputPointer, leftover_bytes); } - catch { return lzx.Error = Error.MSPACK_ERR_WRITE; } + /// + /// Current frame offset within in window + /// + public uint FramePosition { get; set; } - lzx.OutputPointer += leftover_bytes; - lzx.Offset += leftover_bytes; - out_bytes -= leftover_bytes; - } + /// + /// The number of 32kb frames processed + /// + public uint Frame { get; set; } - if (out_bytes == 0) - return Error.MSPACK_ERR_OK; + /// + /// Which frame do we reset the compressor? + /// + public uint ResetInterval { get; set; } - // Restore local state - BufferState state = lzx.RESTORE_BITS(); - byte[] window = lzx.Window; - int window_posn = lzx.WindowPosition; - uint[] R = lzx.R; + /// + /// For the LRU offset system + /// + public uint[] R { get; set; } = new uint[3]; - uint end_frame = (uint)((lzx.Offset + out_bytes) / LZX_FRAME_SIZE) + 1; + /// + /// Uncompressed length of this LZX block + /// + public int BlockLength { get; set; } - while (lzx.Frame < end_frame) - { - // Have we reached the reset interval? (if there is one?) - if (lzx.ResetInterval != 0 && ((lzx.Frame % lzx.ResetInterval) == 0)) - { - if (lzx.BlockRemaining != 0) - { - // This is a file format error, we can make a best effort to extract what we can - Console.WriteLine($"{lzx.BlockRemaining} bytes remaining at reset interval"); - if (warned == 0) - { - lzx.System.Message(null, "WARNING; invalid reset interval detected during LZX decompression"); - warned++; - } - } + /// + /// Uncompressed bytes still left to decode + /// + public int BlockRemaining { get; set; } - // Re-read the intel header and reset the huffman lengths - lzx.ResetState(); - R = lzx.R; - } + /// + /// Magic header value used for transform + /// + public int IntelFileSize { get; set; } - // LZX DELTA format has chunk_size, not present in LZX format - if (lzx.IsDelta) - { - lzx.ENSURE_BITS(16, state); - state.REMOVE_BITS_MSB(16); - } + /// + /// Has intel E8 decoding started? + /// + public bool IntelStarted { get; set; } - //// Read header if necessary - //if (lzx.HeaderRead == 0) - //{ - // // Read 1 bit. If bit=0, intel_filesize = 0. - // // If bit=1, read intel filesize (32 bits) - // int j = 0; - // int i = (int)lzx.READ_BITS_MSB(1, state); + /// + /// Type of the current block + /// + public LZXBlockType BlockType { get; set; } - // if (i != 0) - // { - // i = (int)lzx.READ_BITS_MSB(16, state); - // j = (int)lzx.READ_BITS_MSB(16, state); - // } + /// + /// Have we started decoding at all yet? + /// + public byte HeaderRead { get; set; } - // lzx.IntelFileSize = (i << 16) | j; - // lzx.HeaderRead = 1; - //} + /// + /// Does stream follow LZX DELTA spec? + /// + public bool IsDelta { get; set; } - // Calculate size of frame: all frames are 32k except the final frame - // which is 32kb or less. this can only be calculated when lzx.Length - // has been filled in. - uint frame_size = LZX_FRAME_SIZE; - if (lzx.Length != 0 && (lzx.Length - lzx.Offset) < frame_size) - frame_size = (uint)(lzx.Length - lzx.Offset); + #region Huffman code lengths - // Decode until one more frame is available - int bytes_todo = (int)(lzx.FramePosition + frame_size - window_posn); - while (bytes_todo > 0) - { - // Realign if previous block was an odd-sized UNCOMPRESSED block - if ((lzx.BlockType == LZXBlockType.LZX_BLOCKTYPE_UNCOMPRESSED) && (lzx.BlockLength & 1) != 0) - { - lzx.READ_IF_NEEDED(state); - if (lzx.Error != Error.MSPACK_ERR_OK) - return lzx.Error; + public byte[] PRETREE_len { get; set; } = new byte[LZX_PRETREE_MAXSYMBOLS + LZX_LENTABLE_SAFETY]; + public byte[] MAINTREE_len { get; set; } = new byte[LZX_MAINTREE_MAXSYMBOLS + LZX_LENTABLE_SAFETY]; + public byte[] LENGTH_len { get; set; } = new byte[LZX_LENGTH_MAXSYMBOLS + LZX_LENTABLE_SAFETY]; + public byte[] ALIGNED_len { get; set; } = new byte[LZX_ALIGNED_MAXSYMBOLS + LZX_LENTABLE_SAFETY]; - state.InputPointer++; - } + #endregion - lzx.ReadBlockHeader(buf, ref R, state); - if (lzx.Error != Error.MSPACK_ERR_OK) - return lzx.Error; + #region Huffman decoding tables - // Decode more of the block: - int this_run = Math.Min(lzx.BlockRemaining, bytes_todo); + public ushort[] PRETREE_table { get; set; } = new ushort[(1 << LZX_PRETREE_TABLEBITS) + (LZX_PRETREE_MAXSYMBOLS * 2)]; + public ushort[] MAINTREE_table { get; set; } = new ushort[(1 << LZX_MAINTREE_TABLEBITS) + (LZX_MAINTREE_MAXSYMBOLS * 2)]; + public ushort[] LENGTH_table { get; set; } = new ushort[(1 << LZX_LENGTH_TABLEBITS) + (LZX_LENGTH_MAXSYMBOLS * 2)]; + public ushort[] ALIGNED_table { get; set; } = new ushort[(1 << LZX_ALIGNED_TABLEBITS) + (LZX_ALIGNED_MAXSYMBOLS * 2)]; - // Assume we decode exactly this_run bytes, for now - bytes_todo -= this_run; - lzx.BlockRemaining -= this_run; + #endregion + + public byte LENGTH_empty { get; set; } - // Decode at least this_run bytes - switch (lzx.BlockType) - { - case LZXBlockType.LZX_BLOCKTYPE_ALIGNED: - case LZXBlockType.LZX_BLOCKTYPE_VERBATIM: - lzx.DecompressBlock(window, ref window_posn, ref this_run, ref R, state); - if (lzx.Error != Error.MSPACK_ERR_OK) - return lzx.Error; + // This is used purely for doing the intel E8 transform + public byte[] E8Buffer { get; set; } = new byte[LZX_FRAME_SIZE]; - // If the literal 0xE8 is anywhere in the block... - if (lzx.MAINTREE_len[0xE8] != 0) - lzx.IntelStarted = true; - - break; - - case LZXBlockType.LZX_BLOCKTYPE_UNCOMPRESSED: - // As this_run is limited not to wrap a frame, this also means it - // won't wrap the window (as the window is a multiple of 32k) - int rundest = window_posn; - window_posn += this_run; - while (this_run > 0) - { - int i = state.InputEnd - state.InputPointer; - if (i == 0) - { - lzx.READ_IF_NEEDED(state); - if (lzx.Error != Error.MSPACK_ERR_OK) - return lzx.Error; - } - else - { - if (i > this_run) - i = this_run; - - Array.Copy(lzx.InputBuffer, state.InputPointer, window, rundest, i); - - rundest += i; - state.InputPointer += i; - this_run -= i; - } - } - - // Because we can't assume otherwise - lzx.IntelStarted = true; - - break; - - default: - return lzx.Error = Error.MSPACK_ERR_DECRUNCH; // Might as well - } - - // Did the final match overrun our desired this_run length? - if (this_run < 0) - { - if ((uint)(-this_run) > lzx.BlockRemaining) - { - Console.WriteLine($"Overrun went past end of block by {-this_run} ({lzx.BlockRemaining} remaining)"); - return lzx.Error = Error.MSPACK_ERR_DECRUNCH; - } - - lzx.BlockRemaining -= -this_run; - } - } - - // Streams don't extend over frame boundaries - if ((window_posn - lzx.FramePosition) != frame_size) - { - Console.WriteLine($"Decode beyond output frame limits! {window_posn - lzx.FramePosition} != {frame_size}"); - return lzx.Error = Error.MSPACK_ERR_DECRUNCH; - } - - // Re-align input bitstream - if (state.BitsLeft > 0) - lzx.ENSURE_BITS(16, state); - if ((state.BitsLeft & 15) != 0) - state.REMOVE_BITS_MSB(state.BitsLeft & 15); - - // Check that we've used all of the previous frame first - if (lzx.OutputPointer != lzx.OutputEnd) - { - Console.WriteLine($"{lzx.OutputEnd - lzx.OutputPointer} avail bytes, new {frame_size} frame"); - return lzx.Error = Error.MSPACK_ERR_DECRUNCH; - } - - // Does this intel block _really_ need decoding? - if (lzx.IntelStarted && lzx.IntelFileSize != 0 && (lzx.Frame < 32768) && (frame_size > 10)) - { - lzx.UndoE8Preprocessing(frame_size); - } - else - { - lzx.OutputIsE8 = false; - lzx.OutputPointer = (int)lzx.FramePosition; - } - - lzx.OutputEnd = (int)(lzx.OutputPointer + frame_size); - - // Write a frame - int new_out_bytes = (int)((out_bytes < frame_size) ? out_bytes : frame_size); - try { lzx.System.Write(lzx.OutputFileHandle, lzx.OutputIsE8 ? lzx.E8Buffer : lzx.Window, lzx.OutputPointer, new_out_bytes); } - catch { return lzx.Error = Error.MSPACK_ERR_WRITE; } - - lzx.OutputPointer += new_out_bytes; - lzx.Offset += new_out_bytes; - out_bytes -= new_out_bytes; - - // Advance frame start position - lzx.FramePosition += frame_size; - lzx.Frame++; - - // Wrap window / frame position pointers - if (window_posn == lzx.WindowSize) - window_posn = 0; - if (lzx.FramePosition == lzx.WindowSize) - lzx.FramePosition = 0; - - } - - if (out_bytes != 0) - { - Console.WriteLine("Bytes left to output"); - return lzx.Error = Error.MSPACK_ERR_DECRUNCH; - } - - // Store local state - lzx.STORE_BITS(state); - lzx.WindowPosition = window_posn; - lzx.R = R; - - return Error.MSPACK_ERR_OK; - } + /// + /// Is the output pointer referring to E8? + /// + public bool OutputIsE8 { get; set; } } } diff --git a/BurnOutSharp/External/libmspack/Compression/LZXDStream.cs b/BurnOutSharp/External/libmspack/Compression/LZXDStream.cs deleted file mode 100644 index 95676cfa..00000000 --- a/BurnOutSharp/External/libmspack/Compression/LZXDStream.cs +++ /dev/null @@ -1,695 +0,0 @@ -/* This file is part of libmspack. - * (C) 2003-2013 Stuart Caie. - * - * The LZX method was created by Jonathan Forbes and Tomi Poutanen, adapted - * by Microsoft Corporation. - * - * libmspack is free software { get; set; } you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License (LGPL) version 2.1 - * - * For further details, see the file COPYING.LIB distributed with libmspack - */ - -using System; -using static LibMSPackSharp.Compression.Constants; - -namespace LibMSPackSharp.Compression -{ - public class LZXDStream : CompressionStream - { - #region Fields - - /// - /// Number of bytes actually output - /// - public long Offset { get; set; } - - /// - /// Overall decompressed length of stream - /// - public long Length { get; set; } - - /// - /// Decoding window - /// - public byte[] Window { get; set; } - - /// - /// Window size - /// - public uint WindowSize { get; set; } - - /// - /// LZX DELTA reference data size - /// - public uint ReferenceDataSize { get; set; } - - /// - /// Number of match_offset entries in table - /// - public uint NumOffsets { get; set; } - - /// - /// Decompression offset within window - /// - public int WindowPosition { get; set; } - - /// - /// Current frame offset within in window - /// - public uint FramePosition { get; set; } - - /// - /// The number of 32kb frames processed - /// - public uint Frame { get; set; } - - /// - /// Which frame do we reset the compressor? - /// - public uint ResetInterval { get; set; } - - /// - /// For the LRU offset system - /// - public uint[] R { get; set; } = new uint[3]; - - /// - /// Uncompressed length of this LZX block - /// - public int BlockLength { get; set; } - - /// - /// Uncompressed bytes still left to decode - /// - public int BlockRemaining { get; set; } - - /// - /// Magic header value used for transform - /// - public int IntelFileSize { get; set; } - - /// - /// Has intel E8 decoding started? - /// - public bool IntelStarted { get; set; } - - /// - /// Type of the current block - /// - public LZXBlockType BlockType { get; set; } - - /// - /// Have we started decoding at all yet? - /// - public byte HeaderRead { get; set; } - - /// - /// Does stream follow LZX DELTA spec? - /// - public bool IsDelta { get; set; } - - #region Huffman code lengths - - public byte[] PRETREE_len { get; set; } = new byte[LZX_PRETREE_MAXSYMBOLS + LZX_LENTABLE_SAFETY]; - public byte[] MAINTREE_len { get; set; } = new byte[LZX_MAINTREE_MAXSYMBOLS + LZX_LENTABLE_SAFETY]; - public byte[] LENGTH_len { get; set; } = new byte[LZX_LENGTH_MAXSYMBOLS + LZX_LENTABLE_SAFETY]; - public byte[] ALIGNED_len { get; set; } = new byte[LZX_ALIGNED_MAXSYMBOLS + LZX_LENTABLE_SAFETY]; - - #endregion - - #region Huffman decoding tables - - public ushort[] PRETREE_table { get; set; } = new ushort[(1 << LZX_PRETREE_TABLEBITS) + (LZX_PRETREE_MAXSYMBOLS * 2)]; - public ushort[] MAINTREE_table { get; set; } = new ushort[(1 << LZX_MAINTREE_TABLEBITS) + (LZX_MAINTREE_MAXSYMBOLS * 2)]; - public ushort[] LENGTH_table { get; set; } = new ushort[(1 << LZX_LENGTH_TABLEBITS) + (LZX_LENGTH_MAXSYMBOLS * 2)]; - public ushort[] ALIGNED_table { get; set; } = new ushort[(1 << LZX_ALIGNED_TABLEBITS) + (LZX_ALIGNED_MAXSYMBOLS * 2)]; - - #endregion - - public byte LENGTH_empty { get; set; } - - // This is used purely for doing the intel E8 transform - public byte[] E8Buffer { get; set; } = new byte[LZX_FRAME_SIZE]; - - /// - /// Is the output pointer referring to E8? - /// - public bool OutputIsE8 { get; set; } - - #endregion - - #region Specialty Methods - - public Error DecompressBlock(byte[] window, ref int window_posn, ref int this_run, ref uint[] R, BufferState state) - { - while (this_run > 0) - { - int main_element = (int)READ_HUFFSYM_MSB(MAINTREE_table, MAINTREE_len, LZX_MAINTREE_TABLEBITS, LZX_MAINTREE_MAXSYMBOLS, state); - if (main_element < LZX_NUM_CHARS) - { - // Literal: 0 to LZX_NUM_CHARS-1 - window[window_posn++] = (byte)main_element; - this_run--; - } - else - { - // Match: LZX_NUM_CHARS + ((slot<<3) | length_header (3 bits)) - main_element -= LZX_NUM_CHARS; - - // Get match length - int match_length = main_element & LZX_NUM_PRIMARY_LENGTHS; - if (match_length == LZX_NUM_PRIMARY_LENGTHS) - { - if (LENGTH_empty != 0) - { - Console.WriteLine("LENGTH symbol needed but tree is empty"); - return Error = Error.MSPACK_ERR_DECRUNCH; - } - - int length_footer = (int)READ_HUFFSYM_MSB(LENGTH_table, LENGTH_len, LZX_LENGTH_TABLEBITS, LZX_LENGTH_MAXSYMBOLS, state); - match_length += length_footer; - } - - match_length += LZX_MIN_MATCH; - - // Get match offset - uint match_offset = (uint)(main_element >> 3); - switch (match_offset) - { - case 0: - match_offset = R[0]; - break; - - case 1: - match_offset = R[1]; - R[1] = R[0]; - R[0] = match_offset; - break; - - case 2: - match_offset = R[2]; - R[2] = R[0]; - R[0] = match_offset; - break; - - default: - if (BlockType == LZXBlockType.LZX_BLOCKTYPE_VERBATIM) - { - if (match_offset == 3) - { - match_offset = 1; - } - else - { - int extra = (match_offset >= 36) ? 17 : LZXExtraBits[match_offset]; - int verbatim_bits = (int)READ_BITS_MSB(extra, state); - match_offset = (uint)(LZXPositionBase[match_offset] - 2 + verbatim_bits); - } - } - - // LZX_BLOCKTYPE_ALIGNED - else - { - int extra = (match_offset >= 36) ? 17 : LZXExtraBits[match_offset]; - match_offset = LZXPositionBase[match_offset] - 2; - - // >3: verbatim and aligned bits - if (extra > 3) - { - extra -= 3; - int verbatim_bits = (int)READ_BITS_MSB(extra, state); - match_offset += (uint)(verbatim_bits << 3); - - int aligned_bits = (int)READ_HUFFSYM_MSB(ALIGNED_table, ALIGNED_len, LZX_ALIGNED_TABLEBITS, LZX_ALIGNED_MAXSYMBOLS, state); - match_offset += (uint)aligned_bits; - } - - // 3: aligned bits only - else if (extra == 3) - { - int aligned_bits = (int)READ_HUFFSYM_MSB(ALIGNED_table, ALIGNED_len, LZX_ALIGNED_TABLEBITS, LZX_ALIGNED_MAXSYMBOLS, state); - match_offset += (uint)aligned_bits; - } - - // 1-2: verbatim bits only - else if (extra > 0) - { - int verbatim_bits = (int)READ_BITS_MSB(extra, state); - match_offset += (uint)verbatim_bits; - } - - // 0: not defined in LZX specification! - else - { - match_offset = 1; - } - } - - // Update repeated offset LRU queue - R[2] = R[1]; R[1] = R[0]; R[0] = match_offset; - break; - } - - // LZX DELTA uses max match length to signal even longer match - if (match_length == LZX_MAX_MATCH && IsDelta) - { - int extra_len; - - // 4 entry huffman tree - ENSURE_BITS(3, state); - - // '0' . 8 extra length bits - if (PEEK_BITS_MSB(1, state.BitBuffer) == 0) - { - state.REMOVE_BITS_MSB(1); - extra_len = (int)READ_BITS_MSB(8, state); - } - - // '10' . 10 extra length bits + 0x100 - else if (PEEK_BITS_MSB(2, state.BitBuffer) == 2) - { - state.REMOVE_BITS_MSB(2); - extra_len = (int)READ_BITS_MSB(10, state); - extra_len += 0x100; - } - - // '110' . 12 extra length bits + 0x500 - else if (PEEK_BITS_MSB(3, state.BitBuffer) == 6) - { - state.REMOVE_BITS_MSB(3); - extra_len = (int)READ_BITS_MSB(12, state); - extra_len += 0x500; - } - - // '111' . 15 extra length bits - else - { - state.REMOVE_BITS_MSB(3); - extra_len = (int)READ_BITS_MSB(15, state); - } - - match_length += extra_len; - } - - if ((window_posn + match_length) > WindowSize) - { - Console.WriteLine("Match ran over window wrap"); - return Error = Error.MSPACK_ERR_DECRUNCH; - } - - // Copy match - int rundest = window_posn; - int i = match_length; - - // Does match offset wrap the window? - if (match_offset > window_posn) - { - if (match_offset > Offset && (match_offset - window_posn) > ReferenceDataSize) - { - Console.WriteLine("Match offset beyond LZX stream"); - return Error = Error.MSPACK_ERR_DECRUNCH; - } - - // j = length from match offset to end of window - int j = (int)(match_offset - window_posn); - if (j > (int)WindowSize) - { - Console.WriteLine("Match offset beyond window boundaries"); - return Error = Error.MSPACK_ERR_DECRUNCH; - } - - int runsrc = (int)(WindowSize - j); - if (j < i) - { - // If match goes over the window edge, do two copy runs - i -= j; - while (j-- > 0) - { - window[rundest++] = window[runsrc++]; - } - - runsrc = 0; - } - - while (i-- > 0) - { - window[rundest++] = window[runsrc++]; - } - } - else - { - int runsrc = (int)(rundest - match_offset); - while (i-- > 0) - { - window[rundest++] = window[runsrc++]; - } - } - - this_run -= match_length; - window_posn += match_length; - } - } - - return Error = Error.MSPACK_ERR_OK; - } - - public Error ReadBlockHeader(byte[] buffer, ref uint[] R, BufferState state) - { - ENSURE_BITS(4, state); - - // Read block type (3 bits) and block length (24 bits) - byte block_type = (byte)READ_BITS_MSB(3, state); - BlockType = (LZXBlockType)block_type; - - // Read the block size - int block_size; - if (READ_BITS_MSB(1, state) == 1) - { - block_size = LZX_FRAME_SIZE; - } - else - { - int tmp; - block_size = 0; - - tmp = (int)READ_BITS_MSB(8, state); - block_size |= tmp; - tmp = (int)READ_BITS_MSB(8, state); - block_size <<= 8; - block_size |= tmp; - - if (WindowSize >= 65536) - { - tmp = (int)READ_BITS_MSB(8, state); - block_size <<= 8; - block_size |= tmp; - } - } - - BlockRemaining = BlockLength = block_size; - Console.WriteLine($"New block t {BlockType} len {BlockLength}"); - - // Read individual block headers - switch (BlockType) - { - case LZXBlockType.LZX_BLOCKTYPE_ALIGNED: - // Read lengths of and build aligned huffman decoding tree - for (byte i = 0; i < 8; i++) - { - ALIGNED_len[i] = (byte)READ_BITS_MSB(3, state); - } - - BUILD_TABLE(ALIGNED_table, ALIGNED_len, LZX_ALIGNED_TABLEBITS, LZX_ALIGNED_MAXSYMBOLS); - if (Error != Error.MSPACK_ERR_OK) - return Error; - - // Read lengths of and build main huffman decoding tree - READ_LENGTHS(MAINTREE_len, 0, 256, state); - if (Error != Error.MSPACK_ERR_OK) - return Error; - - READ_LENGTHS(MAINTREE_len, 256, LZX_NUM_CHARS + NumOffsets, state); - if (Error != Error.MSPACK_ERR_OK) - return Error; - - BUILD_TABLE(MAINTREE_table, MAINTREE_len, LZX_MAINTREE_TABLEBITS, LZX_MAINTREE_MAXSYMBOLS); - if (Error != Error.MSPACK_ERR_OK) - return Error; - - // Read lengths of and build lengths huffman decoding tree - READ_LENGTHS(LENGTH_len, 0, LZX_NUM_SECONDARY_LENGTHS, state); - if (Error != Error.MSPACK_ERR_OK) - return Error; - - BUILD_TABLE_MAYBE_EMPTY(); - if (Error != Error.MSPACK_ERR_OK) - return Error; - - break; - - case LZXBlockType.LZX_BLOCKTYPE_VERBATIM: - // Read lengths of and build main huffman decoding tree - READ_LENGTHS(MAINTREE_len, 0, 256, state); - if (Error != Error.MSPACK_ERR_OK) - return Error; - - READ_LENGTHS(MAINTREE_len, 256, LZX_NUM_CHARS + NumOffsets, state); - if (Error != Error.MSPACK_ERR_OK) - return Error; - - BUILD_TABLE(MAINTREE_table, MAINTREE_len, LZX_MAINTREE_TABLEBITS, LZX_MAINTREE_MAXSYMBOLS); - if (Error != Error.MSPACK_ERR_OK) - return Error; - - // If the literal 0xE8 is anywhere in the block... - if (MAINTREE_len[0xE8] != 0) - IntelStarted = true; - - // Read lengths of and build lengths huffman decoding tree - READ_LENGTHS(LENGTH_len, 0, LZX_NUM_SECONDARY_LENGTHS, state); - if (Error != Error.MSPACK_ERR_OK) - return Error; - - BUILD_TABLE_MAYBE_EMPTY(); - if (Error != Error.MSPACK_ERR_OK) - return Error; - - break; - - case LZXBlockType.LZX_BLOCKTYPE_UNCOMPRESSED: - // Read 1-16 (not 0-15) bits to align to bytes - if (state.BitsLeft == 0) - ENSURE_BITS(16, state); - - state.BitsLeft = 0; state.BitBuffer = 0; - - // Read 12 bytes of stored R[0] / R[1] / R[2] values - for (int rundest = 0, k = 0; k < 12; k++) - { - READ_IF_NEEDED(state); - if (Error != Error.MSPACK_ERR_OK) - return Error; - - buffer[rundest++] = InputBuffer[state.InputPointer++]; - } - - // TODO: uint[] R should be a part of a state object - R[0] = (uint)(buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24)); - R[1] = (uint)(buffer[4] | (buffer[5] << 8) | (buffer[6] << 16) | (buffer[7] << 24)); - R[2] = (uint)(buffer[8] | (buffer[9] << 8) | (buffer[10] << 16) | (buffer[11] << 24)); - - break; - - default: - Console.WriteLine($"Bad block type: {BlockType}"); - return Error = Error.MSPACK_ERR_DECRUNCH; - } - - return Error = Error.MSPACK_ERR_OK; - } - - public Error ReadLens(byte[] lens, uint first, uint last) - { - BufferState state = RESTORE_BITS(); - - // Read lengths for pretree (20 symbols, lengths stored in fixed 4 bits) - for (int i = 0; i < LZX_PRETREE_MAXSYMBOLS; i++) - { - uint y = (uint)READ_BITS_MSB(4, state); - PRETREE_len[i] = (byte)y; - } - - BUILD_TABLE(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS); - if (Error != Error.MSPACK_ERR_OK) - return Error; - - for (uint lensPtr = first; lensPtr < last;) - { - uint num_zeroes, num_same; - int tree_code = (int)READ_HUFFSYM_MSB(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS, state); - switch (tree_code) - { - // Code = 17, run of ([read 4 bits]+4) zeros - case 17: - num_zeroes = (uint)READ_BITS_MSB(4, state); - num_zeroes += 4; - while (num_zeroes-- != 0) - { - lens[lensPtr++] = 0; - } - - break; - - // Code = 18, run of ([read 5 bits]+20) zeros - case 18: - num_zeroes = (uint)READ_BITS_MSB(5, state); - num_zeroes += 20; - while (num_zeroes-- != 0) - { - lens[lensPtr++] = 0; - } - - break; - - // Code = 19, run of ([read 1 bit]+4) [read huffman symbol] - case 19: - num_same = (uint)READ_BITS_MSB(1, state); - num_same += 4; - - tree_code = (int)READ_HUFFSYM_MSB(PRETREE_table, PRETREE_len, LZX_PRETREE_TABLEBITS, LZX_PRETREE_MAXSYMBOLS, state); - tree_code = lens[lensPtr] - tree_code; - if (tree_code < 0) - tree_code += 17; - - while (num_same-- != 0) - { - lens[lensPtr++] = (byte)tree_code; - } - - break; - - // Code = 0 to 16, delta current length entry - default: - tree_code = lens[lensPtr] - tree_code; - if (tree_code < 0) - tree_code += 17; - - lens[lensPtr++] = (byte)tree_code; - break; - } - } - - STORE_BITS(state); - - return Error.MSPACK_ERR_OK; - } - - public void ResetState() - { - R[0] = 1; - R[1] = 1; - R[2] = 1; - HeaderRead = 0; - BlockRemaining = 0; - BlockType = LZXBlockType.LZX_BLOCKTYPE_INVALID0; - - // Initialise tables to 0 (because deltas will be applied to them) - for (int i = 0; i < LZX_MAINTREE_MAXSYMBOLS; i++) - { - MAINTREE_len[i] = 0; - } - - for (int i = 0; i < LZX_LENGTH_MAXSYMBOLS; i++) - { - LENGTH_len[i] = 0; - } - } - - public void UndoE8Preprocessing(uint frame_size) - { - int data = 0; - int dataend = (int)(frame_size - 10); - int curpos = (int)Offset; - int filesize = IntelFileSize; - int abs_off, rel_off; - - // Copy e8 block to the e8 buffer and tweak if needed - OutputIsE8 = true; - OutputPointer = data; - Array.Copy(Window, FramePosition, E8Buffer, data, frame_size); - - while (data < dataend) - { - if (E8Buffer[data++] != 0xE8) - { - curpos++; - continue; - } - - abs_off = E8Buffer[data + 0] | (E8Buffer[data + 1] << 8) | (E8Buffer[data + 2] << 16) | (E8Buffer[data + 3] << 24); - if ((abs_off >= -curpos) && (abs_off < filesize)) - { - rel_off = (abs_off >= 0) ? abs_off - curpos : abs_off + filesize; - E8Buffer[data + 0] = (byte)rel_off; - E8Buffer[data + 1] = (byte)(rel_off >> 8); - E8Buffer[data + 2] = (byte)(rel_off >> 16); - E8Buffer[data + 3] = (byte)(rel_off >> 24); - } - - data += 4; - curpos += 5; - } - } - - private Error BUILD_TABLE(ushort[] table, byte[] lengths, int tablebits, int maxsymbols) - { - if (!MakeDecodeTableMSB(maxsymbols, tablebits, lengths, table)) - { - Console.WriteLine($"Failed to build table"); - return Error = Error.MSPACK_ERR_DECRUNCH; - } - - return Error = Error.MSPACK_ERR_OK; - } - - private Error BUILD_TABLE_MAYBE_EMPTY() - { - LENGTH_empty = 0; - if (!MakeDecodeTableMSB(LZX_LENGTH_MAXSYMBOLS, LZX_LENGTH_TABLEBITS, LENGTH_len, LENGTH_table)) - { - for (int i = 0; i < LZX_LENGTH_MAXSYMBOLS; i++) - { - if (LENGTH_len[i] > 0) - { - Console.WriteLine("Failed to build table"); - return Error = Error.MSPACK_ERR_DECRUNCH; - } - } - - // Empty tree - allow it, but don't decode symbols with it - LENGTH_empty = 1; - } - - return Error = Error.MSPACK_ERR_OK; - } - - private Error READ_LENGTHS(byte[] lengths, uint first, uint last, BufferState state) - { - STORE_BITS(state); - - if (ReadLens(lengths, first, last) != Error.MSPACK_ERR_OK) - return Error; - - BufferState temp = RESTORE_BITS(); - state.InputPointer = temp.InputPointer; - state.InputEnd = temp.InputEnd; - state.BitBuffer = temp.BitBuffer; - state.BitsLeft = temp.BitsLeft; - - return Error = Error.MSPACK_ERR_OK; - } - - #endregion - - /// - public override Error HUFF_ERROR() => Error.MSPACK_ERR_DECRUNCH; - - /// - public override void READ_BYTES(BufferState state) - { - READ_IF_NEEDED(state); - if (Error != Error.MSPACK_ERR_OK) - return; - - byte b0 = InputBuffer[state.InputPointer++]; - - READ_IF_NEEDED(state); - if (Error != Error.MSPACK_ERR_OK) - return; - - byte b1 = InputBuffer[state.InputPointer++]; - INJECT_BITS_MSB((b1 << 8) | b0, 16, state); - } - } -} diff --git a/BurnOutSharp/External/libmspack/Compression/MSZIP.Decompress.cs b/BurnOutSharp/External/libmspack/Compression/MSZIP.Decompress.cs new file mode 100644 index 00000000..b507bc80 --- /dev/null +++ b/BurnOutSharp/External/libmspack/Compression/MSZIP.Decompress.cs @@ -0,0 +1,617 @@ +/* This file is part of libmspack. + * (C) 2003-2004 Stuart Caie. + * + * The deflate method was created by Phil Katz. MSZIP is equivalent to the + * deflate method. + * + * libmspack is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License (LGPL) version 2.1 + * + * For further details, see the file COPYING.LIB distributed with libmspack + */ + +using System; +using System.IO; +using static LibMSPackSharp.Compression.Constants; + +namespace LibMSPackSharp.Compression +{ + public partial class MSZIP + { + /// + /// Allocates MS-ZIP decompression stream for decoding the given stream. + /// + /// - uses system.alloc() to allocate memory + /// + /// - returns null if not enough memory + /// + /// - input_buffer_size is how many bytes to use as an input bitstream buffer + /// + /// - if RepairMode is non-zero, errors in decompression will be skipped + /// and 'holes' left will be filled with zero bytes. This allows at least + /// a partial recovery of erroneous data. + /// + public static MSZIP Init(SystemImpl system, FileStream input, FileStream output, int input_buffer_size, bool repair_mode) + { + if (system == null) + return null; + + // Round up input buffer size to multiple of two + input_buffer_size = (input_buffer_size + 1) & -2; + if (input_buffer_size < 2) + return null; + + // Allocate decompression state + return new MSZIP + { + // Allocate input buffer + InputBuffer = new byte[input_buffer_size], + + // Initialise decompression state + System = system, + InputFileHandle = input, + OutputFileHandle = output, + InputBufferSize = (uint)input_buffer_size, + EndOfInput = 0, + Error = Error.MSPACK_ERR_OK, + RepairMode = repair_mode, + + InputPointer = 0, + InputEnd = 0, + OutputPointer = 0, + OutputEnd = 0, + BitBuffer = 0, + BitsLeft = 0, + }; + } + + /// + /// Decompresses, or decompresses more of, an MS-ZIP stream. + /// + /// - out_bytes of data will be decompressed and the function will return + /// with an MSPACK_ERR_OK return code. + /// + /// - decompressing will stop as soon as out_bytes is reached. if the true + /// amount of bytes decoded spills over that amount, they will be kept for + /// a later invocation of mszipd_decompress(). + /// + /// - the output bytes will be passed to the system.write() function given in + /// mszipd_init(), using the output file handle given in mszipd_init(). More + /// than one call may be made to system.write() + /// + /// - MS-ZIP will read input bytes as necessary using the system.read() + /// function given in mszipd_init(), using the input file handle given in + /// mszipd_init(). This will continue until system.read() returns 0 bytes, + /// or an error. + /// + public static Error Decompress(object o, long out_bytes) + { + MSZIP zip = o as MSZIP; + if (zip == null) + return Error.MSPACK_ERR_ARGS; + + int i, readState; + Error error; + + // Easy answers + if (zip == null || (out_bytes < 0)) + return Error.MSPACK_ERR_ARGS; + if (zip.Error != Error.MSPACK_ERR_OK) + return zip.Error; + + // Flush out any stored-up bytes before we begin + i = zip.OutputEnd - zip.OutputPointer; + if (i > out_bytes) + i = (int)out_bytes; + + if (i != 0) + { + if (zip.System.Write(zip.OutputFileHandle, zip.Window, zip.OutputPointer, i) != i) + return zip.Error = Error.MSPACK_ERR_WRITE; + + zip.OutputPointer += i; + out_bytes -= i; + } + + if (out_bytes == 0) + return Error.MSPACK_ERR_OK; + + while (out_bytes > 0) + { + // Skip to next read 'CK' header + i = zip.BitsLeft & 7; + + // Align to bytestream + zip.REMOVE_BITS_LSB(i); + + readState = 0; + do + { + i = (int)zip.READ_BITS_LSB(8); + + if (i == 'C') + readState = 1; + else if ((readState == 1) && (i == 'K')) + readState = 2; + else + readState = 0; + } while (readState != 2); + + // Inflate a block, repair and realign if necessary + zip.WindowPosition = 0; + zip.BytesOutput = 0; + + if ((error = zip.Inflate()) != Error.MSPACK_ERR_OK) + { + Console.WriteLine($"Inflate error {error}"); + if (zip.RepairMode) + { + // Recover partially-inflated buffers + if (zip.BytesOutput == 0 && zip.WindowPosition > 0) + zip.FlushWindow(zip.WindowPosition); + + zip.System.Message(null, $"MSZIP error, {MSZIP_FRAME_SIZE - zip.BytesOutput} bytes of data lost."); + for (i = zip.BytesOutput; i < MSZIP_FRAME_SIZE; i++) + { + zip.Window[i] = 0x00; + } + + zip.BytesOutput = MSZIP_FRAME_SIZE; + } + else + { + return zip.Error = error; + } + } + + zip.OutputPointer = 0; + zip.OutputEnd = zip.BytesOutput; + + // Write a frame + i = (out_bytes < zip.BytesOutput) ? (int)out_bytes : zip.BytesOutput; + if (zip.System.Write(zip.OutputFileHandle, zip.Window, zip.OutputPointer, i) != i) + return zip.Error = Error.MSPACK_ERR_WRITE; + + // mspack errors (i.e. read errors) are fatal and can't be recovered + if ((error > 0) && zip.RepairMode) + return error; + + zip.OutputPointer += i; + out_bytes -= i; + } + + if (out_bytes != 0) + { + Console.WriteLine($"Bytes left to output: {out_bytes}"); + return zip.Error = Error.MSPACK_ERR_DECRUNCH; + } + + return Error.MSPACK_ERR_OK; + } + + /// + /// Decompresses an entire MS-ZIP stream in a KWAJ file. Acts very much + /// like mszipd_decompress(), but doesn't take an out_bytes parameter + /// + public Error DecompressKWAJ() + { + int i, block_len; + Error error; + + // Unpack blocks until block_len == 0 + for (; ; ) + { + // Align to bytestream, read block_len + i = BitsLeft & 7; + REMOVE_BITS_LSB(i); + + block_len = (int)READ_BITS_LSB(8); + i = (int)READ_BITS_LSB(8); + + block_len |= i << 8; + if (block_len == 0) + break; + + // Read "CK" header + i = (int)READ_BITS_LSB(8); + if (i != 'C') + return Error.MSPACK_ERR_DATAFORMAT; + + i = (int)READ_BITS_LSB(8); + if (i != 'K') + return Error.MSPACK_ERR_DATAFORMAT; + + // Inflate block + WindowPosition = 0; + BytesOutput = 0; + + if ((error = Inflate()) != Error.MSPACK_ERR_OK) + { + Console.WriteLine($"Inflate error {error}"); + return Error = (error > 0) ? error : Error.MSPACK_ERR_DECRUNCH; + } + + // Write inflated block + try { System.Write(OutputFileHandle, Window, 0, BytesOutput); } + catch { return Error = Error.MSPACK_ERR_WRITE; } + } + + return Error.MSPACK_ERR_OK; + } + + private Error ReadLens() + { + // bitlen Huffman codes -- immediate lookup, 7 bit max code length + ushort[] bl_table = new ushort[(1 << 7)]; + byte[] bl_len = new byte[19]; + + byte[] lens = new byte[MSZIP_LITERAL_MAXSYMBOLS + MSZIP_DISTANCE_MAXSYMBOLS]; + uint lit_codes, dist_codes, code, last_code = 0, bitlen_codes, i, run; + + // Read the number of codes + lit_codes = (uint)READ_BITS_LSB(5); + lit_codes += 257; + + dist_codes = (uint)READ_BITS_LSB(5); + dist_codes += 1; + + bitlen_codes = (uint)READ_BITS_LSB(5); + bitlen_codes += 4; + + if (lit_codes > MSZIP_LITERAL_MAXSYMBOLS) + return Error.INF_ERR_SYMLENS; + if (dist_codes > MSZIP_DISTANCE_MAXSYMBOLS) + return Error.INF_ERR_SYMLENS; + + // Read in the bit lengths in their unusual order + for (i = 0; i < bitlen_codes; i++) + { + bl_len[BitLengthOrder[i]] = (byte)READ_BITS_LSB(3); + } + + while (i < 19) + { + bl_len[BitLengthOrder[i++]] = 0; + } + + // Create decoding table with an immediate lookup + if (!CompressionStream.MakeDecodeTableLSB(19, 7, bl_len, bl_table)) + return Error.INF_ERR_BITLENTBL; + + // Read literal / distance code lengths + for (i = 0; i < (lit_codes + dist_codes); i++) + { + // Single-level huffman lookup + + ENSURE_BITS(7); + code = bl_table[PEEK_BITS_LSB(7)]; + REMOVE_BITS_LSB(bl_len[code]); + + if (code < 16) + { + lens[i] = (byte)(last_code = code); + } + else + { + switch (code) + { + case 16: + run = (uint)READ_BITS_LSB(2); + run += 3; + code = last_code; + break; + + case 17: + run = (uint)READ_BITS_LSB(3); + run += 3; + code = 0; + break; + + case 18: + run = (uint)READ_BITS_LSB(7); + run += 11; + code = 0; + break; + + default: + Console.WriteLine($"Bad code!: {code}"); + return Error.INF_ERR_BADBITLEN; + } + + if ((i + run) > (lit_codes + dist_codes)) + return Error.INF_ERR_BITOVERRUN; + + while (run-- != 0) + { + lens[i++] = (byte)code; + } + + i--; + } + } + + // Copy LITERAL code lengths and clear any remaining + i = lit_codes; + Array.Copy(lens, 0, LITERAL_len, 0, i); + while (i < MSZIP_LITERAL_MAXSYMBOLS) + { + LITERAL_len[i++] = 0; + } + + i = dist_codes; + Array.Copy(lens, lit_codes, DISTANCE_len, 0, i); + while (i < MSZIP_DISTANCE_MAXSYMBOLS) + { + DISTANCE_len[i++] = 0; + } + + return 0; + } + + /// + /// A clean implementation of RFC 1951 / inflate + /// + private Error Inflate() + { + uint last_block, block_type, distance, length, this_run, i; + Error err; + ushort sym; + + do + { + // Read in last block bit + last_block = (uint)READ_BITS_LSB(1); + + // Read in block type + block_type = (uint)READ_BITS_LSB(2); + + if (block_type == 0) + { + // Uncompressed block + byte[] lens_buf = new byte[4]; + + // Go to byte boundary + i = (uint)(BitsLeft & 7); + REMOVE_BITS_LSB((int)i); + + // Read 4 bytes of data, emptying the bit-buffer if necessary + for (i = 0; (BitsLeft >= 8); i++) + { + if (i == 4) + return Error.INF_ERR_BITBUF; + + lens_buf[i] = (byte)PEEK_BITS_LSB(8); + REMOVE_BITS_LSB(8); + } + + if (BitsLeft != 0) + return Error.INF_ERR_BITBUF; + + while (i < 4) + { + READ_IF_NEEDED(); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + lens_buf[i++] = InputBuffer[InputPointer++]; + } + + // Get the length and its complement + length = (ushort)(lens_buf[0] | (lens_buf[1] << 8)); + i = (ushort)(lens_buf[2] | (lens_buf[3] << 8)); + + ushort compl = (ushort)(~i & 0xFFFF); + if (length != compl) + return Error.INF_ERR_COMPLEMENT; + + // Read and copy the uncompressed data into the window + while (length > 0) + { + READ_IF_NEEDED(); + if (Error != Error.MSPACK_ERR_OK) + return Error; + + this_run = length; + if (this_run > (uint)(InputEnd - InputPointer)) + this_run = (uint)(InputEnd - InputPointer); + + if (this_run > (MSZIP_FRAME_SIZE - WindowPosition)) + this_run = MSZIP_FRAME_SIZE - WindowPosition; + + Array.Copy(InputBuffer, InputPointer, Window, WindowPosition, this_run); + + WindowPosition += this_run; + InputPointer += (int)this_run; + length -= this_run; + + err = FLUSH_IF_NEEDED(); + if (err != Error.MSPACK_ERR_OK) + return err; + } + } + else if ((block_type == 1) || (block_type == 2)) + { + // Huffman-compressed LZ77 block + uint match_posn, code; + + if (block_type == 1) + { + // Block with fixed Huffman codes + i = 0; + while (i < 144) + { + LITERAL_len[i++] = 8; + } + + while (i < 256) + { + LITERAL_len[i++] = 9; + } + + while (i < 280) + { + LITERAL_len[i++] = 7; + } + + while (i < 288) + { + LITERAL_len[i++] = 8; + } + + for (i = 0; i < 32; i++) + { + DISTANCE_len[i] = 5; + } + } + else + { + // Block with dynamic Huffman codes + if ((err = ReadLens()) != Error.MSPACK_ERR_OK) + return err; + } + + // Now huffman lengths are read for either kind of block, + // create huffman decoding tables + if (!CompressionStream.MakeDecodeTableLSB(MSZIP_LITERAL_MAXSYMBOLS, MSZIP_LITERAL_TABLEBITS, LITERAL_len, LITERAL_table)) + return Error.INF_ERR_LITERALTBL; + + if (!CompressionStream.MakeDecodeTableLSB(MSZIP_DISTANCE_MAXSYMBOLS, MSZIP_DISTANCE_TABLEBITS, DISTANCE_len, DISTANCE_table)) + return Error.INF_ERR_DISTANCETBL; + + // Decode forever until end of block code + for (; ; ) + { + code = (uint)READ_HUFFSYM_LSB(LITERAL_table, LITERAL_len, MSZIP_LITERAL_TABLEBITS, MSZIP_LITERAL_MAXSYMBOLS); + + if (code < 256) + { + Window[WindowPosition++] = (byte)code; + err = FLUSH_IF_NEEDED(); + if (err != Error.MSPACK_ERR_OK) + return err; + } + else if (code == 256) + { + // END OF BLOCK CODE: loop break point + break; + } + else + { + code -= 257; // Codes 257-285 are matches + if (code >= 29) + return Error.INF_ERR_LITCODE; // Codes 286-287 are illegal + + length = (uint)READ_BITS_T_LSB(LiteralExtraBits[code]); + length += LiteralLengths[code]; + + code = (uint)READ_HUFFSYM_LSB(DISTANCE_table, DISTANCE_len, MSZIP_DISTANCE_TABLEBITS, MSZIP_DISTANCE_MAXSYMBOLS); + + if (code >= 30) + return Error.INF_ERR_DISTCODE; + + distance = (uint)READ_BITS_T_LSB(DistanceExtraBits[code]); + distance += DistanceOffsets[code]; + + // Match position is window position minus distance. If distance + // is more than window position numerically, it must 'wrap + // around' the frame size. + match_posn = (uint)((distance > WindowPosition) ? MSZIP_FRAME_SIZE : 0) + WindowPosition - distance; + + // Copy match + if (length < 12) + { + // Short match, use slower loop but no loop setup code + while (length-- != 0) + { + Window[WindowPosition++] = Window[match_posn++]; + match_posn &= MSZIP_FRAME_SIZE - 1; + err = FLUSH_IF_NEEDED(); + if (err != Error.MSPACK_ERR_OK) + return err; + } + } + else + { + // Longer match, use faster loop but with setup expense */ + int runsrc, rundest; + do + { + this_run = length; + if ((match_posn + this_run) > MSZIP_FRAME_SIZE) + this_run = MSZIP_FRAME_SIZE - match_posn; + if ((WindowPosition + this_run) > MSZIP_FRAME_SIZE) + this_run = MSZIP_FRAME_SIZE - WindowPosition; + + rundest = (int)WindowPosition; + WindowPosition += this_run; + runsrc = (int)match_posn; + match_posn += this_run; + length -= this_run; + while (this_run-- != 0) + { + Window[rundest++] = Window[runsrc++]; + } + + if (match_posn == MSZIP_FRAME_SIZE) + match_posn = 0; + + err = FLUSH_IF_NEEDED(); + if (err != Error.MSPACK_ERR_OK) + return err; + } while (length > 0); + } + + } + + } + } + else + { + // block_type == 3 -- bad block type + return Error.INF_ERR_BLOCKTYPE; + } + } while (last_block == 0); + + // Flush the remaining data + if (WindowPosition != 0) + { + if (FlushWindow(WindowPosition) != Error.MSPACK_ERR_OK) + return Error.INF_ERR_FLUSH; + } + + // Return success + return Error.MSPACK_ERR_OK; + } + + private Error FLUSH_IF_NEEDED() + { + if (WindowPosition == MSZIP_FRAME_SIZE) + { + if (FlushWindow(MSZIP_FRAME_SIZE) != Error.MSPACK_ERR_OK) + return Error.INF_ERR_FLUSH; + + WindowPosition = 0; + } + + return Error.MSPACK_ERR_OK; + } + + /// + /// inflate() calls this whenever the window should be flushed. As + /// MSZIP only expands to the size of the window, the implementation used + /// simply keeps track of the amount of data flushed, and if more than 32k + /// is flushed, an error is raised. + /// + private Error FlushWindow(uint data_flushed) + { + BytesOutput += (int)data_flushed; + if (BytesOutput > MSZIP_FRAME_SIZE) + { + Console.WriteLine($"Overflow: {data_flushed} bytes flushed, total is now {BytesOutput}"); + return Error.MSPACK_ERR_ARGS; + } + + return Error.MSPACK_ERR_OK; + } + } +} diff --git a/BurnOutSharp/External/libmspack/Compression/MSZIP.ReadBits.cs b/BurnOutSharp/External/libmspack/Compression/MSZIP.ReadBits.cs new file mode 100644 index 00000000..c74fde69 --- /dev/null +++ b/BurnOutSharp/External/libmspack/Compression/MSZIP.ReadBits.cs @@ -0,0 +1,27 @@ +/* This file is part of libmspack. + * (C) 2003-2004 Stuart Caie. + * + * The deflate method was created by Phil Katz. MSZIP is equivalent to the + * deflate method. + * + * libmspack is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License (LGPL) version 2.1 + * + * For further details, see the file COPYING.LIB distributed with libmspack + */ + +namespace LibMSPackSharp.Compression +{ + public partial class MSZIP : CompressionStream + { + /// + public override void READ_BYTES() + { + READ_IF_NEEDED(); + if (Error != Error.MSPACK_ERR_OK) + return; + + INJECT_BITS_LSB(InputBuffer[InputPointer++], 8); + } + } +} diff --git a/BurnOutSharp/External/libmspack/Compression/MSZIP.ReadHuff.cs b/BurnOutSharp/External/libmspack/Compression/MSZIP.ReadHuff.cs new file mode 100644 index 00000000..dad81278 --- /dev/null +++ b/BurnOutSharp/External/libmspack/Compression/MSZIP.ReadHuff.cs @@ -0,0 +1,20 @@ +/* This file is part of libmspack. + * (C) 2003-2004 Stuart Caie. + * + * The deflate method was created by Phil Katz. MSZIP is equivalent to the + * deflate method. + * + * libmspack is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License (LGPL) version 2.1 + * + * For further details, see the file COPYING.LIB distributed with libmspack + */ + +namespace LibMSPackSharp.Compression +{ + public partial class MSZIP : CompressionStream + { + /// + public override Error HUFF_ERROR() => Error.INF_ERR_HUFFSYM; + } +} diff --git a/BurnOutSharp/External/libmspack/Compression/MSZIP.cs b/BurnOutSharp/External/libmspack/Compression/MSZIP.cs index 431b36ae..9486a19a 100644 --- a/BurnOutSharp/External/libmspack/Compression/MSZIP.cs +++ b/BurnOutSharp/External/libmspack/Compression/MSZIP.cs @@ -10,639 +10,38 @@ * For further details, see the file COPYING.LIB distributed with libmspack */ -using System; -using System.IO; using static LibMSPackSharp.Compression.Constants; namespace LibMSPackSharp.Compression { - public class MSZIP + public partial class MSZIP : CompressionStream { /// - /// Allocates MS-ZIP decompression stream for decoding the given stream. - /// - /// - uses system.alloc() to allocate memory - /// - /// - returns null if not enough memory - /// - /// - input_buffer_size is how many bytes to use as an input bitstream buffer - /// - /// - if RepairMode is non-zero, errors in decompression will be skipped - /// and 'holes' left will be filled with zero bytes. This allows at least - /// a partial recovery of erroneous data. + /// 32kb history window /// - public static MSZIPDStream Init(SystemImpl system, FileStream input, FileStream output, int input_buffer_size, bool repair_mode) - { - if (system == null) - return null; - - // Round up input buffer size to multiple of two - input_buffer_size = (input_buffer_size + 1) & -2; - if (input_buffer_size < 2) - return null; - - // Allocate decompression state - return new MSZIPDStream - { - // Allocate input buffer - InputBuffer = new byte[input_buffer_size], - - // Initialise decompression state - System = system, - InputFileHandle = input, - OutputFileHandle = output, - InputBufferSize = (uint)input_buffer_size, - EndOfInput = 0, - Error = Error.MSPACK_ERR_OK, - RepairMode = repair_mode, - FlushWindow = FlushWindow, - - OutputPointer = 0, - OutputEnd = 0, - - BufferState = new BufferState() - { - InputPointer = 0, - InputEnd = 0, - BitBuffer = 0, - BitsLeft = 0, - } - }; - } + public byte[] Window { get; set; } = new byte[MSZIP_FRAME_SIZE]; /// - /// Decompresses, or decompresses more of, an MS-ZIP stream. - /// - /// - out_bytes of data will be decompressed and the function will return - /// with an MSPACK_ERR_OK return code. - /// - /// - decompressing will stop as soon as out_bytes is reached. if the true - /// amount of bytes decoded spills over that amount, they will be kept for - /// a later invocation of mszipd_decompress(). - /// - /// - the output bytes will be passed to the system.write() function given in - /// mszipd_init(), using the output file handle given in mszipd_init(). More - /// than one call may be made to system.write() - /// - /// - MS-ZIP will read input bytes as necessary using the system.read() - /// function given in mszipd_init(), using the input file handle given in - /// mszipd_init(). This will continue until system.read() returns 0 bytes, - /// or an error. + /// Offset within window /// - public static Error Decompress(object o, long out_bytes) - { - MSZIPDStream zip = o as MSZIPDStream; - if (zip == null) - return Error.MSPACK_ERR_ARGS; + public uint WindowPosition { get; set; } - // For the bit buffer - uint bit_buffer; - int bits_left; - int i_ptr, i_end; + public bool RepairMode { get; set; } - int i, readState; - Error error; + public int BytesOutput { get; set; } - // Easy answers - if (zip == null || (out_bytes < 0)) - return Error.MSPACK_ERR_ARGS; - if (zip.Error != Error.MSPACK_ERR_OK) - return zip.Error; + #region Huffman code lengths - // Flush out any stored-up bytes before we begin - i = zip.OutputEnd - zip.OutputPointer; - if (i > out_bytes) - i = (int)out_bytes; + public byte[] LITERAL_len { get; set; } = new byte[MSZIP_LITERAL_MAXSYMBOLS]; + public byte[] DISTANCE_len { get; set; } = new byte[MSZIP_DISTANCE_MAXSYMBOLS]; - if (i != 0) - { - if (zip.System.Write(zip.OutputFileHandle, zip.Window, zip.OutputPointer, i) != i) - return zip.Error = Error.MSPACK_ERR_WRITE; + #endregion - zip.OutputPointer += i; - out_bytes -= i; - } + #region Huffman decoding tables - if (out_bytes == 0) - return Error.MSPACK_ERR_OK; + public ushort[] LITERAL_table { get; set; } = new ushort[MSZIP_LITERAL_TABLESIZE]; + public ushort[] DISTANCE_table { get; set; } = new ushort[MSZIP_DISTANCE_TABLESIZE]; - while (out_bytes > 0) - { - // Unpack another block - BufferState state = zip.RESTORE_BITS(); - - // Skip to next read 'CK' header - i = state.BitsLeft & 7; - - // Align to bytestream - state.REMOVE_BITS_LSB(i); - - readState = 0; - do - { - i = (int)zip.READ_BITS_LSB(8, state); - - if (i == 'C') - readState = 1; - else if ((readState == 1) && (i == 'K')) - readState = 2; - else - readState = 0; - } while (readState != 2); - - // Inflate a block, repair and realign if necessary - zip.WindowPosition = 0; - zip.BytesOutput = 0; - - zip.STORE_BITS(state); - - if ((error = Inflate(zip)) != Error.MSPACK_ERR_OK) - { - Console.WriteLine($"Inflate error {error}"); - if (zip.RepairMode) - { - // Recover partially-inflated buffers - if (zip.BytesOutput == 0 && zip.WindowPosition > 0) - zip.FlushWindow(zip, zip.WindowPosition); - - zip.System.Message(null, $"MSZIP error, {MSZIP_FRAME_SIZE - zip.BytesOutput} bytes of data lost."); - for (i = zip.BytesOutput; i < MSZIP_FRAME_SIZE; i++) - { - zip.Window[i] = 0x00; - } - - zip.BytesOutput = MSZIP_FRAME_SIZE; - } - else - { - return zip.Error = error; - } - } - - zip.OutputPointer = 0; - zip.OutputEnd = zip.BytesOutput; - - // Write a frame - i = (out_bytes < zip.BytesOutput) ? (int)out_bytes : zip.BytesOutput; - if (zip.System.Write(zip.OutputFileHandle, zip.Window, zip.OutputPointer, i) != i) - return zip.Error = Error.MSPACK_ERR_WRITE; - - // mspack errors (i.e. read errors) are fatal and can't be recovered - if ((error > 0) && zip.RepairMode) - return error; - - zip.OutputPointer += i; - out_bytes -= i; - } - - if (out_bytes != 0) - { - Console.WriteLine($"Bytes left to output: {out_bytes}"); - return zip.Error = Error.MSPACK_ERR_DECRUNCH; - } - - return Error.MSPACK_ERR_OK; - } - - /// - /// Decompresses an entire MS-ZIP stream in a KWAJ file. Acts very much - /// like mszipd_decompress(), but doesn't take an out_bytes parameter - /// - public static Error DecompressKWAJ(MSZIPDStream zip) - { - int i, block_len; - Error error; - - // Unpack blocks until block_len == 0 - for (; ; ) - { - BufferState state = zip.RESTORE_BITS(); - - // Align to bytestream, read block_len - i = state.BitsLeft & 7; - state.REMOVE_BITS_LSB(i); - - block_len = (int)zip.READ_BITS_LSB(8, state); - i = (int)zip.READ_BITS_LSB(8, state); - - block_len |= i << 8; - if (block_len == 0) - break; - - // Read "CK" header - i = (int)zip.READ_BITS_LSB(8, state); - if (i != 'C') - return Error.MSPACK_ERR_DATAFORMAT; - - i = (int)zip.READ_BITS_LSB(8, state); - if (i != 'K') - return Error.MSPACK_ERR_DATAFORMAT; - - // Inflate block - zip.WindowPosition = 0; - zip.BytesOutput = 0; - - zip.STORE_BITS(state); - - if ((error = Inflate(zip)) != Error.MSPACK_ERR_OK) - { - Console.WriteLine($"Inflate error {error}"); - return zip.Error = (error > 0) ? error : Error.MSPACK_ERR_DECRUNCH; - } - - // Write inflated block - try { zip.System.Write(zip.OutputFileHandle, zip.Window, 0, zip.BytesOutput); } - catch { return zip.Error = Error.MSPACK_ERR_WRITE; } - } - - return Error.MSPACK_ERR_OK; - } - - private static Error ReadLens(MSZIPDStream zip) - { - // bitlen Huffman codes -- immediate lookup, 7 bit max code length - ushort[] bl_table = new ushort[(1 << 7)]; - byte[] bl_len = new byte[19]; - - byte[] lens = new byte[MSZIP_LITERAL_MAXSYMBOLS + MSZIP_DISTANCE_MAXSYMBOLS]; - uint lit_codes, dist_codes, code, last_code = 0, bitlen_codes, i, run; - - BufferState state = zip.RESTORE_BITS(); - - // Read the number of codes - lit_codes = (uint)zip.READ_BITS_LSB(5, state); - lit_codes += 257; - - dist_codes = (uint)zip.READ_BITS_LSB(5, state); - dist_codes += 1; - - bitlen_codes = (uint)zip.READ_BITS_LSB(5, state); - bitlen_codes += 4; - - if (lit_codes > MSZIP_LITERAL_MAXSYMBOLS) - return Error.INF_ERR_SYMLENS; - if (dist_codes > MSZIP_DISTANCE_MAXSYMBOLS) - return Error.INF_ERR_SYMLENS; - - // Read in the bit lengths in their unusual order - for (i = 0; i < bitlen_codes; i++) - { - bl_len[BitLengthOrder[i]] = (byte)zip.READ_BITS_LSB(3, state); - } - - while (i < 19) - { - bl_len[BitLengthOrder[i++]] = 0; - } - - // Create decoding table with an immediate lookup - if (!CompressionStream.MakeDecodeTableLSB(19, 7, bl_len, bl_table)) - return Error.INF_ERR_BITLENTBL; - - // Read literal / distance code lengths - for (i = 0; i < (lit_codes + dist_codes); i++) - { - // Single-level huffman lookup - - zip.ENSURE_BITS(7, state); - code = bl_table[zip.PEEK_BITS_LSB(7, state.BitBuffer)]; - state.REMOVE_BITS_LSB(bl_len[code]); - - if (code < 16) - { - lens[i] = (byte)(last_code = code); - } - else - { - switch (code) - { - case 16: - run = (uint)zip.READ_BITS_LSB(2, state); - run += 3; - code = last_code; - break; - - case 17: - run = (uint)zip.READ_BITS_LSB(3, state); - run += 3; - code = 0; - break; - - case 18: - run = (uint)zip.READ_BITS_LSB(7, state); - run += 11; - code = 0; - break; - - default: - Console.WriteLine($"Bad code!: {code}"); - return Error.INF_ERR_BADBITLEN; - } - - if ((i + run) > (lit_codes + dist_codes)) - return Error.INF_ERR_BITOVERRUN; - - while (run-- != 0) - { - lens[i++] = (byte)code; - } - - i--; - } - } - - // Copy LITERAL code lengths and clear any remaining - i = lit_codes; - Array.Copy(lens, 0, zip.LITERAL_len, 0, i); - while (i < MSZIP_LITERAL_MAXSYMBOLS) - { - zip.LITERAL_len[i++] = 0; - } - - i = dist_codes; - Array.Copy(lens, lit_codes, zip.DISTANCE_len, 0, i); - while (i < MSZIP_DISTANCE_MAXSYMBOLS) - { - zip.DISTANCE_len[i++] = 0; - } - - zip.STORE_BITS(state); - - return 0; - } - - /// - /// A clean implementation of RFC 1951 / inflate - /// - private static Error Inflate(MSZIPDStream zip) - { - uint last_block, block_type, distance, length, this_run, i; - Error err; - ushort sym; - - BufferState state = zip.RESTORE_BITS(); - - do - { - // Read in last block bit - last_block = (uint)zip.READ_BITS_LSB(1, state); - - // Read in block type - block_type = (uint)zip.READ_BITS_LSB(2, state); - - if (block_type == 0) - { - // Uncompressed block - byte[] lens_buf = new byte[4]; - - // Go to byte boundary - i = (uint)(state.BitsLeft & 7); - state.REMOVE_BITS_LSB((int)i); - - // Read 4 bytes of data, emptying the bit-buffer if necessary - for (i = 0; (state.BitsLeft >= 8); i++) - { - if (i == 4) - return Error.INF_ERR_BITBUF; - - lens_buf[i] = (byte)zip.PEEK_BITS_LSB(8, state.BitBuffer); - state.REMOVE_BITS_LSB(8); - } - - if (state.BitsLeft != 0) - return Error.INF_ERR_BITBUF; - - while (i < 4) - { - zip.READ_IF_NEEDED(state); - if (zip.Error != Error.MSPACK_ERR_OK) - return zip.Error; - - lens_buf[i++] = zip.InputBuffer[state.InputPointer++]; - } - - // Get the length and its complement - length = (ushort)(lens_buf[0] | (lens_buf[1] << 8)); - i = (ushort)(lens_buf[2] | (lens_buf[3] << 8)); - - ushort compl = (ushort)(~i & 0xFFFF); - if (length != compl) - return Error.INF_ERR_COMPLEMENT; - - // Read and copy the uncompressed data into the window - while (length > 0) - { - zip.READ_IF_NEEDED(state); - if (zip.Error != Error.MSPACK_ERR_OK) - return zip.Error; - - this_run = length; - if (this_run > (uint)(state.InputEnd - state.InputPointer)) - this_run = (uint)(state.InputEnd - state.InputPointer); - - if (this_run > (MSZIP_FRAME_SIZE - zip.WindowPosition)) - this_run = MSZIP_FRAME_SIZE - zip.WindowPosition; - - Array.Copy(zip.InputBuffer, state.InputPointer, zip.Window, zip.WindowPosition, this_run); - - zip.WindowPosition += this_run; - state.InputPointer += (int)this_run; - length -= this_run; - - err = FLUSH_IF_NEEDED(zip); - if (err != Error.MSPACK_ERR_OK) - return err; - } - } - else if ((block_type == 1) || (block_type == 2)) - { - // Huffman-compressed LZ77 block - uint match_posn, code; - - if (block_type == 1) - { - // Block with fixed Huffman codes - i = 0; - while (i < 144) - { - zip.LITERAL_len[i++] = 8; - } - - while (i < 256) - { - zip.LITERAL_len[i++] = 9; - } - - while (i < 280) - { - zip.LITERAL_len[i++] = 7; - } - - while (i < 288) - { - zip.LITERAL_len[i++] = 8; - } - - for (i = 0; i < 32; i++) - { - zip.DISTANCE_len[i] = 5; - } - } - else - { - // Block with dynamic Huffman codes - zip.STORE_BITS(state); - - if ((err = ReadLens(zip)) != Error.MSPACK_ERR_OK) - return err; - - state = zip.RESTORE_BITS(); - } - - // Now huffman lengths are read for either kind of block, - // create huffman decoding tables - if (!CompressionStream.MakeDecodeTableLSB(MSZIP_LITERAL_MAXSYMBOLS, MSZIP_LITERAL_TABLEBITS, zip.LITERAL_len, zip.LITERAL_table)) - return Error.INF_ERR_LITERALTBL; - - if (!CompressionStream.MakeDecodeTableLSB(MSZIP_DISTANCE_MAXSYMBOLS, MSZIP_DISTANCE_TABLEBITS, zip.DISTANCE_len, zip.DISTANCE_table)) - return Error.INF_ERR_DISTANCETBL; - - // Decode forever until end of block code - for (; ; ) - { - code = (uint)zip.READ_HUFFSYM_LSB(zip.LITERAL_table, zip.LITERAL_len, MSZIP_LITERAL_TABLEBITS, MSZIP_LITERAL_MAXSYMBOLS, state); - - if (code < 256) - { - zip.Window[zip.WindowPosition++] = (byte)code; - err = FLUSH_IF_NEEDED(zip); - if (err != Error.MSPACK_ERR_OK) - return err; - } - else if (code == 256) - { - // END OF BLOCK CODE: loop break point - break; - } - else - { - code -= 257; // Codes 257-285 are matches - if (code >= 29) - return Error.INF_ERR_LITCODE; // Codes 286-287 are illegal - - length = (uint)zip.READ_BITS_T_LSB(LiteralExtraBits[code], state); - length += LiteralLengths[code]; - - code = (uint)zip.READ_HUFFSYM_LSB(zip.DISTANCE_table, zip.DISTANCE_len, MSZIP_DISTANCE_TABLEBITS, MSZIP_DISTANCE_MAXSYMBOLS, state); - - if (code >= 30) - return Error.INF_ERR_DISTCODE; - - distance = (uint)zip.READ_BITS_T_LSB(DistanceExtraBits[code], state); - distance += DistanceOffsets[code]; - - // Match position is window position minus distance. If distance - // is more than window position numerically, it must 'wrap - // around' the frame size. - match_posn = (uint)((distance > zip.WindowPosition) ? MSZIP_FRAME_SIZE : 0) + zip.WindowPosition - distance; - - // Copy match - if (length < 12) - { - // Short match, use slower loop but no loop setup code - while (length-- != 0) - { - zip.Window[zip.WindowPosition++] = zip.Window[match_posn++]; - match_posn &= MSZIP_FRAME_SIZE - 1; - err = FLUSH_IF_NEEDED(zip); - if (err != Error.MSPACK_ERR_OK) - return err; - } - } - else - { - // Longer match, use faster loop but with setup expense */ - int runsrc, rundest; - do - { - this_run = length; - if ((match_posn + this_run) > MSZIP_FRAME_SIZE) - this_run = MSZIP_FRAME_SIZE - match_posn; - if ((zip.WindowPosition + this_run) > MSZIP_FRAME_SIZE) - this_run = MSZIP_FRAME_SIZE - zip.WindowPosition; - - rundest = (int)zip.WindowPosition; - zip.WindowPosition += this_run; - runsrc = (int)match_posn; - match_posn += this_run; - length -= this_run; - while (this_run-- != 0) - { - zip.Window[rundest++] = zip.Window[runsrc++]; - } - - if (match_posn == MSZIP_FRAME_SIZE) - match_posn = 0; - - err = FLUSH_IF_NEEDED(zip); - if (err != Error.MSPACK_ERR_OK) - return err; - } while (length > 0); - } - - } - - } - } - else - { - // block_type == 3 -- bad block type - return Error.INF_ERR_BLOCKTYPE; - } - } while (last_block == 0); - - // Flush the remaining data - if (zip.WindowPosition != 0) - { - if (zip.FlushWindow(zip, zip.WindowPosition) != Error.MSPACK_ERR_OK) - return Error.INF_ERR_FLUSH; - } - - zip.STORE_BITS(state); - - // Return success - return Error.MSPACK_ERR_OK; - } - - private static Error FLUSH_IF_NEEDED(MSZIPDStream zip) - { - if (zip.WindowPosition == MSZIP_FRAME_SIZE) - { - if (zip.FlushWindow(zip, MSZIP_FRAME_SIZE) != Error.MSPACK_ERR_OK) - return Error.INF_ERR_FLUSH; - - zip.WindowPosition = 0; - } - - return Error.MSPACK_ERR_OK; - } - - /// - /// inflate() calls this whenever the window should be flushed. As - /// MSZIP only expands to the size of the window, the implementation used - /// simply keeps track of the amount of data flushed, and if more than 32k - /// is flushed, an error is raised. - /// - private static Error FlushWindow(MSZIPDStream zip, uint data_flushed) - { - zip.BytesOutput += (int)data_flushed; - if (zip.BytesOutput > MSZIP_FRAME_SIZE) - { - Console.WriteLine($"Overflow: {data_flushed} bytes flushed, total is now {zip.BytesOutput}"); - return Error.MSPACK_ERR_ARGS; - } - - return Error.MSPACK_ERR_OK; - } + #endregion } } diff --git a/BurnOutSharp/External/libmspack/Compression/MSZIPDStream.cs b/BurnOutSharp/External/libmspack/Compression/MSZIPDStream.cs deleted file mode 100644 index ec1bbf75..00000000 --- a/BurnOutSharp/External/libmspack/Compression/MSZIPDStream.cs +++ /dev/null @@ -1,70 +0,0 @@ -/* This file is part of libmspack. - * (C) 2003-2004 Stuart Caie. - * - * The deflate method was created by Phil Katz. MSZIP is equivalent to the - * deflate method. - * - * libmspack is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License (LGPL) version 2.1 - * - * For further details, see the file COPYING.LIB distributed with libmspack - */ - -using System; -using static LibMSPackSharp.Compression.Constants; - -namespace LibMSPackSharp.Compression -{ - public class MSZIPDStream : CompressionStream - { - #region Fields - - /// - /// 32kb history window - /// - public byte[] Window { get; set; } = new byte[MSZIP_FRAME_SIZE]; - - /// - /// Offset within window - /// - public uint WindowPosition { get; set; } - - /// - /// inflate() will call this whenever the window should be emptied. - /// - public Func FlushWindow; - - public bool RepairMode { get; set; } - - public int BytesOutput { get; set; } - - #region Huffman code lengths - - public byte[] LITERAL_len { get; set; } = new byte[MSZIP_LITERAL_MAXSYMBOLS]; - public byte[] DISTANCE_len { get; set; } = new byte[MSZIP_DISTANCE_MAXSYMBOLS]; - - #endregion - - #region Huffman decoding tables - - public ushort[] LITERAL_table { get; set; } = new ushort[MSZIP_LITERAL_TABLESIZE]; - public ushort[] DISTANCE_table { get; set; } = new ushort[MSZIP_DISTANCE_TABLESIZE]; - - #endregion - - #endregion - - /// - public override Error HUFF_ERROR() => Error.INF_ERR_HUFFSYM; - - /// - public override void READ_BYTES(BufferState state) - { - READ_IF_NEEDED(state); - if (Error != Error.MSPACK_ERR_OK) - return; - - INJECT_BITS_LSB(InputBuffer[state.InputPointer++], 8, state); - } - } -} diff --git a/BurnOutSharp/External/libmspack/Compression/QTM.Decompress.cs b/BurnOutSharp/External/libmspack/Compression/QTM.Decompress.cs new file mode 100644 index 00000000..c9187f6b --- /dev/null +++ b/BurnOutSharp/External/libmspack/Compression/QTM.Decompress.cs @@ -0,0 +1,546 @@ +/* This file is part of libmspack. + * (C) 2003-2004 Stuart Caie. + * + * The Quantum method was created by David Stafford, adapted by Microsoft + * Corporation. + * + * This decompressor is based on an implementation by Matthew Russotto, used + * with permission. + * + * libmspack is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License (LGPL) version 2.1 + * + * For further details, see the file COPYING.LIB distributed with libmspack + */ + +/* Quantum decompression implementation */ + +/* This decompressor was researched and implemented by Matthew Russotto. It + * has since been tidied up by Stuart Caie. More information can be found at + * http://www.speakeasy.org/~russotto/quantumcomp.html + */ + +using System; +using System.IO; +using static LibMSPackSharp.Compression.Constants; + +namespace LibMSPackSharp.Compression +{ + public partial class QTM + { + /// + /// allocates Quantum decompression state for decoding the given stream. + /// + /// - returns null if window_bits is outwith the range 10 to 21 (inclusive). + /// - uses system.alloc() to allocate memory + /// - returns null if not enough memory + /// - window_bits is the size of the Quantum window, from 1Kb(10) to 2Mb(21). + /// - input_buffer_size is the number of bytes to use to store bitstream data. + /// + public static QTM Init(SystemImpl system, FileStream input, FileStream output, int window_bits, int input_buffer_size) + { + uint window_size = (uint)(1 << window_bits); + + if (system == null) + return null; + + // Quantum supports window sizes of 2^10 (1Kb) through 2^21 (2Mb) + if (window_bits < 10 || window_bits > 21) + return null; + + // Round up input buffer size to multiple of two + input_buffer_size = (input_buffer_size + 1) & -2; + if (input_buffer_size < 2) + return null; + + // Allocate decompression state + QTM qtm = new QTM() + { + // Allocate decompression window and input buffer + Window = new byte[window_size], + InputBuffer = new byte[input_buffer_size], + + // Initialise decompression state + System = system, + InputFileHandle = input, + OutputFileHandle = output, + InputBufferSize = (uint)input_buffer_size, + WindowSize = window_size, + WindowPosition = 0, + FrameTODO = QTM_FRAME_SIZE, + HeaderRead = 0, + Error = Error.MSPACK_ERR_OK, + + InputPointer = 0, + InputEnd = 0, + OutputPointer = 0, + OutputEnd = 0, + BitBuffer = 0, + BitsLeft = 0, + EndOfInput = 0, + }; + + // Initialise arithmetic coding models + // - model 4 depends on window size, ranges from 20 to 24 + // - model 5 depends on window size, ranges from 20 to 36 + // - model 6pos depends on window size, ranges from 20 to 42 + + int i = window_bits * 2; + qtm.InitModel(qtm.Model0, qtm.Model0Symbols, 0, 64); + qtm.InitModel(qtm.Model1, qtm.Model1Symbols, 64, 64); + qtm.InitModel(qtm.Model2, qtm.Model2Symbols, 128, 64); + qtm.InitModel(qtm.Model3, qtm.Model3Symbols, 192, 64); + qtm.InitModel(qtm.Model4, qtm.Model4Symbols, 0, (i > 24) ? 24 : i); + qtm.InitModel(qtm.Model5, qtm.Model5Symbols, 0, (i > 36) ? 36 : i); + qtm.InitModel(qtm.Model6, qtm.Model6Symbols, 0, i); + qtm.InitModel(qtm.Model6Len, qtm.Model6LenSymbols, 0, 27); + qtm.InitModel(qtm.Model7, qtm.Model7Symbols, 0, 7); + + // All ok + return qtm; + } + + /// + /// Decompresses, or decompresses more of, a Quantum stream. + /// + /// - out_bytes of data will be decompressed and the function will return + /// with an MSPACK_ERR_OK return code. + /// + /// - decompressing will stop as soon as out_bytes is reached. if the true + /// amount of bytes decoded spills over that amount, they will be kept for + /// a later invocation of qtmd_decompress(). + /// + /// - the output bytes will be passed to the system.write() function given in + /// qtmd_init(), using the output file handle given in qtmd_init(). More + /// than one call may be made to system.write() + /// + /// - Quantum will read input bytes as necessary using the system.read() + /// function given in qtmd_init(), using the input file handle given in + /// qtmd_init(). This will continue until system.read() returns 0 bytes, + /// or an error. + /// + public static Error Decompress(object o, long out_bytes) + { + QTM qtm = o as QTM; + if (qtm == null) + return Error.MSPACK_ERR_ARGS; + + uint frame_todo, frame_end, window_posn, match_offset, range; + byte[] window; + int runsrc, rundest; + int i, j, selector, extra, sym, match_length; + ushort H, L, C, symf; + + // Easy answers + if (qtm == null || (out_bytes < 0)) + return Error.MSPACK_ERR_ARGS; + + if (qtm.Error != Error.MSPACK_ERR_OK) + return qtm.Error; + + // Flush out any stored-up bytes before we begin + i = qtm.OutputEnd - qtm.OutputPointer; + if (i > out_bytes) + i = (int)out_bytes; + + if (i != 0) + { + if (qtm.System.Write(qtm.OutputFileHandle, qtm.Window, qtm.OutputPointer, i) != i) + return qtm.Error = Error.MSPACK_ERR_WRITE; + + qtm.OutputPointer += i; + out_bytes -= i; + } + + if (out_bytes == 0) + return Error.MSPACK_ERR_OK; + + // Restore local state + window = qtm.Window; + window_posn = qtm.WindowPosition; + frame_todo = qtm.FrameTODO; + H = qtm.High; + L = qtm.Low; + C = qtm.Current; + + // While we do not have enough decoded bytes in reserve + while ((qtm.OutputEnd - qtm.OutputPointer) < out_bytes) + { + // Read header if necessary. Initialises H, L and C + if (qtm.HeaderRead != 0) + { + H = 0xFFFF; + L = 0; + C = (ushort)qtm.READ_BITS_MSB(16); + qtm.HeaderRead = 1; + } + + // Decode more, up to the number of bytes needed, the frame boundary, + // or the window boundary, whichever comes first + frame_end = (uint)(window_posn + (out_bytes - (qtm.OutputEnd - qtm.OutputPointer))); + if ((window_posn + frame_todo) < frame_end) + frame_end = window_posn + frame_todo; + if (frame_end > qtm.WindowSize) + frame_end = qtm.WindowSize; + + while (window_posn < frame_end) + { + selector = qtm.GET_SYMBOL(qtm.Model7, ref H, ref L, ref C); + if (selector < 4) + { + // Literal byte + QTMDModel mdl; + switch (selector) + { + case 0: + mdl = qtm.Model0; + break; + + case 1: + mdl = qtm.Model1; + break; + + case 2: + mdl = qtm.Model2; + break; + + case 3: + default: + mdl = qtm.Model3; + break; + } + + sym = qtm.GET_SYMBOL(mdl, ref H, ref L, ref C); + window[window_posn++] = (byte)sym; + frame_todo--; + } + else + { + // Match repeated string + switch (selector) + { + // Selector 4 = fixed length match (3 bytes) + case 4: + sym = qtm.GET_SYMBOL(qtm.Model4, ref H, ref L, ref C); + extra = (int)qtm.READ_MANY_BITS_MSB(QTMExtraBits[sym]); + match_offset = (uint)(QTMPositionBase[sym] + extra + 1); + match_length = 3; + break; + + // Selector 5 = fixed length match (4 bytes) + case 5: + sym = qtm.GET_SYMBOL(qtm.Model5, ref H, ref L, ref C); + extra = (int)qtm.READ_MANY_BITS_MSB(QTMExtraBits[sym]); + match_offset = (uint)(QTMPositionBase[sym] + extra + 1); + match_length = 4; + break; + + // Selector 6 = variable length match + case 6: + sym = qtm.GET_SYMBOL(qtm.Model6Len, ref H, ref L, ref C); + extra = (int)qtm.READ_MANY_BITS_MSB(QTMLengthExtra[sym]); + match_length = QTMLengthBase[sym] + extra + 5; + + sym = qtm.GET_SYMBOL(qtm.Model6, ref H, ref L, ref C); + extra = (int)qtm.READ_MANY_BITS_MSB(QTMExtraBits[sym]); + match_offset = (uint)(QTMPositionBase[sym] + extra + 1); + break; + + default: + // Should be impossible, model7 can only return 0-6 + Console.WriteLine($"Got {selector} from selector"); + return qtm.Error = Error.MSPACK_ERR_DECRUNCH; + } + + rundest = (int)window_posn; + frame_todo -= (uint)match_length; + + // Does match destination wrap the window? This situation is possible + // where the window size is less than the 32k frame size, but matches + // must not go beyond a frame boundary + if ((window_posn + match_length) > qtm.WindowSize) + { + // Copy first part of match, before window end + i = (int)(qtm.WindowSize - window_posn); + j = (int)(window_posn - match_offset); + + while (i-- != 0) + { + window[rundest++] = window[j++ & (qtm.WindowSize - 1)]; + } + + // Flush currently stored data + i = (int)(qtm.WindowSize - qtm.OutputPointer); + + // This should not happen, but if it does then this code + // can't handle the situation (can't flush up to the end of + // the window, but can't break out either because we haven't + // finished writing the match). Bail out in this case + if (i > out_bytes) + { + Console.WriteLine($"During window-wrap match; {i} bytes to flush but only need {out_bytes}"); + return qtm.Error = Error.MSPACK_ERR_DECRUNCH; + } + + if (qtm.System.Write(qtm.OutputFileHandle, window, qtm.OutputPointer, i) != i) + return qtm.Error = Error.MSPACK_ERR_WRITE; + + out_bytes -= i; + qtm.OutputPointer = 0; + qtm.OutputEnd = 0; + + // Copy second part of match, after window wrap + rundest = 0; + i = (int)(match_length - (qtm.WindowSize - window_posn)); + while (i-- != 0) + { + window[rundest++] = window[j++ & (qtm.WindowSize - 1)]; + } + + window_posn = (uint)(window_posn + match_length - qtm.WindowSize); + + break; // Because "window_posn < frame_end" has now failed + } + else + { + // Normal match - output won't wrap window or frame end + i = match_length; + + // Does match _offset_ wrap the window? + if (match_offset > window_posn) + { + // j = length from match offset to end of window + j = (int)(match_offset - window_posn); + if (j > (int)qtm.WindowSize) + { + Console.WriteLine("Match offset beyond window boundaries"); + return qtm.Error = Error.MSPACK_ERR_DECRUNCH; + } + + runsrc = (int)(qtm.WindowSize - j); + if (j < i) + { + // If match goes over the window edge, do two copy runs + i -= j; + while (j-- > 0) + { + window[rundest++] = window[runsrc++]; + } + + runsrc = 0; + } + + while (i-- > 0) + { + window[rundest++] = window[runsrc++]; + } + } + else + { + runsrc = (int)(rundest - match_offset); + while (i-- > 0) + { + window[rundest++] = window[runsrc++]; + } + } + + window_posn += (uint)match_length; + } + } + } + + qtm.OutputEnd = (int)window_posn; + + // If we subtracted too much from frame_todo, it will + // wrap around past zero and go above its max value + if (frame_todo > QTM_FRAME_SIZE) + { + Console.WriteLine("Overshot frame alignment"); + return qtm.Error = Error.MSPACK_ERR_DECRUNCH; + } + + // Another frame completed? + if (frame_todo == 0) + { + // Re-align input + if ((qtm.BitsLeft & 7) != 0) + qtm.REMOVE_BITS_MSB(qtm.BitsLeft & 7); + + // Special Quantum hack -- cabd.c injects a trailer byte to allow the + // decompressor to realign itself. CAB Quantum blocks, unlike LZX + // blocks, can have anything from 0 to 4 trailing null bytes. + do + { + i = (int)qtm.READ_BITS_MSB(8); + } while (i != 0xFF); + + qtm.HeaderRead = 0; + + frame_todo = QTM_FRAME_SIZE; + } + + // Window wrap? + if (window_posn == qtm.WindowSize) + { + // Flush all currently stored data + i = (qtm.OutputEnd - qtm.OutputPointer); + + // Break out if we have more than enough to finish this request + if (i >= out_bytes) + break; + + if (qtm.System.Write(qtm.OutputFileHandle, window, qtm.OutputPointer, i) != i) + return qtm.Error = Error.MSPACK_ERR_WRITE; + + out_bytes -= i; + qtm.OutputPointer = 0; + qtm.OutputEnd = 0; + window_posn = 0; + } + + } + + if (out_bytes != 0) + { + i = (int)out_bytes; + + if (qtm.System.Write(qtm.OutputFileHandle, window, qtm.OutputPointer, i) != i) + return qtm.Error = Error.MSPACK_ERR_WRITE; + + qtm.OutputPointer += i; + } + + // Store local state + qtm.WindowPosition = window_posn; + qtm.FrameTODO = frame_todo; + qtm.High = H; + qtm.Low = L; + qtm.Current = C; + + return Error.MSPACK_ERR_OK; + } + + private ushort GET_SYMBOL(QTMDModel model, ref ushort H, ref ushort L, ref ushort C) + { + uint range = (uint)((H - L) & 0xFFFF) + 1; + ushort symf = (ushort)(((((C - L + 1) * model.Syms[0].CumulativeFrequency) - 1) / range) & 0xFFFF); + + int i = 1; + for (; i < model.Entries; i++) + { + if (model.Syms[i].CumulativeFrequency <= symf) + break; + } + + ushort temp = model.Syms[i - 1].Sym; + + range = (uint)(H - L) + 1; + symf = model.Syms[0].CumulativeFrequency; + H = (ushort)(L + ((model.Syms[i - 1].CumulativeFrequency * range) / symf) - 1); + L = (ushort)(L + ((model.Syms[i].CumulativeFrequency * range) / symf)); + + do + { + model.Syms[--i].CumulativeFrequency += 8; + } while (i > 0); + + if (model.Syms[0].CumulativeFrequency > 3800) + UpdateModel(model); + + while (true) + { + if ((L & 0x8000) != (H & 0x8000)) + { + if ((L & 0x4000) != 0 && (H & 0x4000) == 0) + { + // Underflow case + C ^= 0x4000; + L &= 0x3FFF; + H |= 0x4000; + } + else + { + break; + } + } + + L <<= 1; + H = (ushort)((H << 1) | 1); + + ENSURE_BITS(1); + C = (ushort)((C << 1) | (PEEK_BITS_MSB(1))); + REMOVE_BITS_MSB(1); + } + + return temp; + } + + private void UpdateModel(QTMDModel model) + { + QTMDModelSym tmp; + int i, j; + + if (--model.ShiftsLeft != 0) + { + for (i = model.Entries - 1; i >= 0; i--) + { + // -1, not -2; the 0 entry saves this + model.Syms[i].CumulativeFrequency >>= 1; + if (model.Syms[i].CumulativeFrequency <= model.Syms[i + 1].CumulativeFrequency) + model.Syms[i].CumulativeFrequency = (ushort)(model.Syms[i + 1].CumulativeFrequency + 1); + } + } + else + { + model.ShiftsLeft = 50; + for (i = 0; i < model.Entries; i++) + { + // No -1, want to include the 0 entry + + // This converts CumFreqs into frequencies, then shifts right + model.Syms[i].CumulativeFrequency -= model.Syms[i + 1].CumulativeFrequency; + model.Syms[i].CumulativeFrequency++; // Avoid losing things entirely + model.Syms[i].CumulativeFrequency >>= 1; + } + + // Now sort by frequencies, decreasing order -- this must be an + // inplace selection sort, or a sort with the same (in)stability + // characteristics + for (i = 0; i < model.Entries - 1; i++) + { + for (j = i + 1; j < model.Entries; j++) + { + if (model.Syms[i].CumulativeFrequency < model.Syms[j].CumulativeFrequency) + { + tmp = model.Syms[i]; + model.Syms[i] = model.Syms[j]; + model.Syms[j] = tmp; + } + } + } + + // Then convert frequencies back to CumFreq + for (i = model.Entries - 1; i >= 0; i--) + { + model.Syms[i].CumulativeFrequency += model.Syms[i + 1].CumulativeFrequency; + } + } + } + + private void InitModel(QTMDModel model, QTMDModelSym[] syms, int start, int len) + { + model.ShiftsLeft = 4; + model.Entries = len; + model.Syms = syms; + + for (int i = 0; i <= len; i++) + { + // Actual symbol + syms[i].Sym = (ushort)(start + i); + + // Current frequency of that symbol + syms[i].CumulativeFrequency = (ushort)(len - i); + } + } + } +} diff --git a/BurnOutSharp/External/libmspack/Compression/QTM.ReadBits.cs b/BurnOutSharp/External/libmspack/Compression/QTM.ReadBits.cs new file mode 100644 index 00000000..83ce7f6c --- /dev/null +++ b/BurnOutSharp/External/libmspack/Compression/QTM.ReadBits.cs @@ -0,0 +1,34 @@ +/* This file is part of libmspack. + * (C) 2003-2004 Stuart Caie. + * + * The Quantum method was created by David Stafford, adapted by Microsoft + * Corporation. + * + * libmspack is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License (LGPL) version 2.1 + * + * For further details, see the file COPYING.LIB distributed with libmspack + */ + +namespace LibMSPackSharp.Compression +{ + public partial class QTM : CompressionStream + { + /// + public override void READ_BYTES() + { + READ_IF_NEEDED(); + if (Error != Error.MSPACK_ERR_OK) + return; + + byte b0 = InputBuffer[InputPointer++]; + + READ_IF_NEEDED(); + if (Error != Error.MSPACK_ERR_OK) + return; + + byte b1 = InputBuffer[InputPointer++]; + INJECT_BITS_MSB((b0 << 8) | b1, 16); + } + } +} diff --git a/BurnOutSharp/External/libmspack/Compression/QTM.ReadHuff.cs b/BurnOutSharp/External/libmspack/Compression/QTM.ReadHuff.cs new file mode 100644 index 00000000..96b62bb9 --- /dev/null +++ b/BurnOutSharp/External/libmspack/Compression/QTM.ReadHuff.cs @@ -0,0 +1,22 @@ +/* This file is part of libmspack. + * (C) 2003-2004 Stuart Caie. + * + * The Quantum method was created by David Stafford, adapted by Microsoft + * Corporation. + * + * libmspack is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License (LGPL) version 2.1 + * + * For further details, see the file COPYING.LIB distributed with libmspack + */ + +using System; + +namespace LibMSPackSharp.Compression +{ + public partial class QTM : CompressionStream + { + /// + public override Error HUFF_ERROR() => throw new NotImplementedException(); + } +} diff --git a/BurnOutSharp/External/libmspack/Compression/QTM.cs b/BurnOutSharp/External/libmspack/Compression/QTM.cs index 5b6cdfc9..33bc9f3e 100644 --- a/BurnOutSharp/External/libmspack/Compression/QTM.cs +++ b/BurnOutSharp/External/libmspack/Compression/QTM.cs @@ -4,552 +4,120 @@ * The Quantum method was created by David Stafford, adapted by Microsoft * Corporation. * - * This decompressor is based on an implementation by Matthew Russotto, used - * with permission. - * * libmspack is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License (LGPL) version 2.1 * * For further details, see the file COPYING.LIB distributed with libmspack */ -/* Quantum decompression implementation */ - -/* This decompressor was researched and implemented by Matthew Russotto. It - * has since been tidied up by Stuart Caie. More information can be found at - * http://www.speakeasy.org/~russotto/quantumcomp.html - */ - -using System; -using System.IO; -using static LibMSPackSharp.Compression.Constants; - namespace LibMSPackSharp.Compression { - public class QTM + public partial class QTM : CompressionStream { /// - /// allocates Quantum decompression state for decoding the given stream. - /// - /// - returns null if window_bits is outwith the range 10 to 21 (inclusive). - /// - uses system.alloc() to allocate memory - /// - returns null if not enough memory - /// - window_bits is the size of the Quantum window, from 1Kb(10) to 2Mb(21). - /// - input_buffer_size is the number of bytes to use to store bitstream data. + /// Decoding window /// - public static QTMDStream Init(SystemImpl system, FileStream input, FileStream output, int window_bits, int input_buffer_size) - { - uint window_size = (uint)(1 << window_bits); - - if (system == null) - return null; - - // Quantum supports window sizes of 2^10 (1Kb) through 2^21 (2Mb) - if (window_bits < 10 || window_bits > 21) - return null; - - // Round up input buffer size to multiple of two - input_buffer_size = (input_buffer_size + 1) & -2; - if (input_buffer_size < 2) - return null; - - // Allocate decompression state - QTMDStream qtm = new QTMDStream() - { - // Allocate decompression window and input buffer - Window = new byte[window_size], - InputBuffer = new byte[input_buffer_size], - - // Initialise decompression state - System = system, - InputFileHandle = input, - OutputFileHandle = output, - InputBufferSize = (uint)input_buffer_size, - WindowSize = window_size, - WindowPosition = 0, - FrameTODO = QTM_FRAME_SIZE, - HeaderRead = 0, - Error = Error.MSPACK_ERR_OK, - - OutputPointer = 0, - OutputEnd = 0, - EndOfInput = 0, - - BufferState = new BufferState() - { - InputPointer = 0, - InputEnd = 0, - BitBuffer = 0, - BitsLeft = 0, - } - }; - - // Initialise arithmetic coding models - // - model 4 depends on window size, ranges from 20 to 24 - // - model 5 depends on window size, ranges from 20 to 36 - // - model 6pos depends on window size, ranges from 20 to 42 - - int i = window_bits * 2; - InitModel(qtm.Model0, qtm.Model0Symbols, 0, 64); - InitModel(qtm.Model1, qtm.Model1Symbols, 64, 64); - InitModel(qtm.Model2, qtm.Model2Symbols, 128, 64); - InitModel(qtm.Model3, qtm.Model3Symbols, 192, 64); - InitModel(qtm.Model4, qtm.Model4Symbols, 0, (i > 24) ? 24 : i); - InitModel(qtm.Model5, qtm.Model5Symbols, 0, (i > 36) ? 36 : i); - InitModel(qtm.Model6, qtm.Model6Symbols, 0, i); - InitModel(qtm.Model6Len, qtm.Model6LenSymbols, 0, 27); - InitModel(qtm.Model7, qtm.Model7Symbols, 0, 7); - - // All ok - return qtm; - } + public byte[] Window { get; set; } /// - /// Decompresses, or decompresses more of, a Quantum stream. - /// - /// - out_bytes of data will be decompressed and the function will return - /// with an MSPACK_ERR_OK return code. - /// - /// - decompressing will stop as soon as out_bytes is reached. if the true - /// amount of bytes decoded spills over that amount, they will be kept for - /// a later invocation of qtmd_decompress(). - /// - /// - the output bytes will be passed to the system.write() function given in - /// qtmd_init(), using the output file handle given in qtmd_init(). More - /// than one call may be made to system.write() - /// - /// - Quantum will read input bytes as necessary using the system.read() - /// function given in qtmd_init(), using the input file handle given in - /// qtmd_init(). This will continue until system.read() returns 0 bytes, - /// or an error. + /// Window size /// - public static Error Decompress(object o, long out_bytes) - { - QTMDStream qtm = o as QTMDStream; - if (qtm == null) - return Error.MSPACK_ERR_ARGS; + public uint WindowSize { get; set; } - uint frame_todo, frame_end, window_posn, match_offset, range; - byte[] window; - int i_ptr, i_end, runsrc, rundest; - int i, j, selector, extra, sym, match_length; - ushort H, L, C, symf; + /// + /// Decompression offset within window + /// + public uint WindowPosition { get; set; } - uint bit_buffer; - int bits_left; + /// + /// Bytes remaining for current frame + /// + public uint FrameTODO { get; set; } - // Easy answers - if (qtm == null || (out_bytes < 0)) - return Error.MSPACK_ERR_ARGS; + /// + /// High: arith coding state + /// + public ushort High { get; set; } - if (qtm.Error != Error.MSPACK_ERR_OK) - return qtm.Error; + /// + /// Low: arith coding state + /// + public ushort Low { get; set; } - // Flush out any stored-up bytes before we begin - i = qtm.OutputEnd - qtm.OutputPointer; - if (i > out_bytes) - i = (int)out_bytes; + /// + /// Current: arith coding state + /// + public ushort Current { get; set; } - if (i != 0) - { - if (qtm.System.Write(qtm.OutputFileHandle, qtm.Window, qtm.OutputPointer, i) != i) - return qtm.Error = Error.MSPACK_ERR_WRITE; + /// + /// Have we started decoding a new frame? + /// + public byte HeaderRead { get; set; } - qtm.OutputPointer += i; - out_bytes -= i; - } + // Four literal models, each representing 64 symbols - if (out_bytes == 0) - return Error.MSPACK_ERR_OK; + /// + /// For literals from 0 to 63 (selector = 0) + /// + public QTMDModel Model0 { get; set; } - // Restore local state - BufferState state = qtm.RESTORE_BITS(); - window = qtm.Window; - window_posn = qtm.WindowPosition; - frame_todo = qtm.FrameTODO; - H = qtm.High; - L = qtm.Low; - C = qtm.Current; + /// + /// For literals from 64 to 127 (selector = 1) + /// + public QTMDModel Model1 { get; set; } - // While we do not have enough decoded bytes in reserve - while ((qtm.OutputEnd - qtm.OutputPointer) < out_bytes) - { - // Read header if necessary. Initialises H, L and C - if (qtm.HeaderRead != 0) - { - H = 0xFFFF; - L = 0; - C = (ushort)qtm.READ_BITS_MSB(16, state); - qtm.HeaderRead = 1; - } + /// + /// For literals from 128 to 191 (selector = 2) + /// + public QTMDModel Model2 { get; set; } - // Decode more, up to the number of bytes needed, the frame boundary, - // or the window boundary, whichever comes first - frame_end = (uint)(window_posn + (out_bytes - (qtm.OutputEnd - qtm.OutputPointer))); - if ((window_posn + frame_todo) < frame_end) - frame_end = window_posn + frame_todo; - if (frame_end > qtm.WindowSize) - frame_end = qtm.WindowSize; + /// + /// For literals from 129 to 255 (selector = 3) + /// + public QTMDModel Model3 { get; set; } - while (window_posn < frame_end) - { - selector = GET_SYMBOL(qtm, qtm.Model7, ref H, ref L, ref C, state); - if (selector < 4) - { - // Literal byte - QTMDModel mdl; - switch (selector) - { - case 0: - mdl = qtm.Model0; - break; + // Three match models. - case 1: - mdl = qtm.Model1; - break; + /// + /// For match with fixed length of 3 bytes + /// + public QTMDModel Model4 { get; set; } - case 2: - mdl = qtm.Model2; - break; + /// + /// For match with fixed length of 4 bytes + /// + public QTMDModel Model5 { get; set; } - case 3: - default: - mdl = qtm.Model3; - break; - } + /// + /// For variable length match, encoded with model6len model + /// + public QTMDModel Model6 { get; set; } - sym = GET_SYMBOL(qtm, mdl, ref H, ref L, ref C, state); - window[window_posn++] = (byte)sym; - frame_todo--; - } - else - { - // Match repeated string - switch (selector) - { - // Selector 4 = fixed length match (3 bytes) - case 4: - sym = GET_SYMBOL(qtm, qtm.Model4, ref H, ref L, ref C, state); - extra = (int)qtm.READ_MANY_BITS_MSB(QTMExtraBits[sym], state); - match_offset = (uint)(QTMPositionBase[sym] + extra + 1); - match_length = 3; - break; + public QTMDModel Model6Len { get; set; } - // Selector 5 = fixed length match (4 bytes) - case 5: - sym = GET_SYMBOL(qtm, qtm.Model5, ref H, ref L, ref C, state); - extra = (int)qtm.READ_MANY_BITS_MSB(QTMExtraBits[sym], state); - match_offset = (uint)(QTMPositionBase[sym] + extra + 1); - match_length = 4; - break; + /// + /// Selector model. 0-6 to say literal (0,1,2,3) or match (4,5,6) + /// + public QTMDModel Model7 { get; set; } - // Selector 6 = variable length match - case 6: - sym = GET_SYMBOL(qtm, qtm.Model6Len, ref H, ref L, ref C, state); - extra = (int)qtm.READ_MANY_BITS_MSB(QTMLengthExtra[sym], state); - match_length = QTMLengthBase[sym] + extra + 5; + // Symbol arrays for all models - sym = GET_SYMBOL(qtm, qtm.Model6, ref H, ref L, ref C, state); - extra = (int)qtm.READ_MANY_BITS_MSB(QTMExtraBits[sym], state); - match_offset = (uint)(QTMPositionBase[sym] + extra + 1); - break; + public QTMDModelSym[] Model0Symbols { get; set; } = new QTMDModelSym[64 + 1]; - default: - // Should be impossible, model7 can only return 0-6 - Console.WriteLine($"Got {selector} from selector"); - return qtm.Error = Error.MSPACK_ERR_DECRUNCH; - } + public QTMDModelSym[] Model1Symbols { get; set; } = new QTMDModelSym[64 + 1]; - rundest = (int)window_posn; - frame_todo -= (uint)match_length; + public QTMDModelSym[] Model2Symbols { get; set; } = new QTMDModelSym[64 + 1]; - // Does match destination wrap the window? This situation is possible - // where the window size is less than the 32k frame size, but matches - // must not go beyond a frame boundary - if ((window_posn + match_length) > qtm.WindowSize) - { - // Copy first part of match, before window end - i = (int)(qtm.WindowSize - window_posn); - j = (int)(window_posn - match_offset); + public QTMDModelSym[] Model3Symbols { get; set; } = new QTMDModelSym[64 + 1]; - while (i-- != 0) - { - window[rundest++] = window[j++ & (qtm.WindowSize - 1)]; - } + public QTMDModelSym[] Model4Symbols { get; set; } = new QTMDModelSym[24 + 1]; - // Flush currently stored data - i = (int)(qtm.WindowSize - qtm.OutputPointer); + public QTMDModelSym[] Model5Symbols { get; set; } = new QTMDModelSym[36 + 1]; - // This should not happen, but if it does then this code - // can't handle the situation (can't flush up to the end of - // the window, but can't break out either because we haven't - // finished writing the match). Bail out in this case - if (i > out_bytes) - { - Console.WriteLine($"During window-wrap match; {i} bytes to flush but only need {out_bytes}"); - return qtm.Error = Error.MSPACK_ERR_DECRUNCH; - } + public QTMDModelSym[] Model6Symbols { get; set; } = new QTMDModelSym[42 + 1]; - if (qtm.System.Write(qtm.OutputFileHandle, window, qtm.OutputPointer, i) != i) - return qtm.Error = Error.MSPACK_ERR_WRITE; + public QTMDModelSym[] Model6LenSymbols { get; set; } = new QTMDModelSym[27 + 1]; - out_bytes -= i; - qtm.OutputPointer = 0; - qtm.OutputEnd = 0; - - // Copy second part of match, after window wrap - rundest = 0; - i = (int)(match_length - (qtm.WindowSize - window_posn)); - while (i-- != 0) - { - window[rundest++] = window[j++ & (qtm.WindowSize - 1)]; - } - - window_posn = (uint)(window_posn + match_length - qtm.WindowSize); - - break; // Because "window_posn < frame_end" has now failed - } - else - { - // Normal match - output won't wrap window or frame end - i = match_length; - - // Does match _offset_ wrap the window? - if (match_offset > window_posn) - { - // j = length from match offset to end of window - j = (int)(match_offset - window_posn); - if (j > (int)qtm.WindowSize) - { - Console.WriteLine("Match offset beyond window boundaries"); - return qtm.Error = Error.MSPACK_ERR_DECRUNCH; - } - - runsrc = (int)(qtm.WindowSize - j); - if (j < i) - { - // If match goes over the window edge, do two copy runs - i -= j; - while (j-- > 0) - { - window[rundest++] = window[runsrc++]; - } - - runsrc = 0; - } - - while (i-- > 0) - { - window[rundest++] = window[runsrc++]; - } - } - else - { - runsrc = (int)(rundest - match_offset); - while (i-- > 0) - { - window[rundest++] = window[runsrc++]; - } - } - - window_posn += (uint)match_length; - } - } - } - - qtm.OutputEnd = (int)window_posn; - - // If we subtracted too much from frame_todo, it will - // wrap around past zero and go above its max value - if (frame_todo > QTM_FRAME_SIZE) - { - Console.WriteLine("Overshot frame alignment"); - return qtm.Error = Error.MSPACK_ERR_DECRUNCH; - } - - // Another frame completed? - if (frame_todo == 0) - { - // Re-align input - if ((state.BitsLeft & 7) != 0) - state.REMOVE_BITS_MSB(state.BitsLeft & 7); - - // Special Quantum hack -- cabd.c injects a trailer byte to allow the - // decompressor to realign itself. CAB Quantum blocks, unlike LZX - // blocks, can have anything from 0 to 4 trailing null bytes. - do - { - i = (int)qtm.READ_BITS_MSB(8, state); - } while (i != 0xFF); - - qtm.HeaderRead = 0; - - frame_todo = QTM_FRAME_SIZE; - } - - // Window wrap? - if (window_posn == qtm.WindowSize) - { - // Flush all currently stored data - i = (qtm.OutputEnd - qtm.OutputPointer); - - // Break out if we have more than enough to finish this request - if (i >= out_bytes) - break; - - if (qtm.System.Write(qtm.OutputFileHandle, window, qtm.OutputPointer, i) != i) - return qtm.Error = Error.MSPACK_ERR_WRITE; - - out_bytes -= i; - qtm.OutputPointer = 0; - qtm.OutputEnd = 0; - window_posn = 0; - } - - } - - if (out_bytes != 0) - { - i = (int)out_bytes; - - if (qtm.System.Write(qtm.OutputFileHandle, window, qtm.OutputPointer, i) != i) - return qtm.Error = Error.MSPACK_ERR_WRITE; - - qtm.OutputPointer += i; - } - - // Store local state - qtm.STORE_BITS(state); - qtm.WindowPosition = window_posn; - qtm.FrameTODO = frame_todo; - qtm.High = H; - qtm.Low = L; - qtm.Current = C; - - return Error.MSPACK_ERR_OK; - } - - private static ushort GET_SYMBOL(QTMDStream qtm, QTMDModel model, ref ushort H, ref ushort L, ref ushort C, BufferState state) - { - uint range = (uint)((H - L) & 0xFFFF) + 1; - ushort symf = (ushort)(((((C - L + 1) * model.Syms[0].CumulativeFrequency) - 1) / range) & 0xFFFF); - - int i = 1; - for (; i < model.Entries; i++) - { - if (model.Syms[i].CumulativeFrequency <= symf) - break; - } - - ushort temp = model.Syms[i - 1].Sym; - - range = (uint)(H - L) + 1; - symf = model.Syms[0].CumulativeFrequency; - H = (ushort)(L + ((model.Syms[i - 1].CumulativeFrequency * range) / symf) - 1); - L = (ushort)(L + ((model.Syms[i].CumulativeFrequency * range) / symf)); - - do - { - model.Syms[--i].CumulativeFrequency += 8; - } while (i > 0); - - if (model.Syms[0].CumulativeFrequency > 3800) - UpdateModel(model); - - while (true) - { - if ((L & 0x8000) != (H & 0x8000)) - { - if ((L & 0x4000) != 0 && (H & 0x4000) == 0) - { - // Underflow case - C ^= 0x4000; - L &= 0x3FFF; - H |= 0x4000; - } - else - { - break; - } - } - - L <<= 1; - H = (ushort)((H << 1) | 1); - - qtm.ENSURE_BITS(1, state); - C = (ushort)((C << 1) | (qtm.PEEK_BITS_MSB(1, state.BitBuffer))); - state.REMOVE_BITS_MSB(1); - } - - return temp; - } - - private static void UpdateModel(QTMDModel model) - { - QTMDModelSym tmp; - int i, j; - - if (--model.ShiftsLeft != 0) - { - for (i = model.Entries - 1; i >= 0; i--) - { - // -1, not -2; the 0 entry saves this - model.Syms[i].CumulativeFrequency >>= 1; - if (model.Syms[i].CumulativeFrequency <= model.Syms[i + 1].CumulativeFrequency) - model.Syms[i].CumulativeFrequency = (ushort)(model.Syms[i + 1].CumulativeFrequency + 1); - } - } - else - { - model.ShiftsLeft = 50; - for (i = 0; i < model.Entries; i++) - { - // No -1, want to include the 0 entry - - // This converts CumFreqs into frequencies, then shifts right - model.Syms[i].CumulativeFrequency -= model.Syms[i + 1].CumulativeFrequency; - model.Syms[i].CumulativeFrequency++; // Avoid losing things entirely - model.Syms[i].CumulativeFrequency >>= 1; - } - - // Now sort by frequencies, decreasing order -- this must be an - // inplace selection sort, or a sort with the same (in)stability - // characteristics - for (i = 0; i < model.Entries - 1; i++) - { - for (j = i + 1; j < model.Entries; j++) - { - if (model.Syms[i].CumulativeFrequency < model.Syms[j].CumulativeFrequency) - { - tmp = model.Syms[i]; - model.Syms[i] = model.Syms[j]; - model.Syms[j] = tmp; - } - } - } - - // Then convert frequencies back to CumFreq - for (i = model.Entries - 1; i >= 0; i--) - { - model.Syms[i].CumulativeFrequency += model.Syms[i + 1].CumulativeFrequency; - } - } - } - - private static void InitModel(QTMDModel model, QTMDModelSym[] syms, int start, int len) - { - model.ShiftsLeft = 4; - model.Entries = len; - model.Syms = syms; - - for (int i = 0; i <= len; i++) - { - // Actual symbol - syms[i].Sym = (ushort)(start + i); - - // Current frequency of that symbol - syms[i].CumulativeFrequency = (ushort)(len - i); - } - } + public QTMDModelSym[] Model7Symbols { get; set; } = new QTMDModelSym[7 + 1]; } } diff --git a/BurnOutSharp/External/libmspack/Compression/QTMDStream.cs b/BurnOutSharp/External/libmspack/Compression/QTMDStream.cs deleted file mode 100644 index 2d7ebb1e..00000000 --- a/BurnOutSharp/External/libmspack/Compression/QTMDStream.cs +++ /dev/null @@ -1,149 +0,0 @@ -/* This file is part of libmspack. - * (C) 2003-2004 Stuart Caie. - * - * The Quantum method was created by David Stafford, adapted by Microsoft - * Corporation. - * - * libmspack is free software; you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License (LGPL) version 2.1 - * - * For further details, see the file COPYING.LIB distributed with libmspack - */ - -using System; - -namespace LibMSPackSharp.Compression -{ - public class QTMDStream : CompressionStream - { - #region Fields - - /// - /// Decoding window - /// - public byte[] Window { get; set; } - - /// - /// Window size - /// - public uint WindowSize { get; set; } - - /// - /// Decompression offset within window - /// - public uint WindowPosition { get; set; } - - /// - /// Bytes remaining for current frame - /// - public uint FrameTODO { get; set; } - - /// - /// High: arith coding state - /// - public ushort High { get; set; } - - /// - /// Low: arith coding state - /// - public ushort Low { get; set; } - - /// - /// Current: arith coding state - /// - public ushort Current { get; set; } - - /// - /// Have we started decoding a new frame? - /// - public byte HeaderRead { get; set; } - - // Four literal models, each representing 64 symbols - - /// - /// For literals from 0 to 63 (selector = 0) - /// - public QTMDModel Model0 { get; set; } - - /// - /// For literals from 64 to 127 (selector = 1) - /// - public QTMDModel Model1 { get; set; } - - /// - /// For literals from 128 to 191 (selector = 2) - /// - public QTMDModel Model2 { get; set; } - - /// - /// For literals from 129 to 255 (selector = 3) - /// - public QTMDModel Model3 { get; set; } - - // Three match models. - - /// - /// For match with fixed length of 3 bytes - /// - public QTMDModel Model4 { get; set; } - - /// - /// For match with fixed length of 4 bytes - /// - public QTMDModel Model5 { get; set; } - - /// - /// For variable length match, encoded with model6len model - /// - public QTMDModel Model6 { get; set; } - - public QTMDModel Model6Len { get; set; } - - /// - /// Selector model. 0-6 to say literal (0,1,2,3) or match (4,5,6) - /// - public QTMDModel Model7 { get; set; } - - // Symbol arrays for all models - - public QTMDModelSym[] Model0Symbols { get; set; } = new QTMDModelSym[64 + 1]; - - public QTMDModelSym[] Model1Symbols { get; set; } = new QTMDModelSym[64 + 1]; - - public QTMDModelSym[] Model2Symbols { get; set; } = new QTMDModelSym[64 + 1]; - - public QTMDModelSym[] Model3Symbols { get; set; } = new QTMDModelSym[64 + 1]; - - public QTMDModelSym[] Model4Symbols { get; set; } = new QTMDModelSym[24 + 1]; - - public QTMDModelSym[] Model5Symbols { get; set; } = new QTMDModelSym[36 + 1]; - - public QTMDModelSym[] Model6Symbols { get; set; } = new QTMDModelSym[42 + 1]; - - public QTMDModelSym[] Model6LenSymbols { get; set; } = new QTMDModelSym[27 + 1]; - - public QTMDModelSym[] Model7Symbols { get; set; } = new QTMDModelSym[7 + 1]; - - #endregion - - /// - public override Error HUFF_ERROR() => throw new NotImplementedException(); - - /// - public override void READ_BYTES(BufferState state) - { - READ_IF_NEEDED(state); - if (Error != Error.MSPACK_ERR_OK) - return; - - byte b0 = InputBuffer[state.InputPointer++]; - - READ_IF_NEEDED(state); - if (Error != Error.MSPACK_ERR_OK) - return; - - byte b1 = InputBuffer[state.InputPointer++]; - INJECT_BITS_MSB((b0 << 8) | b1, 16, state); - } - } -} diff --git a/BurnOutSharp/External/libmspack/KWAJ/Decompressor.cs b/BurnOutSharp/External/libmspack/KWAJ/Decompressor.cs index b1e7a95a..8d85f023 100644 --- a/BurnOutSharp/External/libmspack/KWAJ/Decompressor.cs +++ b/BurnOutSharp/External/libmspack/KWAJ/Decompressor.cs @@ -155,13 +155,13 @@ namespace LibMSPackSharp.KWAJ } else if (hdr.KWAJHeader.CompressionType == CompressionType.MSKWAJ_COMP_LZH) { - LZHKWAJStream lzh = LZHKWAJ.Init(System, fh, outfh); - Error = (lzh != null) ? LZHKWAJ.Decompress(lzh) : Error.MSPACK_ERR_NOMEMORY; + LZHKWAJ lzh = LZHKWAJ.Init(System, fh, outfh); + Error = (lzh != null) ? lzh.Decompress() : Error.MSPACK_ERR_NOMEMORY; } else if (hdr.KWAJHeader.CompressionType == CompressionType.MSKWAJ_COMP_MSZIP) { - MSZIPDStream zip = MSZIP.Init(System, fh, outfh, KWAJ_INPUT_SIZE, false); - Error = (zip != null) ? MSZIP.DecompressKWAJ(zip) : Error.MSPACK_ERR_NOMEMORY; + MSZIP zip = MSZIP.Init(System, fh, outfh, KWAJ_INPUT_SIZE, false); + Error = (zip != null) ? zip.DecompressKWAJ() : Error.MSPACK_ERR_NOMEMORY; } else { diff --git a/BurnOutSharp/External/libmspack/OAB/Decompressor.cs b/BurnOutSharp/External/libmspack/OAB/Decompressor.cs index 94db15b5..1a4f6bfb 100644 --- a/BurnOutSharp/External/libmspack/OAB/Decompressor.cs +++ b/BurnOutSharp/External/libmspack/OAB/Decompressor.cs @@ -51,7 +51,7 @@ namespace LibMSPackSharp.OAB public Error Decompress(string input, string output) { byte[] hdrbuf = new byte[oabhead_SIZEOF]; - LZXDStream lzx = null; + LZX lzx = null; Error ret = Error.MSPACK_ERR_OK; FileStream infh = System.Open(input, OpenMode.MSPACK_SYS_OPEN_READ); @@ -235,7 +235,7 @@ namespace LibMSPackSharp.OAB public Error DecompressIncremental(string input, string basePath, string output) { byte[] hdrbuf = new byte[patchhead_SIZEOF]; - LZXDStream lzx = null; + LZX lzx = null; int window_bits; uint window_size; Error ret = Error.MSPACK_ERR_OK; @@ -348,7 +348,7 @@ namespace LibMSPackSharp.OAB return ret; } - ret = LZX.SetReferenceData(lzx, System, basefh, blk_ssize); + ret = lzx.SetReferenceData(System, basefh, blk_ssize); if (ret != Error.MSPACK_ERR_OK) { System.Close(outfh);