diff --git a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/Block.cs b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/Block.cs index 603cc9af..e53b273f 100644 --- a/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/Block.cs +++ b/BurnOutSharp.Models/MicrosoftCabinet/MSZIP/Block.cs @@ -3,7 +3,7 @@ namespace BurnOutSharp.Models.MicrosoftCabinet.MSZIP /// /// Each MSZIP block MUST consist of a 2-byte MSZIP signature and one or more RFC 1951 blocks. The /// 2-byte MSZIP signature MUST consist of the bytes 0x43 and 0x4B. The MSZIP signature MUST be - /// the first 2 bytes in the MSZIP block.The MSZIP signature is shown in the following packet diagram. + /// the first 2 bytes in the MSZIP block. The MSZIP signature is shown in the following packet diagram. /// /// public class Block diff --git a/BurnOutSharp.Utilities/BitStream.cs b/BurnOutSharp.Utilities/BitStream.cs new file mode 100644 index 00000000..33e046c4 --- /dev/null +++ b/BurnOutSharp.Utilities/BitStream.cs @@ -0,0 +1,147 @@ +using System.Collections; +using System.IO; + +namespace BurnOutSharp.Utilities +{ + /// + /// Stream that allows reading bits or groups of bits at a time + /// + public class BitStream + { + /// + /// Underlying stream to read from + /// + private readonly Stream _stream; + + /// + /// Bit array representing the current byte in the stream + /// + private BitArray _bitBuffer; + + /// + /// Next bit position to read from in the buffer + /// + private int _bitPosition; + + public BitStream(byte[] data) + { + _stream = new MemoryStream(data); + _bitBuffer = null; + _bitPosition = -1; + } + + public BitStream(Stream data) + { + _stream = data; + _bitBuffer = null; + _bitPosition = -1; + } + + /// + /// Read an array of bits from the input + /// + /// Number of bits to read + /// Array representing the read bits, null on error + public BitArray ReadBits(int bitCount) + { + // If we have an invalid bit count + if (bitCount <= 0) + return null; + + // If we have an invalid bit buffer + if (_bitBuffer == null || _bitPosition < 0 || _bitPosition >= 8) + RefreshBuffer(); + + // Create an array to hold the bits + BitArray bits = new BitArray(bitCount); + + // Loop through and populate the bits + for (int i = 0; i < bitCount; i++) + { + // Read the next bit from the buffer + bits[i] = _bitBuffer[_bitPosition++]; + + // Attempt to refresh the buffer if we need to + if (_bitPosition < 0 || _bitPosition > 7) + { + // If the refresh failed + if (!RefreshBuffer()) + break; + } + } + + // Return the bits + return bits; + } + + /// + /// Read an array of bytes from the input + /// + /// Number of bytes to read + /// Array representing the read bytes, null on error + public byte[] ReadBytes(int byteCount) + { + // If we have an invalid byte count + if (byteCount <= 0) + return null; + + // Get the corresponding bit array + BitArray bits = ReadBits(byteCount * 8); + if (bits == null) + return null; + + // Create the byte array + byte[] bytes = new byte[byteCount]; + + // Initialize the loop variables + byte byt = 0; int bitNumber = 0, byteNumber = 0; + + // Loop and build the byte array + for (int i = 0; i < bits.Length; i++) + { + // Add the new bit to the byte + byt <<= 1; + byt |= (byte)(bits[i] ? 1 : 0); + bitNumber++; + + // If we are at the end of a byte + if (bitNumber == 8) + { + bytes[byteNumber++] = byt; + bitNumber = 0; + } + } + + // Add the last byte + bytes[byteNumber] = byt; + + return bytes; + } + + /// + /// Discard the remaining bits in the buffer + /// + public void DiscardBuffer() => RefreshBuffer(); + + /// + /// Refresh the bit buffer from the data source + /// + /// True if the buffer refreshed, false otherwise + private bool RefreshBuffer() + { + // If we ran out of bytes + if (_stream.Position >= _stream.Length) + { + _bitBuffer = null; + _bitPosition = -1; + return false; + } + + // Read the next byte and reset + byte next = _stream.ReadByteValue(); + _bitBuffer = new BitArray(new byte[] { next }); + _bitPosition = 0; + return true; + } + } +} \ No newline at end of file diff --git a/BurnOutSharp.Utilities/Extensions.cs b/BurnOutSharp.Utilities/Extensions.cs index fd44a235..3a5ed2b0 100644 --- a/BurnOutSharp.Utilities/Extensions.cs +++ b/BurnOutSharp.Utilities/Extensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -29,6 +30,146 @@ namespace BurnOutSharp.Utilities #endregion + #region BitArray + + /// + /// Convert a bit array into a byte + /// + public static byte AsByte(this BitArray array) + { + byte value = 0; + + int maxValue = Math.Min(8, array.Length); + for (int i = 0; i < maxValue; i++) + { + value <<= 1; + value |= (byte)(array[i] ? 1 : 0); + } + + return value; + } + + /// + /// Convert a bit array into an sbyte + /// + public static sbyte AsSByte(this BitArray array) + { + sbyte value = 0; + + int maxValue = Math.Min(8, array.Length); + for (int i = 0; i < maxValue; i++) + { + value <<= 1; + value |= (sbyte)(array[i] ? 1 : 0); + } + + return value; + } + + /// + /// Convert a bit array into a short + /// + public static short AsInt16(this BitArray array) + { + short value = 0; + + int maxValue = Math.Min(16, array.Length); + for (int i = 0; i < maxValue; i++) + { + value <<= 1; + value |= (short)(array[i] ? 1 : 0); + } + + return value; + } + + /// + /// Convert a bit array into a ushort + /// + public static ushort AsUInt16(this BitArray array) + { + ushort value = 0; + + int maxValue = Math.Min(16, array.Length); + for (int i = 0; i < maxValue; i++) + { + value <<= 1; + value |= (ushort)(array[i] ? 1 : 0); + } + + return value; + } + + /// + /// Convert a bit array into an int + /// + public static int AsInt32(this BitArray array) + { + int value = 0; + + int maxValue = Math.Min(32, array.Length); + for (int i = 0; i < maxValue; i++) + { + value <<= 1; + value |= (int)(array[i] ? 1 : 0); + } + + return value; + } + + /// + /// Convert a bit array into a uint + /// + public static uint AsUInt32(this BitArray array) + { + uint value = 0; + + int maxValue = Math.Min(32, array.Length); + for (int i = 0; i < maxValue; i++) + { + value <<= 1; + value |= (uint)(array[i] ? 1 : 0); + } + + return value; + } + + /// + /// Convert a bit array into a long + /// + public static long AsInt64(this BitArray array) + { + long value = 0; + + int maxValue = Math.Min(64, array.Length); + for (int i = 0; i < maxValue; i++) + { + value <<= 1; + value |= (long)(array[i] ? 1 : 0); + } + + return value; + } + + /// + /// Convert a bit array into a ulong + /// + public static ulong AsUInt64(this BitArray array) + { + ulong value = 0; + + int maxValue = Math.Min(64, array.Length); + for (int i = 0; i < maxValue; i++) + { + value <<= 1; + value |= (ulong)(array[i] ? 1 : 0); + } + + return value; + } + + #endregion + #region Byte Array Reading /// diff --git a/BurnOutSharp/FileType/MicrosoftCAB.MSZIP.cs b/BurnOutSharp/FileType/MicrosoftCAB.MSZIP.cs index 3f0d769b..c42a4846 100644 --- a/BurnOutSharp/FileType/MicrosoftCAB.MSZIP.cs +++ b/BurnOutSharp/FileType/MicrosoftCAB.MSZIP.cs @@ -44,18 +44,18 @@ namespace BurnOutSharp.FileType public static class MSZIPDynamicHuffmanCompressedBlockBuilder { - public static DynamicHuffmanCompressedBlock Create(MSZIPDeflateStream stream) + public static DynamicHuffmanCompressedBlock Create(BitStream stream) { DynamicHuffmanCompressedBlock dynamicHuffmanCompressedBlock = new DynamicHuffmanCompressedBlock(); // # of Literal/Length codes - 257 - ulong HLIT = stream.ReadBitsLSB(5) + 257; + ulong HLIT = stream.ReadBits(5).AsUInt64() + 257; // # of Distance codes - 1 - ulong HDIST = stream.ReadBitsLSB(5) + 1; + ulong HDIST = stream.ReadBits(5).AsUInt64() + 1; // HCLEN, # of Code Length codes - 4 - ulong HCLEN = stream.ReadBitsLSB(5) + 4; + ulong HCLEN = stream.ReadBits(5).AsUInt64() + 4; // (HCLEN + 4) x 3 bits: code lengths for the code length // alphabet given just above @@ -66,7 +66,7 @@ namespace BurnOutSharp.FileType // length) is not used. int[] codeLengthAlphabet = new int[19]; for (ulong i = 0; i < HCLEN; i++) - codeLengthAlphabet[MSZIPDeflate.BitLengthOrder[i]] = (int)stream.ReadBitsLSB(3); + codeLengthAlphabet[MSZIPDeflate.BitLengthOrder[i]] = (int)stream.ReadBits(3).AsUInt64(); for (ulong i = HCLEN; i < 19; i++) codeLengthAlphabet[MSZIPDeflate.BitLengthOrder[i]] = 0; @@ -88,7 +88,7 @@ namespace BurnOutSharp.FileType /// /// The alphabet for code lengths is as follows /// - private static int[] BuildHuffmanTree(MSZIPDeflateStream stream, ulong codeCount, int[] codeLengths) + private static int[] BuildHuffmanTree(BitStream stream, ulong codeCount, int[] codeLengths) { // Setup the huffman tree int[] tree = new int[codeCount]; @@ -97,7 +97,7 @@ namespace BurnOutSharp.FileType int lastCode = 0, repeatLength = 0; for (ulong i = 0; i < codeCount; i++) { - int code = codeLengths[(int)stream.ReadBitsLSB(7)]; + int code = codeLengths[(int)stream.ReadBits(7).AsUInt64()]; // Represent code lengths of 0 - 15 if (code > 0 && code <= 15) @@ -111,7 +111,7 @@ namespace BurnOutSharp.FileType // Example: Codes 8, 16 (+2 bits 11), 16 (+2 bits 10) will expand to 12 code lengths of 8 (1 + 6 + 5) else if (code == 16) { - repeatLength = (int)stream.ReadBitsLSB(2); + repeatLength = (int)stream.ReadBits(2).AsUInt64(); repeatLength += 2; code = lastCode; } @@ -120,7 +120,7 @@ namespace BurnOutSharp.FileType // (3 bits of length) else if (code == 17) { - repeatLength = (int)stream.ReadBitsLSB(3); + repeatLength = (int)stream.ReadBits(3).AsUInt64(); repeatLength += 3; code = 0; } @@ -129,7 +129,7 @@ namespace BurnOutSharp.FileType // (7 bits of length) else if (code == 18) { - repeatLength = (int)stream.ReadBitsLSB(7); + repeatLength = (int)stream.ReadBits(7).AsUInt64(); repeatLength += 11; code = 0; } @@ -329,7 +329,7 @@ namespace BurnOutSharp.FileType /// /// The decoding algorithm for the actual data /// - public static void Decode(MSZIPDeflateStream data) + public static void Decode(BitStream data) { // Create the output byte array List decodedBytes = new List(); @@ -339,7 +339,7 @@ namespace BurnOutSharp.FileType do { - ulong header = data.ReadBitsLSB(3); + ulong header = data.ReadBits(3).AsUInt64(); block = MSZIPDeflateBlockBuilder.Create(header); // We should never get a reserved block @@ -350,15 +350,15 @@ namespace BurnOutSharp.FileType if (block.BTYPE == DeflateCompressionType.NoCompression) { // Skip any remaining bits in current partially processed byte - data.DiscardToByteBoundary(); + data.DiscardBuffer(); // Read LEN and NLEN - byte[] nonCompressedHeader = data.ReadBytesLSB(4); + byte[] nonCompressedHeader = data.ReadBytes(4); block.BlockData = MSZIPNonCompressedBlockBuilder.Create(nonCompressedHeader); // Copy LEN bytes of data to output ushort length = ((NonCompressedBlock)block.BlockData).LEN; - ((NonCompressedBlock)block.BlockData).Data = data.ReadBytesLSB(length); + ((NonCompressedBlock)block.BlockData).Data = data.ReadBytes(length); decodedBytes.AddRange(((NonCompressedBlock)block.BlockData).Data); } @@ -383,7 +383,7 @@ namespace BurnOutSharp.FileType while (true) { // Decode literal/length value from input stream - int symbol = literalDecodeTable[data.ReadBitsLSB(9)]; + int symbol = literalDecodeTable[data.ReadBits(9).AsUInt64()]; // Copy value (literal byte) to output stream if (symbol < 256) @@ -398,12 +398,12 @@ namespace BurnOutSharp.FileType else { // Decode distance from input stream - ulong length = data.ReadBitsLSB(LiteralExtraBits[symbol]); + ulong length = data.ReadBits(LiteralExtraBits[symbol]).AsUInt64(); length += (ulong)LiteralLengths[symbol]; int code = distanceDecodeTable[length]; - ulong distance = data.ReadBitsLSB(DistanceExtraBits[code]); + ulong distance = data.ReadBits(DistanceExtraBits[code]).AsUInt64(); distance += (ulong)DistanceOffsets[code]; @@ -535,398 +535,5 @@ namespace BurnOutSharp.FileType } } - /// - 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; - } - } - #endregion }