diff --git a/BitStream.cs b/BitStream.cs
new file mode 100644
index 0000000..5243acc
--- /dev/null
+++ b/BitStream.cs
@@ -0,0 +1,133 @@
+using System;
+using System.IO;
+using SabreTools.IO;
+
+namespace SabreTools.Compression
+{
+ ///
+ /// Wrapper to allow reading bits from a source stream
+ ///
+ public class BitStream
+ {
+ ///
+ /// Original stream source
+ ///
+ private Stream _source;
+
+ ///
+ /// Last read byte value from the stream
+ ///
+ private byte? _lastRead;
+
+ ///
+ /// 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.CanRead || !source.CanSeek)
+ throw new ArgumentException(nameof(source));
+
+ _source = source;
+ _lastRead = null;
+ _bitIndex = 0;
+ }
+
+ ///
+ /// Discard the current cached byte
+ ///
+ public void Discard()
+ {
+ _lastRead = 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 (_lastRead == null)
+ {
+ // Read the next byte, if possible
+ _lastRead = ReadSourceByte();
+ if (_lastRead == null)
+ return null;
+
+ // Reset the bit index
+ _bitIndex = 0;
+ }
+
+ // Get the value by bit-shifting
+ int value = (_lastRead.Value >> _bitIndex++) & 0x01;
+
+ // Reset the byte if we're at the end
+ if (_bitIndex >= 8)
+ Discard();
+
+ return (byte)value;
+ }
+
+ ///
+ /// Read a full byte, if possible
+ ///
+ /// The next byte, null on error or end of stream
+ public byte? ReadByte()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Read a full UInt16, if possible
+ ///
+ /// The next UInt16, null on error or end of stream
+ public byte? ReadUInt16()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Read a full UInt32, if possible
+ ///
+ /// The next UInt32, null on error or end of stream
+ public byte? ReadUInt32()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Read a full UInt64, if possible
+ ///
+ /// The next UInt64, null on error or end of stream
+ public byte? ReadUInt64()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// 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;
+ }
+ }
+ }
+}
\ No newline at end of file