Partial classes

This commit is contained in:
Matt Nadareski
2022-05-24 16:52:41 -07:00
parent 4dbaa415c5
commit 15c05c65e7
28 changed files with 3601 additions and 3642 deletions

View File

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

View File

@@ -31,6 +31,6 @@ namespace LibMSPackSharp.CHM
/// <summary>
/// LZX decompressor state
/// </summary>
public LZXDStream State { get; set; }
public LZX State { get; set; }
}
}

View File

@@ -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
{
/// <summary>
/// i_ptr
/// </summary>
public int InputPointer { get; set; }
/// <summary>
/// i_end
/// </summary>
public int InputEnd { get; set; }
/// <summary>
/// bit_buffer
/// </summary>
public uint BitBuffer { get; set; }
/// <summary>
/// bits_left
/// </summary>
public int BitsLeft { get; set; }
#region Common
/// <summary>
/// Initialises bitstream state in state structure
/// </summary>
public void Init()
{
InputPointer = 0;
InputEnd = 0;
BitBuffer = 0;
BitsLeft = 0;
}
#endregion
#region MSB
/// <summary>
/// Removes N bits from the bit buffer
/// </summary>
public void REMOVE_BITS_MSB(int nbits)
{
BitBuffer <<= nbits;
BitsLeft -= nbits;
}
#endregion
#region LSB
/// <summary>
/// Removes N bits from the bit buffer
/// </summary>
public void REMOVE_BITS_LSB(int nbits)
{
BitBuffer >>= nbits;
BitsLeft -= nbits;
}
#endregion
}
}

View File

@@ -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
* <limits.h> 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<<N)-1). However, you can define BITS_LSB_TABLE to use a lookup
* table instead of computing this. This adds two new macros,
* PEEK_BITS_T and READ_BITS_T which work the same way as PEEK_BITS
* and READ_BITS, except they use this lookup table. This is useful if
* you need to look up a number of bits that are only known at
* runtime, so the bit mask can't be turned into a constant by the
* compiler.
* The bit buffer datatype should be at least 32 bits wide: it must be
* possible to ENSURE_BITS(17), so it must be possible to add 16 new bits
* to the bit buffer when the bit buffer already has 1 to 15 bits left.
*/
public abstract partial class CompressionStream : BaseDecompressState
{
#region Common
/// <summary>
/// Initialises bitstream state in state structure
/// </summary>
public void INIT_BITS()
{
InputPointer = 0;
InputEnd = 0;
BitBuffer = 0;
BitsLeft = 0;
EndOfInput = 0;
}
/// <summary>
/// Ensure there are at least N bits in the bit buffer
/// </summary>
public void ENSURE_BITS(int nbits)
{
while (BitsLeft < nbits)
{
READ_BYTES();
if (Error != Error.MSPACK_ERR_OK)
return;
}
}
/// <summary>
/// Read from the input if the buffer is empty
/// </summary>
public void READ_IF_NEEDED()
{
if (InputPointer >= InputEnd)
ReadInput();
}
/// <summary>
/// Read bytes from the input into the bit buffer
/// </summary>
public abstract void READ_BYTES();
/// <summary>
/// Read an input stream and fill the buffer
/// </summary>
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
/// <summary>
/// Inject data into the bit buffer
/// </summary>
public void INJECT_BITS_MSB(int bitdata, int nbits)
{
BitBuffer |= (uint)(bitdata << (BITBUF_WIDTH - nbits - BitsLeft));
BitsLeft += nbits;
}
/// <summary>
/// Extracts without removing N bits from the bit buffer
/// </summary>
public long PEEK_BITS_MSB(int nbits) => (BitBuffer >> (BITBUF_WIDTH - (nbits)));
/// <summary>
/// Takes N bits from the buffer and puts them in var
/// </summary>
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;
}
/// <summary>
/// Read multiple bits and put them in var
/// </summary>
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;
}
/// <summary>
/// Removes N bits from the bit buffer
/// </summary>
public void REMOVE_BITS_MSB(int nbits)
{
BitBuffer <<= nbits;
BitsLeft -= nbits;
}
#endregion
#region LSB
/// <summary>
/// Inject data into the bit buffer
/// </summary>
public void INJECT_BITS_LSB(int bitdata, int nbits)
{
BitBuffer |= (uint)(bitdata << BitsLeft);
BitsLeft += nbits;
}
/// <summary>
/// Extracts without removing N bits from the bit buffer
/// </summary>
public long PEEK_BITS_LSB(int nbits) => (BitBuffer & ((1 << (nbits)) - 1));
/// <summary>
/// Extracts without removing N bits from the bit buffer using a bit mask
/// </summary>
public long PEEK_BITS_T_LSB(int nbits) => BitBuffer & LSBBitMask[(nbits)];
/// <summary>
/// Takes N bits from the buffer and puts them in var
/// </summary>
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;
}
/// <summary>
/// Takes N bits from the buffer and puts them in var using a bit mask
/// </summary>
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;
}
/// <summary>
/// Removes N bits from the bit buffer
/// </summary>
public void REMOVE_BITS_LSB(int nbits)
{
BitBuffer >>= nbits;
BitsLeft -= nbits;
}
#endregion
}
}

