From d715072cbce4b13f030835e87af4cd9bd7c7352d Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 14 Dec 2022 10:55:56 -0800 Subject: [PATCH] Start writing Inflate implementation --- BurnOutSharp/FileType/MicrosoftCAB.MSZIP.cs | 931 +++++++++++++++++--- 1 file changed, 790 insertions(+), 141 deletions(-) diff --git a/BurnOutSharp/FileType/MicrosoftCAB.MSZIP.cs b/BurnOutSharp/FileType/MicrosoftCAB.MSZIP.cs index ec3986d6..8a88441b 100644 --- a/BurnOutSharp/FileType/MicrosoftCAB.MSZIP.cs +++ b/BurnOutSharp/FileType/MicrosoftCAB.MSZIP.cs @@ -1,4 +1,7 @@ -using BurnOutSharp.Tools; +using System; +using System.Collections; +using System.Collections.Generic; +using BurnOutSharp.Tools; /// /// @@ -81,7 +84,7 @@ namespace BurnOutSharp.FileType /// /// How the data are compressed /// - public enum DeflateCompressionType : byte + public enum MSZIPDeflateCompressionType : byte { /// /// no compression @@ -104,173 +107,819 @@ namespace BurnOutSharp.FileType Reserved = 0b11, } + public class MSZIPDeflateStream + { + #region Instance Variables + + /// + /// Original data source to read from + /// + private System.IO.Stream _dataStream = null; + + /// + /// Current rolling buffer + /// + private byte[] _buffer = null; + + /// + /// Current position in the buffer + /// + private int _bufferPointer = -1; + + /// + /// Bit buffer to read bits from when necessary + /// + private BitArray _bitBuffer = null; + + /// + /// Number of bits left in the buffer + /// + private int _bitsLeft = 0; + + #endregion + + /// + /// Constructor + /// + public MSZIPDeflateStream(System.IO.Stream dataStream) + { + _dataStream = dataStream; + } + + /// + /// Read between 0 and 64 bits of data from the stream assuming LSB + /// + /// + public ulong ReadBitsLSB(int numBits) + { + // If we are reading an invalid number of bits + if (numBits < 0 || numBits > 64) + throw new ArgumentOutOfRangeException(); + + // Allocate the bit buffer + ulong bitBuffer = 0; + + // If the bit buffer has the right number remaining + if (_bitsLeft >= numBits) + { + for (int i = 0; i < numBits; i++) + { + bitBuffer |= _bitBuffer[i + _bitBuffer.Length - _bitsLeft--] ? 1u : 0; + bitBuffer <<= 1; + } + + return bitBuffer; + } + + // Otherwise, we need to read what we can + int bitsRemaining = _bitsLeft; + for (int i = 0; i < bitsRemaining; i++) + { + bitBuffer |= _bitBuffer[i + _bitBuffer.Length - _bitsLeft--] ? 1u : 0; + bitBuffer <<= 1; + } + + // Fill the bit buffer, if possible + FillBitBuffer(); + + // If we couldn't read anything, throw an exception + if (_buffer == null) + throw new IndexOutOfRangeException(); + + // Otherwise, read in the remaining bits needed + for (int i = 0; i < bitsRemaining; i++) + { + bitBuffer |= _bitBuffer[i + _bitBuffer.Length - _bitsLeft--] ? 1u : 0; + bitBuffer <<= 1; + } + + return bitBuffer; + } + + /// + /// Read between 0 and 64 bits of data from the stream assuming MSB + /// + /// + public ulong ReadBitsMSB(int numBits) + { + // If we are reading an invalid number of bits + if (numBits < 0 || numBits > 64) + throw new ArgumentOutOfRangeException(); + + // Allocate the bit buffer + ulong bitBuffer = 0; + + // If the bit buffer has the right number remaining + if (_bitsLeft >= numBits) + { + for (int i = 0; i < numBits; i++) + { + bitBuffer |= _bitBuffer[i + _bitsLeft--] ? 1u : 0; + bitBuffer <<= 1; + } + + return bitBuffer; + } + + // Otherwise, we need to read what we can + int bitsRemaining = _bitsLeft; + for (int i = 0; i < bitsRemaining; i++) + { + bitBuffer |= _bitBuffer[i + _bitsLeft--] ? 1u : 0; + bitBuffer <<= 1; + } + + // Fill the bit buffer, if possible + FillBitBuffer(); + + // If we couldn't read anything, throw an exception + if (_buffer == null) + throw new IndexOutOfRangeException(); + + // Otherwise, read in the remaining bits needed + for (int i = 0; i < bitsRemaining; i++) + { + bitBuffer |= _bitBuffer[i + _bitsLeft--] ? 1u : 0; + bitBuffer <<= 1; + } + + return bitBuffer; + } + + /// + /// Read more than 0 bytes of data from the stream assuming LSB + /// + public byte[] ReadBytesLSB(int numBytes) + { + // If we are reading an invalid number of bytes + if (numBytes < 0) + throw new ArgumentOutOfRangeException(); + + // Allocate the byte buffer + byte[] byteBuffer = new byte[numBytes]; + int byteBufferPtr = 0; + + // If the bit buffer has the right number remaining + if (_bitsLeft >= numBytes * 8) + { + byte fullBitBuffer = 0; + for (int i = 0; i < numBytes * 8; i++) + { + fullBitBuffer |= (byte)(_bitBuffer[i + _bitBuffer.Length - _bitsLeft--] ? 1 : 0); + if (i % 8 == 7) + { + byteBuffer[byteBufferPtr++] = fullBitBuffer; + fullBitBuffer = 0; + } + else + { + fullBitBuffer <<= 1; + } + } + + byteBuffer[byteBufferPtr++] = fullBitBuffer; + return byteBuffer; + } + + // Otherwise, we need to read what we can + int bitsRemaining = _bitsLeft; + + byte bitBuffer = 0; + for (int i = 0; i < numBytes * 8; i++) + { + bitBuffer |= (byte)(_bitBuffer[i + _bitBuffer.Length - _bitsLeft--] ? 1 : 0); + if (i % 8 == 7) + { + byteBuffer[byteBufferPtr++] = bitBuffer; + bitBuffer = 0; + } + else + { + bitBuffer <<= 1; + } + } + + // Fill the bit buffer, if possible + FillBitBuffer(); + + // If we couldn't read anything, throw an exception + if (_buffer == null) + throw new IndexOutOfRangeException(); + + // Otherwise, read in the remaining bits needed + for (int i = 0; i < bitsRemaining; i++) + { + bitBuffer |= (byte)(_bitBuffer[i + _bitBuffer.Length - _bitsLeft--] ? 1 : 0); + if (i % 8 == 7) + { + byteBuffer[byteBufferPtr++] = bitBuffer; + bitBuffer = 0; + } + else + { + bitBuffer <<= 1; + } + } + + byteBuffer[byteBufferPtr++] = bitBuffer; + return byteBuffer; + } + + /// + /// Read more than 0 bytes of data from the stream assuming MSB + /// + public byte[] ReadBytesMSB(int numBytes) + { + // If we are reading an invalid number of bytes + if (numBytes < 0) + throw new ArgumentOutOfRangeException(); + + // Allocate the byte buffer + byte[] byteBuffer = new byte[numBytes]; + int byteBufferPtr = 0; + + // If the bit buffer has the right number remaining + if (_bitsLeft >= numBytes * 8) + { + byte fullBitBuffer = 0; + for (int i = 0; i < numBytes * 8; i++) + { + fullBitBuffer |= (byte)(_bitBuffer[i + _bitsLeft--] ? 1 : 0); + if (i % 8 == 7) + { + byteBuffer[byteBufferPtr++] = fullBitBuffer; + fullBitBuffer = 0; + } + else + { + fullBitBuffer <<= 1; + } + } + + byteBuffer[byteBufferPtr++] = fullBitBuffer; + return byteBuffer; + } + + // Otherwise, we need to read what we can + int bitsRemaining = _bitsLeft; + + byte bitBuffer = 0; + for (int i = 0; i < numBytes * 8; i++) + { + bitBuffer |= (byte)(_bitBuffer[i + _bitsLeft--] ? 1 : 0); + if (i % 8 == 7) + { + byteBuffer[byteBufferPtr++] = bitBuffer; + bitBuffer = 0; + } + else + { + bitBuffer <<= 1; + } + } + + // Fill the bit buffer, if possible + FillBitBuffer(); + + // If we couldn't read anything, throw an exception + if (_buffer == null) + throw new IndexOutOfRangeException(); + + // Otherwise, read in the remaining bits needed + for (int i = 0; i < bitsRemaining; i++) + { + bitBuffer |= (byte)(_bitBuffer[i + _bitsLeft--] ? 1 : 0); + if (i % 8 == 7) + { + byteBuffer[byteBufferPtr++] = bitBuffer; + bitBuffer = 0; + } + else + { + bitBuffer <<= 1; + } + } + + byteBuffer[byteBufferPtr++] = bitBuffer; + return byteBuffer; + } + + /// + /// Discard bits in the array up to the next byte boundary + /// + public void DiscardToByteBoundary() + { + int bitsToDiscard = _bitsLeft & 7; + _bitsLeft -= bitsToDiscard; + } + + /// + /// Fill the internal bit buffer from the internal buffer + /// + /// Fills up to 4 bytes worth of data at a time + private void FillBitBuffer() + { + // If we have 4 bytes left, just create the bit buffer directly + if (_bufferPointer < _buffer.Length - 4) + { + // Read all 4 bytes directly + byte[] readAllBytes = new ReadOnlySpan(_buffer, _bufferPointer, 4).ToArray(); + _bufferPointer += 4; + + // Create the new bit buffer + _bitBuffer = new BitArray(readAllBytes); + _bitsLeft = 32; + return; + } + + // If we have less than 4 bytes left, we need to get creative + // Create the byte array to hold the data + byte[] bytes = new byte[4]; + + // Read what we can first + int bytesRemaining = _buffer.Length - _bufferPointer; + if (bytesRemaining > 0) + { + byte[] readBytesRemaining = new ReadOnlySpan(_buffer, _bufferPointer, bytesRemaining).ToArray(); + Array.Copy(readBytesRemaining, 0, bytes, 0, bytesRemaining); + _bufferPointer += bytesRemaining; + } + + // Fill the buffer, if we can + FillBuffer(); + + // If we couldn't read anything, reset the buffer + if (_buffer == null && bytesRemaining == 4) + { + _bitBuffer = null; + _bitsLeft = 0; + return; + } + + // If we don't have anything left, just create a bit array + if (_buffer == null) + { + byte[] readBytesRemaining = new ReadOnlySpan(bytes, 0, bytesRemaining).ToArray(); + _bitBuffer = new BitArray(readBytesRemaining); + _bitsLeft = 8 * bytesRemaining; + return; + } + + // Otherwise, we want to read in the remaining necessary bytes + int bytesToRead = 4 - bytesRemaining; + byte[] bytesRead = new ReadOnlySpan(_buffer, _bufferPointer, bytesToRead).ToArray(); + _bufferPointer += bytesToRead; + + Array.Copy(bytesRead, 0, bytes, bytesRemaining, bytesToRead); + _bitBuffer = new BitArray(bytes); + _bitsLeft = 32; + } + + /// + /// Fill the internal buffer from the original data source + /// + /// Reads up to 4096 bytes at a time + private void FillBuffer() + { + // Get the amount of bytes to read + int bytesRemaining = (int)(_dataStream.Length - _dataStream.Position); + int bytesToRead = Math.Min(bytesRemaining, 4096); + + // If we can't ready any bytes, reset the buffer + if (bytesToRead == 0) + { + _buffer = null; + _bufferPointer = -1; + return; + } + + // Otherwise, read and reset the position + _buffer = _dataStream.ReadBytes(bytesToRead); + _bufferPointer = 0; + } + } + public class MSZIPDeflate { - /* - 3.2.5. Compressed blocks (length and distance codes) + #region Properties - As noted above, encoded data blocks in the "deflate" format - consist of sequences of symbols drawn from three conceptually - distinct alphabets: either literal bytes, from the alphabet of - byte values(0..255), or pairs, - where the length is drawn from(3..258) and the distance is - drawn from(1..32,768). In fact, the literal and length - alphabets are merged into a single alphabet(0..285), where - values 0..255 represent literal bytes, the value 256 indicates - end-of-block, and values 257..285 represent length codes - (possibly in conjunction with extra bits following the symbol - code) as follows: + /// + /// Match lengths for literal codes 257..285 + /// + /// Each value here is the lower bound for lengths represented + public Dictionary LiteralLengths + { + get + { + // If we have cached length mappings, use those + if (_literalLengths != null) + return _literalLengths; - Extra Extra Extra - Code Bits Length(s) Code Bits Lengths Code Bits Length(s) - ---- ---- ------ ---- ---- ------- ---- ---- ------- - 257 0 3 267 1 15,16 277 4 67-82 - 258 0 4 268 1 17,18 278 4 83-98 - 259 0 5 269 2 19-22 279 4 99-114 - 260 0 6 270 2 23-26 280 4 115-130 - 261 0 7 271 2 27-30 281 5 131-162 - 262 0 8 272 2 31-34 282 5 163-194 - 263 0 9 273 3 35-42 283 5 195-226 - 264 0 10 274 3 43-50 284 5 227-257 - 265 1 11,12 275 3 51-58 285 0 258 - 266 1 13,14 276 3 59-66 + // Otherwise, build it from scratch + _literalLengths = new Dictionary + { + [257] = 3, + [258] = 4, + [259] = 5, + [260] = 6, + [261] = 7, + [262] = 8, + [263] = 9, + [264] = 10, + [265] = 11, // 11,12 + [266] = 13, // 13,14 + [267] = 15, // 15,16 + [268] = 17, // 17,18 + [269] = 19, // 19-22 + [270] = 23, // 23-26 + [271] = 27, // 27-30 + [272] = 31, // 31-34 + [273] = 35, // 35-42 + [274] = 43, // 43-50 + [275] = 51, // 51-58 + [276] = 59, // 59-66 + [277] = 67, // 67-82 + [278] = 83, // 83-98 + [279] = 99, // 99-114 + [280] = 115, // 115-130 + [281] = 131, // 131-162 + [282] = 163, // 163-194 + [283] = 195, // 195-226 + [284] = 227, // 227-257 + [285] = 258, + }; - The extra bits should be interpreted as a machine integer - stored with the most-significant bit first, e.g., bits 1110 - represent the value 14. + return _literalLengths; + } + } - Extra Extra Extra - Code Bits Dist Code Bits Dist Code Bits Distance - ---- ---- ---- ---- ---- ------ ---- ---- -------- - 0 0 1 10 4 33-48 20 9 1025-1536 - 1 0 2 11 4 49-64 21 9 1537-2048 - 2 0 3 12 5 65-96 22 10 2049-3072 - 3 0 4 13 5 97-128 23 10 3073-4096 - 4 1 5,6 14 6 129-192 24 11 4097-6144 - 5 1 7,8 15 6 193-256 25 11 6145-8192 - 6 2 9-12 16 7 257-384 26 12 8193-12288 - 7 2 13-16 17 7 385-512 27 12 12289-16384 - 8 3 17-24 18 8 513-768 28 13 16385-24576 - 9 3 25-32 19 8 769-1024 29 13 24577-32768 - */ + /// + /// Extra bits for literal codes 257..285 + /// + public Dictionary LiteralExtraBits + { + get + { + // If we have cached bit mappings, use those + if (_literalExtraBits != null) + return _literalExtraBits; + + // Otherwise, build it from scratch + _literalExtraBits = new Dictionary(); + + // Literal Value 257 - 264, 0 bits + for (int i = 257; i < 265; i++) + _literalExtraBits[i] = 0; + + // Literal Value 265 - 268, 1 bit + for (int i = 265; i < 269; i++) + _literalExtraBits[i] = 1; + + // Literal Value 269 - 272, 2 bits + for (int i = 269; i < 273; i++) + _literalExtraBits[i] = 2; + + // Literal Value 273 - 276, 3 bits + for (int i = 273; i < 277; i++) + _literalExtraBits[i] = 3; + + // Literal Value 277 - 280, 4 bits + for (int i = 277; i < 281; i++) + _literalExtraBits[i] = 4; + + // Literal Value 281 - 284, 5 bits + for (int i = 281; i < 285; i++) + _literalExtraBits[i] = 5; + + // Literal Value 285, 0 bits + _literalExtraBits[285] = 0; + + return _literalExtraBits; + } + } + + /// + /// Match offsets for distance codes 0..29 + /// + /// Each value here is the lower bound for lengths represented + public static readonly int[] DistanceOffsets = new int[30] + { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, + 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, + 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, + }; + + /// + /// Extra bits for distance codes 0..29 + /// + public static readonly int[] DistanceExtraBits = new int[30] + { + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, + 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, + 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, + }; + + /// + /// The order of the bit length Huffman code lengths + /// + public static readonly int[] BitLengthOrder = new int[19] + { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15, + }; + + #endregion + + #region Instance Variables + + /// + /// Match lengths for literal codes 257..285 + /// + private Dictionary _literalLengths = null; + + /// + /// Extra bits for literal codes 257..285 + /// + private Dictionary _literalExtraBits = null; + + #endregion + + /// + /// The decoding algorithm for the actual data + /// + public static void Decode(MSZIPDeflateStream data) + { + // Create the output byte array + List decodedBytes = new List(); + + // Create the loop variable block + MSZIPDeflateBlock block; + + do + { + ulong header = data.ReadBitsLSB(3); + block = new MSZIPDeflateBlock(header); + + // We should never get a reserved block + if (block.BTYPE == MSZIPDeflateCompressionType.Reserved) + throw new Exception(); + + // If stored with no compression + if (block.BTYPE == MSZIPDeflateCompressionType.NoCompression) + { + // Skip any remaining bits in current partially processed byte + data.DiscardToByteBoundary(); + + // Read LEN and NLEN + byte[] nonCompressedHeader = data.ReadBytesLSB(4); + block.BlockData = new MSZIPNonCompressedBlock(nonCompressedHeader); + + // Copy LEN bytes of data to output + ushort length = ((MSZIPNonCompressedBlock)block.BlockData).LEN; + ((MSZIPNonCompressedBlock)block.BlockData).Data = data.ReadBytesLSB(length); + decodedBytes.AddRange(((MSZIPNonCompressedBlock)block.BlockData).Data); + } + + // Otherwise + else + { + block.BlockData = block.BTYPE == MSZIPDeflateCompressionType.DynamicHuffman + ? (IMSZIPBlockData)new MSZIPDynamicHuffmanCompressedBlock() + : (IMSZIPBlockData)new MSZIPFixedHuffmanCompressedBlock(); + + // If compressed with dynamic Huffman codes + if (block.BTYPE == MSZIPDeflateCompressionType.DynamicHuffman) + { + // read representation of code trees (see subsection below) + } + + // Loop until end of block code recognized + while (true) + { + /* + decode literal/length value from input stream + if value < 256 + copy value (literal byte) to output stream + otherwise + if value = end of block (256) + break from loop + otherwise (value = 257..285) + decode distance from input stream + + move backwards distance bytes in the output + stream, and copy length bytes from this + position to the output stream. + */ + } + } + } while (!block.BFINAL); + + /* + Note that a duplicated string reference may refer to a string + in a previous block; i.e., the backward distance may cross one + or more block boundaries. However a distance cannot refer past + the beginning of the output stream. (An application using a + preset dictionary might discard part of the output stream; a + distance can refer to that part of the output stream anyway) + Note also that the referenced string may overlap the current + position; for example, if the last 2 bytes decoded have values + X and Y, a string reference with + adds X,Y,X,Y,X to the output stream. + */ + } } public class MSZIPDeflateBlock { - /* - 3.2.3. Details of block format + #region Properties - Each block of compressed data begins with 3 header bits - containing the following data: + /// + /// Set if and only if this is the last block of the data set. + /// + /// Bit 0 + public bool BFINAL { get; set; } - first bit BFINAL - next 2 bits BTYPE + /// + /// Specifies how the data are compressed + /// + /// Bits 1-2 + public MSZIPDeflateCompressionType BTYPE { get; set; } - Note that the header bits do not necessarily begin on a byte - boundary, since a block does not necessarily occupy an integral - number of bytes. + /// + /// Block data as defined by the compression type + /// + public IMSZIPBlockData BlockData { get; set; } - BFINAL is set if and only if this is the last block of the data - set. + #endregion - BTYPE specifies how the data are compressed, as follows: - - 00 - no compression - 01 - compressed with fixed Huffman codes - 10 - compressed with dynamic Huffman codes - 11 - reserved (error) - - The only difference between the two compressed cases is how the - Huffman codes for the literal/length and distance alphabets are - defined. - - In all cases, the decoding algorithm for the actual data is as - follows: - - do - read block header from input stream. - if stored with no compression - skip any remaining bits in current partially - processed byte - read LEN and NLEN (see next section) - copy LEN bytes of data to output - otherwise - if compressed with dynamic Huffman codes - read representation of code trees (see - subsection below) - loop (until end of block code recognized) - decode literal/length value from input stream - if value < 256 - copy value (literal byte) to output stream - otherwise - if value = end of block (256) - break from loop - otherwise (value = 257..285) - decode distance from input stream - - move backwards distance bytes in the output - stream, and copy length bytes from this - position to the output stream. - end loop - while not last block - - Note that a duplicated string reference may refer to a string - in a previous block; i.e., the backward distance may cross one - or more block boundaries. However a distance cannot refer past - the beginning of the output stream. (An application using a - preset dictionary might discard part of the output stream; a - distance can refer to that part of the output stream anyway) - Note also that the referenced string may overlap the current - position; for example, if the last 2 bytes decoded have values - X and Y, a string reference with - adds X,Y,X,Y,X to the output stream. - - We now specify each compression method in turn. - */ + /// + /// Constructor + /// + public MSZIPDeflateBlock(ulong header) + { + BFINAL = (header & 0b100) != 0; + BTYPE = (MSZIPDeflateCompressionType)(header & 0b011); + } } - public class MSZIPNonCompressedBlock + /// + /// Empty interface defining block types + /// + public interface IMSZIPBlockData { } + + /// + /// Non-compressed blocks (BTYPE=00) + /// + public class MSZIPNonCompressedBlock : IMSZIPBlockData { - /* - 3.2.4. Non-compressed blocks (BTYPE=00) + #region Properties - Any bits of input up to the next byte boundary are ignored. - The rest of the block consists of the following information: + /// + /// The number of data bytes in the block + /// + /// Bytes 0-1 + public ushort LEN { get; set; } - 0 1 2 3 4... - +---+---+---+---+================================+ - | LEN | NLEN |... LEN bytes of literal data...| - +---+---+---+---+================================+ + /// + /// The one's complement of LEN + /// + /// Bytes 2-3 + public ushort NLEN { get; set; } - LEN is the number of data bytes in the block. NLEN is the - one's complement of LEN. - */ + /// + /// bytes of literal data + /// + public byte[] Data { get; set; } + + #endregion + + /// + /// Constructor + /// + public MSZIPNonCompressedBlock(byte[] header) + { + // If we have invalid header data + if (header == null || header.Length < 4) + throw new ArgumentException(); + + int offset = 0; + LEN = header.ReadUInt16(ref offset); + NLEN = header.ReadUInt16(ref offset); + + // TODO: Confirm NLEN is 1's compliment of LEN + } } - public class MSZIPFixedHuffmanCompressedBlock + /// + /// Base class for compressed blocks + /// + public abstract class MSZIPCompressedBlock : IMSZIPBlockData { - /* - 3.2.6. Compression with fixed Huffman codes (BTYPE = 01) + /// + /// Huffman code lengths for the literal / length alphabet + /// + public abstract int[] LiteralLengths { get; } - The Huffman codes for the two alphabets are fixed, and are not - represented explicitly in the data.The Huffman code lengths - for the literal / length alphabet are: - - Lit Value Bits Codes - -------- - --------- - 0 - 143 8 00110000 through - 10111111 - 144 - 255 9 110010000 through - 111111111 - 256 - 279 7 0000000 through - 0010111 - 280 - 287 8 11000000 through - 11000111 - */ + /// + /// Huffman distance codes for the literal / length alphabet + /// + public abstract int[] DistanceCodes { get; } } - public class MSZIPDynamicHuffmanCompressedBlock + /// + /// Compression with fixed Huffman codes (BTYPE=01) + /// + public class MSZIPFixedHuffmanCompressedBlock : MSZIPCompressedBlock { + #region Properties + + /// + /// Huffman code lengths for the literal / length alphabet + /// + public override int[] LiteralLengths + { + get + { + // If we have cached lengths, use those + if (_literalLengths != null) + return _literalLengths; + + // Otherwise, build it from scratch + _literalLengths = new int[288]; + + // Literal Value 0 - 143, 8 bits + for (int i = 0; i < 144; i++) + _literalLengths[i] = 8; + + // Literal Value 144 - 255, 9 bits + for (int i = 144; i < 256; i++) + _literalLengths[i] = 9; + + // Literal Value 256 - 279, 7 bits + for (int i = 256; i < 280; i++) + _literalLengths[i] = 7; + + // Literal Value 280 - 287, 8 bits + for (int i = 280; i < 288; i++) + _literalLengths[i] = 8; + + return _literalLengths; + } + } + + /// + /// Huffman distance codes for the literal / length alphabet + /// + public override int[] DistanceCodes + { + get + { + // If we have cached distances, use those + if (_distanceCodes != null) + return _distanceCodes; + + // Otherwise, build it from scratch + _distanceCodes = new int[32]; + + // Fixed length, 5 bits + for (int i = 0; i < 32; i++) + _distanceCodes[i] = 5; + + return _distanceCodes; + } + } + + #endregion + + #region Instance Variables + + /// + /// Huffman code lengths for the literal / length alphabet + /// + private int[] _literalLengths = null; + + /// + /// Huffman distance codes for the literal / length alphabet + /// + private int[] _distanceCodes = null; + + #endregion + } + + /// + /// Compression with dynamic Huffman codes (BTYPE=10) + /// + public class MSZIPDynamicHuffmanCompressedBlock : MSZIPCompressedBlock + { + /// + /// The Huffman codes for the literal/length code + /// + public override int[] LiteralLengths => new int[19]; + + /// + /// The Huffman codes for the distance code + /// + public override int[] DistanceCodes => new int[19]; + /* 3.2.7. Compression with dynamic Huffman codes (BTYPE=10)