using System; using System.IO; using SabreTools.IO; namespace SabreTools.Compression { /// /// Wrapper to allow reading bits from a source stream /// public class BitStream { /// public long Position => _source.Position; /// public long Length => _source.Length; /// /// Original stream source /// private Stream _source; /// /// Last read byte value from the stream /// private byte? _bitBuffer; /// /// Index in the byte of the current bit /// private int _bitIndex; /// /// Create a new BitStream from a source Stream /// public BitStream(Stream? source) { if (source == null || !source.CanRead || !source.CanSeek) throw new ArgumentException(nameof(source)); _source = source; _bitBuffer = null; _bitIndex = 0; } /// /// Discard the current cached byte /// public void Discard() { _bitBuffer = null; _bitIndex = 0; } /// /// Read a single bit, if possible /// /// The next bit encoded in a byte, null on error or end of stream public byte? ReadBit() { // If we reached the end of the stream if (_source.Position >= _source.Length) return null; // If we don't have a value cached if (_bitBuffer == null) { // Read the next byte, if possible _bitBuffer = ReadSourceByte(); if (_bitBuffer == null) return null; // Reset the bit index _bitIndex = 0; } // Get the value by bit-shifting int value = _bitBuffer.Value & 0x01; _bitBuffer = (byte?)(_bitBuffer >> 1); _bitIndex++; // Reset the byte if we're at the end if (_bitIndex >= 8) Discard(); return (byte)value; } /// /// Read a multiple bits in LSB, if possible /// /// The next bits encoded in a UInt32, null on error or end of stream public uint? ReadBitsLSB(int bits) { uint value = 0; for (int i = 0; i < bits; i++) { // Read the next bit byte? bitValue = ReadBit(); if (bitValue == null) return null; // Add the bit shifted by the current index value += (uint)(bitValue.Value << i); } return value; } /// /// Read a multiple bits in MSB, if possible /// /// The next bits encoded in a UInt32, null on error or end of stream public uint? ReadBitsMSB(int bits) { uint value = 0; for (int i = 0; i < bits; i++) { // Read the next bit byte? bitValue = ReadBit(); if (bitValue == null) return null; // Add the bit shifted by the current index value += (value << 1) + bitValue.Value; } return value; } /// /// Read a byte, if possible /// /// The next byte, null on error or end of stream /// Assumes the stream is byte-aligned public byte? ReadByte() { try { Discard(); return _source.ReadByteValue(); } catch { return null; } } /// /// Read a UInt16, if possible /// /// The next UInt16, null on error or end of stream /// Assumes the stream is byte-aligned public ushort? ReadUInt16() { try { Discard(); return _source.ReadUInt16(); } catch { return null; } } /// /// Read a UInt32, if possible /// /// The next UInt32, null on error or end of stream /// Assumes the stream is byte-aligned public uint? ReadUInt32() { try { Discard(); return _source.ReadUInt32(); } catch { return null; } } /// /// Read a UInt64, if possible /// /// The next UInt64, null on error or end of stream /// Assumes the stream is byte-aligned public ulong? ReadUInt64() { try { Discard(); return _source.ReadUInt64(); } catch { return null; } } /// /// Read bytes, if possible /// /// Number of bytes to read /// The next bytes, null on error or end of stream /// Assumes the stream is byte-aligned public byte[]? ReadBytes(int bytes) { try { Discard(); return _source.ReadBytes(bytes); } catch { return null; } } /// /// Read a single byte from the underlying stream, if possible /// /// The next full byte from the stream, null on error or end of stream private byte? ReadSourceByte() { try { return _source.ReadByteValue(); } catch { return null; } } } }