View File

@@ -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
/// <summary>
/// Per compression error code for decoding failure
/// </summary>
public abstract Error HUFF_ERROR();
#endregion
#region MSB
/// <summary>
/// Decodes the next huffman symbol from the input bitstream into var.
/// Do not use this macro on a table unless build_decode_table() succeeded.
/// </summary>
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;
}
/// <summary>
/// Traverse for a single symbol
/// </summary>
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);
}
/// <summary>
/// This function was originally coded by David Tritscher.
///
/// It builds a fast huffman decoding table from
/// a canonical huffman code lengths table.
/// </summary>
/// <param name="nsyms">total number of symbols in this huffman tree.</param>
/// <param name="nbits">any symbols with a code length of nbits or less can be decoded in one lookup of the table.</param>
/// <param name="length">A table to get code lengths from [0 to nsyms-1]</param>
/// <param name="table">
/// The table to fill up with decoded symbols and pointers.
/// Should be ((1<<nbits) + (nsyms*2)) in length.
/// </param>
/// <returns>True for OK or false for error</returns>
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
/// <summary>
/// Decodes the next huffman symbol from the input bitstream into var.
/// Do not use this macro on a table unless build_decode_table() succeeded.
/// </summary>
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;
}
/// <summary>
/// Traverse for a single symbol
/// </summary>
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);
}
/// <summary>
/// This function was originally coded by David Tritscher.
///
/// It builds a fast huffman decoding table from
/// a canonical huffman code lengths table.
/// </summary>
/// <param name="nsyms">total number of symbols in this huffman tree.</param>
/// <param name="nbits">any symbols with a code length of nbits or less can be decoded in one lookup of the table.</param>
/// <param name="length">A table to get code lengths from [0 to nsyms-1]</param>
/// <param name="table">
/// The table to fill up with decoded symbols and pointers.
/// Should be ((1<<nbits) + (nsyms*2)) in length.
/// </param>
/// <returns>True for OK or false for error</returns>
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
}
}

View File

@@ -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; }
/// <summary>
/// i_ptr
/// </summary>
public int InputPointer { get; set; }
/// <summary>
/// i_end
/// </summary>
public int InputEnd { get; set; }
/// <summary>
/// o_ptr
/// </summary>
public int OutputPointer { get; set; }
/// <summary>
/// o_end
/// </summary>
public int OutputEnd { get; set; }
internal BufferState BufferState { get; set; }
/// <summary>
/// bit_buffer
/// </summary>
public uint BitBuffer { get; set; }
/// <summary>
/// bits_left
/// </summary>
public int BitsLeft { get; set; }
/// <summary>
/// Have we reached the end of input?
/// </summary>
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
* <limits.h> 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<<N)-1). However, you can define BITS_LSB_TABLE to use a lookup
* table instead of computing this. This adds two new macros,
* PEEK_BITS_T and READ_BITS_T which work the same way as PEEK_BITS
* and READ_BITS, except they use this lookup table. This is useful if
* you need to look up a number of bits that are only known at
* runtime, so the bit mask can't be turned into a constant by the
* compiler.
* The bit buffer datatype should be at least 32 bits wide: it must be
* possible to ENSURE_BITS(17), so it must be possible to add 16 new bits
* to the bit buffer when the bit buffer already has 1 to 15 bits left.
*/
#region Common
/// <summary>
/// Initialises bitstream state in state structure
/// </summary>
public void INIT_BITS()
{
BufferState = new BufferState();
BufferState.Init();
EndOfInput = 0;
}
/// <summary>
/// Stores bitstream state in state structure
/// </summary>
public void STORE_BITS(BufferState state)
{
BufferState.InputPointer = state.InputPointer;
BufferState.InputEnd = state.InputEnd;
BufferState.BitBuffer = state.BitBuffer;
BufferState.BitsLeft = state.BitsLeft;
}
/// <summary>
/// Restores bitstream state from state structure
/// </summary>
public BufferState RESTORE_BITS()
{
return new BufferState()
{
InputPointer = BufferState.InputPointer,
InputEnd = BufferState.InputEnd,
BitBuffer = BufferState.BitBuffer,
BitsLeft = BufferState.BitsLeft,
};
}
/// <summary>
/// Ensure there are at least N bits in the bit buffer
/// </summary>
public void ENSURE_BITS(int nbits, BufferState state)
{
while (state.BitsLeft < nbits)
{
READ_BYTES(state);
if (Error != Error.MSPACK_ERR_OK)
return;
}
}
/// <summary>
/// Read from the input if the buffer is empty
/// </summary>
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;
}
}
/// <summary>
/// Read bytes from the input into the bit buffer
/// </summary>
public abstract void READ_BYTES(BufferState state);
/// <summary>
/// Read an input stream and fill the buffer
/// </summary>
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
/// <summary>
/// Inject data into the bit buffer
/// </summary>
public void INJECT_BITS_MSB(int bitdata, int nbits, BufferState state)
{
state.BitBuffer |= (uint)(bitdata << (BITBUF_WIDTH - nbits - state.BitsLeft));
state.BitsLeft += nbits;
}
/// <summary>
/// Extracts without removing N bits from the bit buffer
/// </summary>
public long PEEK_BITS_MSB(int nbits, uint bit_buffer) => (bit_buffer >> (BITBUF_WIDTH - (nbits)));
/// <summary>
/// Takes N bits from the buffer and puts them in var
/// </summary>
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;
}
/// <summary>
/// Read multiple bits and put them in var
/// </summary>
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
/// <summary>
/// Inject data into the bit buffer
/// </summary>
public void INJECT_BITS_LSB(int bitdata, int nbits, BufferState state)
{
state.BitBuffer |= (uint)(bitdata << state.BitsLeft);
state.BitsLeft += nbits;
}
/// <summary>
/// Extracts without removing N bits from the bit buffer
/// </summary>
public long PEEK_BITS_LSB(int nbits, uint bit_buffer) => (bit_buffer & ((1 << (nbits)) - 1));
/// <summary>
/// Extracts without removing N bits from the bit buffer using a bit mask
/// </summary>
public long PEEK_BITS_T_LSB(int nbits, uint bit_buffer) => bit_buffer & LSBBitMask[(nbits)];
/// <summary>
/// Takes N bits from the buffer and puts them in var
/// </summary>
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;
}
/// <summary>
/// Takes N bits from the buffer and puts them in var using a bit mask
/// </summary>
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
/// <summary>
/// Per compression error code for decoding failure
/// </summary>
public abstract Error HUFF_ERROR();
#endregion
#region MSB
/// <summary>
/// Decodes the next huffman symbol from the input bitstream into var.
/// Do not use this macro on a table unless build_decode_table() succeeded.
/// </summary>
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;
}
/// <summary>
/// Traverse for a single symbol
/// </summary>
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);
}
/// <summary>
/// This function was originally coded by David Tritscher.
///
/// It builds a fast huffman decoding table from
/// a canonical huffman code lengths table.
/// </summary>
/// <param name="nsyms">total number of symbols in this huffman tree.</param>
/// <param name="nbits">any symbols with a code length of nbits or less can be decoded in one lookup of the table.</param>
/// <param name="length">A table to get code lengths from [0 to nsyms-1]</param>
/// <param name="table">
/// The table to fill up with decoded symbols and pointers.
/// Should be ((1<<nbits) + (nsyms*2)) in length.
/// </param>
/// <returns>True for OK or false for error</returns>
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
/// <summary>
/// Decodes the next huffman symbol from the input bitstream into var.
/// Do not use this macro on a table unless build_decode_table() succeeded.
/// </summary>
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;
}
/// <summary>
/// Traverse for a single symbol
/// </summary>
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);
}
/// <summary>
/// This function was originally coded by David Tritscher.
///
/// It builds a fast huffman decoding table from
/// a canonical huffman code lengths table.
/// </summary>
/// <param name="nsyms">total number of symbols in this huffman tree.</param>
/// <param name="nbits">any symbols with a code length of nbits or less can be decoded in one lookup of the table.</param>
/// <param name="length">A table to get code lengths from [0 to nsyms-1]</param>
/// <param name="table">
/// The table to fill up with decoded symbols and pointers.
/// Should be ((1<<nbits) + (nsyms*2)) in length.
/// </param>
/// <returns>True for OK or false for error</returns>
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
}
}

View File

@@ -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
{
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>
/// Safely read bits from the buffer
/// </summary>
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;
}
/// <summary>
/// Safely read a symbol from a Huffman tree
/// </summary>
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;
}
/// <summary>
/// Write a single byte to the output stream
/// </summary>
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;
}
}
}

View File

@@ -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
{
/// <inheritdoc/>
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);
}
/// <inheritdoc/>
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;
}
}
}

View File

@@ -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
{
/// <inheritdoc/>
public override Error HUFF_ERROR() => Error.MSPACK_ERR_DATAFORMAT;
}
}

View File

@@ -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
{
/// <summary>
/// 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.
/// </summary>
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];
}
}

View File

@@ -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.
*/
/// <summary>
/// Safely read bits from the buffer
/// </summary>
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;
}
/// <summary>
/// Safely read a symbol from a Huffman tree
/// </summary>
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;
}
/// <summary>
/// Write a single byte to the output stream
/// </summary>
public void WRITE_BYTE(int pos)
{
if (System.Write(OutputFileHandle, Window, pos, 1) != 1)
Error = Error.MSPACK_ERR_WRITE;
}
#endregion
/// <inheritdoc/>
public override Error HUFF_ERROR() => Error.MSPACK_ERR_DATAFORMAT;
/// <inheritdoc/>
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);
}
/// <inheritdoc/>
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;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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
{
/// <inheritdoc/>
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);
}
}
}

View File

@@ -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
{
/// <inheritdoc/>
public override Error HUFF_ERROR() => Error.MSPACK_ERR_DECRUNCH;
}
}

View File

@@ -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
{
/// <summary>
/// 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
/// </summary>
/// <param name="system">
/// an mspack_system structure used to read from
/// the input stream and write to the output
/// stream, also to allocate and free memory.
/// </param>
/// <param name="input">an input stream with the LZX data.</param>
/// <param name="output">an output stream to write the decoded data to.</param>
/// <param name="window_bits">
/// 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.</param>
/// <param name="reset_interval">
/// 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.
/// </param>
/// <param name="input_buffer_size">
/// the number of bytes to use as an input
/// bitstream buffer.
/// </param>
/// <param name="output_length">
/// 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.
/// </param>
/// <param name="is_delta">
/// should be zero for all regular LZX data,
/// non-zero for LZX DELTA encoded data.
/// </param>
/// <returns>
/// a pointer to an initialised LZXDStream structure, or null if
/// there was not enough memory or parameters to the function were wrong.
/// </returns>
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; }
/// <summary>
/// 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
/// </summary>
/// <param name="lzx">the LZX stream to apply this reference data to</param>
/// <param name="system">
/// an mspack_system implementation to use with the
/// input param. Only read() will be called.
/// </param>
/// <param name="input"> an input file handle to read reference data using system.read().</param>
/// <param name="length">
/// the length of the reference data. Cannot be longer
/// than the LZX window size.
/// </param>
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
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; }
/// <summary>
/// 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
/// </summary>
/// <param name="o">LZX decompression state, as allocated by lzxd_init().</param>
/// <param name="out_bytes">the number of bytes of data to decompress.</param>
/// <returns>an error code, or MSPACK_ERR_OK if successful</returns>
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];
/// <summary>
/// Window size
/// </summary>
public uint WindowSize { get; set; }
// Easy answers
if (lzx == null || (out_bytes < 0))
return Error.MSPACK_ERR_ARGS;
/// <summary>
/// LZX DELTA reference data size
/// </summary>
public uint ReferenceDataSize { get; set; }
if (lzx.Error != Error.MSPACK_ERR_OK)
return lzx.Error;
/// <summary>
/// Number of match_offset entries in table
/// </summary>
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;
/// <summary>
/// Decompression offset within window
/// </summary>
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; }
/// <summary>
/// Current frame offset within in window
/// </summary>
public uint FramePosition { get; set; }
lzx.OutputPointer += leftover_bytes;
lzx.Offset += leftover_bytes;
out_bytes -= leftover_bytes;
}
/// <summary>
/// The number of 32kb frames processed
/// </summary>
public uint Frame { get; set; }
if (out_bytes == 0)
return Error.MSPACK_ERR_OK;
/// <summary>
/// Which frame do we reset the compressor?
/// </summary>
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;
/// <summary>
/// For the LRU offset system
/// </summary>
public uint[] R { get; set; } = new uint[3];
uint end_frame = (uint)((lzx.Offset + out_bytes) / LZX_FRAME_SIZE) + 1;
/// <summary>
/// Uncompressed length of this LZX block
/// </summary>
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++;
}
}
/// <summary>
/// Uncompressed bytes still left to decode
/// </summary>
public int BlockRemaining { get; set; }
// Re-read the intel header and reset the huffman lengths
lzx.ResetState();
R = lzx.R;
}
/// <summary>
/// Magic header value used for transform
/// </summary>
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);
}
/// <summary>
/// Has intel E8 decoding started?
/// </summary>
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);
/// <summary>
/// Type of the current block
/// </summary>
public LZXBlockType BlockType { get; set; }
// if (i != 0)
// {
// i = (int)lzx.READ_BITS_MSB(16, state);
// j = (int)lzx.READ_BITS_MSB(16, state);
// }
/// <summary>
/// Have we started decoding at all yet?
/// </summary>
public byte HeaderRead { get; set; }
// lzx.IntelFileSize = (i << 16) | j;
// lzx.HeaderRead = 1;
//}
/// <summary>
/// Does stream follow LZX DELTA spec?
/// </summary>
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;
}
/// <summary>
/// Is the output pointer referring to E8?
/// </summary>
public bool OutputIsE8 { get; set; }
}
}

View File

@@ -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
/// <summary>
/// Number of bytes actually output
/// </summary>
public long Offset { get; set; }
/// <summary>
/// Overall decompressed length of stream
/// </summary>
public long Length { get; set; }
/// <summary>
/// Decoding window
/// </summary>
public byte[] Window { get; set; }
/// <summary>
/// Window size
/// </summary>
public uint WindowSize { get; set; }
/// <summary>
/// LZX DELTA reference data size
/// </summary>
public uint ReferenceDataSize { get; set; }
/// <summary>
/// Number of match_offset entries in table
/// </summary>
public uint NumOffsets { get; set; }
/// <summary>
/// Decompression offset within window
/// </summary>
public int WindowPosition { get; set; }
/// <summary>
/// Current frame offset within in window
/// </summary>
public uint FramePosition { get; set; }
/// <summary>
/// The number of 32kb frames processed
/// </summary>
public uint Frame { get; set; }
/// <summary>
/// Which frame do we reset the compressor?
/// </summary>
public uint ResetInterval { get; set; }
/// <summary>
/// For the LRU offset system
/// </summary>
public uint[] R { get; set; } = new uint[3];
/// <summary>
/// Uncompressed length of this LZX block
/// </summary>
public int BlockLength { get; set; }
/// <summary>
/// Uncompressed bytes still left to decode
/// </summary>
public int BlockRemaining { get; set; }
/// <summary>
/// Magic header value used for transform
/// </summary>
public int IntelFileSize { get; set; }
/// <summary>
/// Has intel E8 decoding started?
/// </summary>
public bool IntelStarted { get; set; }
/// <summary>
/// Type of the current block
/// </summary>
public LZXBlockType BlockType { get; set; }
/// <summary>
/// Have we started decoding at all yet?
/// </summary>
public byte HeaderRead { get; set; }
/// <summary>
/// Does stream follow LZX DELTA spec?
/// </summary>
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];
/// <summary>
/// Is the output pointer referring to E8?
/// </summary>
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
/// <inheritdoc/>
public override Error HUFF_ERROR() => Error.MSPACK_ERR_DECRUNCH;
/// <inheritdoc/>
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);
}
}
}

View File

@@ -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
{
/// <summary>
/// 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.
/// </summary>
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,
};
}
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>
/// Decompresses an entire MS-ZIP stream in a KWAJ file. Acts very much
/// like mszipd_decompress(), but doesn't take an out_bytes parameter
/// </summary>
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;
}
/// <summary>
/// A clean implementation of RFC 1951 / inflate
/// </summary>
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;
}
/// <summary>
/// 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.
/// </summary>
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;
}
}
}

View File

@@ -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
{
/// <inheritdoc/>
public override void READ_BYTES()
{
READ_IF_NEEDED();
if (Error != Error.MSPACK_ERR_OK)
return;
INJECT_BITS_LSB(InputBuffer[InputPointer++], 8);
}
}
}

View File

@@ -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
{
/// <inheritdoc/>
public override Error HUFF_ERROR() => Error.INF_ERR_HUFFSYM;
}
}

View File

@@ -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
{
/// <summary>
/// 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
/// </summary>
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];
/// <summary>
/// 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
/// </summary>
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;
}
/// <summary>
/// Decompresses an entire MS-ZIP stream in a KWAJ file. Acts very much
/// like mszipd_decompress(), but doesn't take an out_bytes parameter
/// </summary>
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;
}
/// <summary>
/// A clean implementation of RFC 1951 / inflate
/// </summary>
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;
}
/// <summary>
/// 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.
/// </summary>
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
}
}

View File

@@ -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
/// <summary>
/// 32kb history window
/// </summary>
public byte[] Window { get; set; } = new byte[MSZIP_FRAME_SIZE];
/// <summary>
/// Offset within window
/// </summary>
public uint WindowPosition { get; set; }
/// <summary>
/// inflate() will call this whenever the window should be emptied.
/// </summary>
public Func<MSZIPDStream, uint, Error> 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
/// <inheritdoc/>
public override Error HUFF_ERROR() => Error.INF_ERR_HUFFSYM;
/// <inheritdoc/>
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);
}
}
}

View File

@@ -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
{
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>
/// 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.
/// </summary>
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);
}
}
}
}

View File

@@ -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
{
/// <inheritdoc/>
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);
}
}
}

View File

@@ -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
{
/// <inheritdoc/>
public override Error HUFF_ERROR() => throw new NotImplementedException();
}
}

View File

@@ -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
{
/// <summary>
/// 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
/// </summary>
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; }
/// <summary>
/// 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
/// </summary>
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;
/// <summary>
/// Decompression offset within window
/// </summary>
public uint WindowPosition { get; set; }
uint bit_buffer;
int bits_left;
/// <summary>
/// Bytes remaining for current frame
/// </summary>
public uint FrameTODO { get; set; }
// Easy answers
if (qtm == null || (out_bytes < 0))
return Error.MSPACK_ERR_ARGS;
/// <summary>
/// High: arith coding state
/// </summary>
public ushort High { get; set; }
if (qtm.Error != Error.MSPACK_ERR_OK)
return qtm.Error;
/// <summary>
/// Low: arith coding state
/// </summary>
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;
/// <summary>
/// Current: arith coding state
/// </summary>
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;
/// <summary>
/// Have we started decoding a new frame?
/// </summary>
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;
/// <summary>
/// For literals from 0 to 63 (selector = 0)
/// </summary>
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;
/// <summary>
/// For literals from 64 to 127 (selector = 1)
/// </summary>
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;
}
/// <summary>
/// For literals from 128 to 191 (selector = 2)
/// </summary>
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;
/// <summary>
/// For literals from 129 to 255 (selector = 3)
/// </summary>
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;
/// <summary>
/// For match with fixed length of 3 bytes
/// </summary>
public QTMDModel Model4 { get; set; }
case 2:
mdl = qtm.Model2;
break;
/// <summary>
/// For match with fixed length of 4 bytes
/// </summary>
public QTMDModel Model5 { get; set; }
case 3:
default:
mdl = qtm.Model3;
break;
}
/// <summary>
/// For variable length match, encoded with model6len model
/// </summary>
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;
/// <summary>
/// Selector model. 0-6 to say literal (0,1,2,3) or match (4,5,6)
/// </summary>
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];
}
}

View File

@@ -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
/// <summary>
/// Decoding window
/// </summary>
public byte[] Window { get; set; }
/// <summary>
/// Window size
/// </summary>
public uint WindowSize { get; set; }
/// <summary>
/// Decompression offset within window
/// </summary>
public uint WindowPosition { get; set; }
/// <summary>
/// Bytes remaining for current frame
/// </summary>
public uint FrameTODO { get; set; }
/// <summary>
/// High: arith coding state
/// </summary>
public ushort High { get; set; }
/// <summary>
/// Low: arith coding state
/// </summary>
public ushort Low { get; set; }
/// <summary>
/// Current: arith coding state
/// </summary>
public ushort Current { get; set; }
/// <summary>
/// Have we started decoding a new frame?
/// </summary>
public byte HeaderRead { get; set; }
// Four literal models, each representing 64 symbols
/// <summary>
/// For literals from 0 to 63 (selector = 0)
/// </summary>
public QTMDModel Model0 { get; set; }
/// <summary>
/// For literals from 64 to 127 (selector = 1)
/// </summary>
public QTMDModel Model1 { get; set; }
/// <summary>
/// For literals from 128 to 191 (selector = 2)
/// </summary>
public QTMDModel Model2 { get; set; }
/// <summary>
/// For literals from 129 to 255 (selector = 3)
/// </summary>
public QTMDModel Model3 { get; set; }
// Three match models.
/// <summary>
/// For match with fixed length of 3 bytes
/// </summary>
public QTMDModel Model4 { get; set; }
/// <summary>
/// For match with fixed length of 4 bytes
/// </summary>
public QTMDModel Model5 { get; set; }
/// <summary>
/// For variable length match, encoded with model6len model
/// </summary>
public QTMDModel Model6 { get; set; }
public QTMDModel Model6Len { get; set; }
/// <summary>
/// Selector model. 0-6 to say literal (0,1,2,3) or match (4,5,6)
/// </summary>
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
/// <inheritdoc/>
public override Error HUFF_ERROR() => throw new NotImplementedException();
/// <inheritdoc/>
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);
}
}
}

View File

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

View File

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