diff --git a/src/BitSet/BitInfo.cs b/src/BitSet/BitInfo.cs
new file mode 100644
index 0000000..5d6e8ca
--- /dev/null
+++ b/src/BitSet/BitInfo.cs
@@ -0,0 +1,29 @@
+using System.Runtime.CompilerServices;
+
+namespace BitSet
+{
+ public static class BitInfo
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static sbyte GetFirstBitIndex(ulong mask)
+ {
+ for (sbyte i = 0; i < 64; i++)
+ {
+ if ((mask & (1UL << i)) > 0)
+ return i;
+ }
+
+ return -1;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static sbyte GetFirstBitIndex(long mask)
+ {
+ return GetFirstBitIndex((ulong)mask);
+ }
+
+ public static ulong BitsToBytes(ulong bits)
+ {
+ return (bits + 7) >> 3;
+ }
+ }
+}
diff --git a/src/BitSet/BitSet.csproj b/src/BitSet/BitSet.csproj
new file mode 100644
index 0000000..1bd40e7
--- /dev/null
+++ b/src/BitSet/BitSet.csproj
@@ -0,0 +1,79 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {8F8A6380-5B10-4891-AC21-3D03494B3E44}
+ Library
+ Properties
+ BitSet
+ BitSet
+ v4.5.2
+ true
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ true
+ true
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/BitSet/BitStream.cs b/src/BitSet/BitStream.cs
new file mode 100644
index 0000000..1366198
--- /dev/null
+++ b/src/BitSet/BitStream.cs
@@ -0,0 +1,299 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BitSet
+{
+ [DebuggerDisplay("{ToString(),nq}")]
+ public class BitStream : ICloneable
+ {
+ public ulong Length
+ {
+ get { return m_MaxCursor - m_MinCursor; }
+ }
+
+ public ulong Cursor
+ {
+ get { return m_Cursor - m_MinCursor; }
+ set
+ {
+ ulong newCursor = m_MinCursor + value;
+ if (newCursor > m_MaxCursor)
+ throw new OverflowException(string.Format("Attempted seek beyond the bounds of this {0}", nameof(BitStream)));
+
+ m_Cursor = newCursor;
+ }
+ }
+
+ byte[] m_Data;
+ ulong m_Cursor;
+
+ ulong m_MinCursor;
+ ulong m_MaxCursor;
+
+ private IEnumerable> DebugBitsGrouped
+ {
+ get
+ {
+ ulong count = 0;
+ ulong total = 0;
+ bool old = false;
+ foreach (bool bit in DebugBits)
+ {
+ if (bit != old && count > 0)
+ {
+ yield return Tuple.Create(count, old, total);
+ count = 0;
+ }
+
+ old = bit;
+ count++;
+ total++;
+ }
+ }
+ }
+
+ private IEnumerable DebugBits
+ {
+ get
+ {
+ for (ulong i = m_Cursor; i < m_MaxCursor; i++)
+ {
+ ulong dummy = i;
+ yield return BitReader.ReadBool(m_Data, ref dummy);
+ }
+ }
+ }
+
+ private BitStream() { }
+
+ public BitStream(byte[] data, ulong? cursor = null, ulong minCursor = 0, ulong? maxCursor = null)
+ {
+ if (data == null)
+ throw new ArgumentNullException(nameof(data));
+
+ if (!maxCursor.HasValue)
+ maxCursor = (ulong)data.LongLength * 8;
+
+ if (minCursor > maxCursor.Value)
+ throw new ArgumentOutOfRangeException(nameof(minCursor), string.Format("{0} ({1}) greater than {2} ({3})",
+ nameof(minCursor), minCursor, nameof(maxCursor), maxCursor));
+
+ if (!cursor.HasValue)
+ cursor = minCursor;
+
+ if (cursor.Value < minCursor || cursor.Value > maxCursor.Value)
+ throw new ArgumentOutOfRangeException(nameof(cursor), string.Format("{0} ({1}) outside the range specified by {2} and {3} [{4}, {5}]", nameof(cursor), cursor, nameof(minCursor), nameof(maxCursor), minCursor, maxCursor));
+
+ m_Data = data;
+ m_Cursor = cursor.Value;
+ m_MinCursor = minCursor;
+ m_MaxCursor = maxCursor.Value;
+ }
+
+ public ulong ReadULong(byte bits = 64)
+ {
+ if (bits < 1 || bits > 64)
+ throw new ArgumentOutOfRangeException(nameof(bits));
+
+ ThrowIfOverflow(bits);
+
+ return BitReader.ReadUIntBits(m_Data, ref m_Cursor, bits);
+ }
+
+ public short ReadShort(byte bits = 16)
+ {
+ return (short)(ReadUShort(bits) << (32 - bits) >> (32 - bits));
+ }
+ public ushort ReadUShort(byte bits = 16)
+ {
+ if (bits < 1 || bits > 16)
+ throw new ArgumentOutOfRangeException(nameof(bits));
+
+ ThrowIfOverflow(bits);
+
+ return (ushort)BitReader.ReadUIntBits(m_Data, ref m_Cursor, bits);
+ }
+
+ public uint ReadUInt(byte bits = 32)
+ {
+ if (bits < 1 || bits > 32)
+ throw new ArgumentOutOfRangeException(nameof(bits));
+
+ ThrowIfOverflow(bits);
+
+ return (uint)BitReader.ReadUIntBits(m_Data, ref m_Cursor, bits);
+ }
+ public int ReadInt(byte bits = 32)
+ {
+ return unchecked((int)ReadUInt(bits)) << (32 - bits) >> (32 - bits);
+ }
+
+ public byte ReadByte(byte bits = 8)
+ {
+ if (bits < 1 || bits > 8)
+ throw new ArgumentOutOfRangeException(nameof(bits));
+
+ ThrowIfOverflow(bits);
+
+ return (byte)BitReader.ReadUIntBits(m_Data, ref m_Cursor, bits);
+ }
+
+ public byte[] ReadBytes(ulong bytes)
+ {
+ ThrowIfOverflow(bytes * 8);
+
+ byte[] retVal = new byte[bytes];
+
+ for (ulong i = 0; i < bytes; i++)
+ retVal[i] = ReadByte(8);
+
+ return retVal;
+ }
+
+ public float ReadSingle()
+ {
+ ThrowIfOverflow(32);
+
+ return BitReader.ReadSingle(m_Data, ref m_Cursor);
+ }
+
+ public bool PeekBool()
+ {
+ ThrowIfOverflow(1);
+
+ ulong dummy = m_Cursor;
+ return BitReader.ReadUIntBits(m_Data, ref dummy, 1) == 1;
+ }
+ public bool ReadBool()
+ {
+ ThrowIfOverflow(1);
+
+ return BitReader.ReadUIntBits(m_Data, ref m_Cursor, 1) == 1;
+ }
+
+ public string ReadCString()
+ {
+ ulong startCursor = m_Cursor;
+
+ string retVal = BitReader.ReadCString(m_Data, ref m_Cursor);
+
+ ulong endCursor = m_Cursor;
+ m_Cursor = startCursor;
+
+ ThrowIfOverflow(endCursor - startCursor);
+
+ m_Cursor = endCursor;
+
+ Debug.Assert(retVal != null);
+ return retVal;
+ }
+
+ public char ReadChar()
+ {
+ ThrowIfOverflow(8);
+
+ return Encoding.ASCII.GetChars(new byte[1] { ReadByte() }).Single();
+ }
+
+ public uint ReadVarUInt()
+ {
+ ulong startCursor = m_Cursor;
+
+ uint retVal = BitReader.ReadVarInt(m_Data, ref m_Cursor);
+
+ ulong endCursor = m_Cursor;
+ m_Cursor = startCursor;
+
+ ThrowIfOverflow(endCursor - startCursor);
+
+ m_Cursor = endCursor;
+
+ return retVal;
+ }
+
+ public int ReadVarInt()
+ {
+ var result = ReadVarUInt();
+ return (int)((result >> 1) ^ -(result & 1));
+ }
+
+ public bool CheckOverflow(ulong bits)
+ {
+ if ((m_Cursor + bits) > m_MaxCursor)
+ return true;
+ else
+ return false;
+ }
+
+ void ThrowIfOverflow(ulong bits)
+ {
+ if (CheckOverflow(bits))
+ throw new OverflowException(string.Format("Attempted seek beyond the bounds of this {0}", nameof(BitStream)));
+ }
+
+ public BitStream Subsection(ulong minCursor = 0, ulong? maxCursor = null)
+ {
+ if (!maxCursor.HasValue)
+ maxCursor = m_MaxCursor;
+
+ if (minCursor > maxCursor.Value)
+ throw new ArgumentOutOfRangeException(nameof(minCursor), string.Format("{0} ({1}) greater than {2} ({3})", nameof(minCursor), minCursor, nameof(maxCursor), maxCursor));
+
+ if (maxCursor.Value > m_MaxCursor)
+ throw new ArgumentOutOfRangeException(nameof(maxCursor), string.Format("{0} ({1}) greater than {2} ({3})", nameof(maxCursor), maxCursor, nameof(Length), Length));
+
+ BitStream retVal = new BitStream();
+ retVal.m_Data = m_Data;
+
+ retVal.m_MinCursor = m_MinCursor + minCursor;
+
+ if (maxCursor.HasValue)
+ retVal.m_MaxCursor = m_MinCursor + maxCursor.Value;
+ else
+ retVal.m_MaxCursor = m_MaxCursor;
+
+ retVal.m_Cursor = retVal.m_MinCursor;
+
+ return retVal;
+ }
+
+ public ulong Seek(ulong bits, SeekOrigin origin)
+ {
+ if (origin == SeekOrigin.Begin)
+ Cursor = bits;
+ else if (origin == SeekOrigin.Current)
+ Cursor += bits;
+ else if (origin == SeekOrigin.End)
+ Cursor = Length - bits;
+
+ return Length;
+ }
+ public ulong Seek(long bits, SeekOrigin origin)
+ {
+ if (origin == SeekOrigin.Begin)
+ Cursor = (ulong)bits;
+ else if (origin == SeekOrigin.Current)
+ Cursor = (ulong)((long)Cursor + bits);
+ else if (origin == SeekOrigin.End)
+ Cursor = (ulong)((long)Length - bits);
+
+ return Cursor;
+ }
+
+ public BitStream Clone()
+ {
+ return (BitStream)MemberwiseClone();
+ }
+ object ICloneable.Clone() { return Clone(); }
+
+ public override string ToString()
+ {
+ return string.Format("{0}: {1} / {2}", nameof(BitStream), Cursor, Length);
+ }
+ }
+}
diff --git a/src/BitSet/CString.cs b/src/BitSet/CString.cs
new file mode 100644
index 0000000..e9b8edd
--- /dev/null
+++ b/src/BitSet/CString.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ public static string ReadCString(byte[] buffer, ref ulong bitOffset)
+ {
+ StringBuilder builder = new StringBuilder();
+
+ char c;
+ while ((c = (char)ReadUIntBits(buffer, ref bitOffset, 8)) != '\0')
+ {
+ builder.Append(c);
+ }
+
+ return builder.ToString();
+ }
+ }
+}
diff --git a/src/BitSet/CopyBits.cs b/src/BitSet/CopyBits.cs
new file mode 100644
index 0000000..0ca8227
--- /dev/null
+++ b/src/BitSet/CopyBits.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
+ private static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
+
+ public static void CopyBits(byte[] src, ulong bitsToCopy, ref ulong readBitOffset, byte[] dest)
+ {
+ ulong dummy = 0;
+ CopyBits(src, bitsToCopy, ref readBitOffset, dest, ref dummy);
+ }
+
+ public static void CopyBits(byte[] src, ulong bitsToCopy, ref ulong readBitOffset,
+ byte[] dest, ref ulong writeBitOffset)
+ {
+ var copied = CopyBits(src, bitsToCopy, (byte)(readBitOffset % 8), readBitOffset / 8,
+ dest, (byte)(writeBitOffset % 8), writeBitOffset / 8);
+
+ readBitOffset += copied;
+ writeBitOffset += copied;
+ }
+
+ public static unsafe ulong CopyBits(byte[] src, ulong bitsToCopy, byte readBitOffset, ulong readByteOffset,
+ byte[] dest, byte writeBitOffset = 0, ulong writeByteOffset = 0)
+ {
+ if ((bitsToCopy % 8) == 0 && readBitOffset == 0 && writeBitOffset == 0)
+ {
+ // We're perfectly aligned, so we can just copy straight over.
+ Array.ConstrainedCopy(src, (int)readByteOffset, dest, (int)writeByteOffset, (int)(bitsToCopy / 8));
+ return bitsToCopy;
+ }
+
+ fixed (void* voidSrc = src, voidDst = dest)
+ {
+ var bitsToCopyOriginal = bitsToCopy;
+
+ while (bitsToCopy > 0)
+ {
+ ulong buffer = 0;
+ byte bytesToRead = (byte)Math.Min(BitInfo.BitsToBytes(bitsToCopy + readBitOffset), 8);
+
+ CopyMemory(new IntPtr(&((byte*)&buffer)[0]), new IntPtr(&((byte*)voidSrc)[readByteOffset]), bytesToRead);
+ //memcpy(&((byte*)&buffer)[0], &((byte*)voidSrc)[readByteOffset], bytesToRead);
+
+ // Cut off any high bits we don't want
+ ulong readMask = (0xFFFFFFFFFFFFFFFF << readBitOffset) &
+ (0xFFFFFFFFFFFFFFFF >> (int)(64 - Math.Min(readBitOffset + bitsToCopy, 64)));
+
+ buffer &= readMask;
+
+ // Shift everything right so the first read bit is actually at bit 0
+ buffer = (buffer >> readBitOffset);
+
+ buffer = (buffer << writeBitOffset);
+
+ ulong writeMask =
+ (0xFFFFFFFFFFFFFFFF << writeBitOffset) &
+ (0xFFFFFFFFFFFFFFFF >> (int)(64 - Math.Min(writeBitOffset + bitsToCopy, 64)));
+
+ unchecked
+ {
+ if ((bitsToCopy + writeBitOffset) > 56) // >= 8 bytes available
+ {
+ // Write 8 bytes.
+
+ // Clear bits to 0 first
+ *(ulong*)(&(((byte*)voidDst)[writeByteOffset])) &= ~writeMask;
+
+ // Now write the data
+ *(ulong*)(&(((byte*)voidDst)[writeByteOffset])) |= (buffer & writeMask);
+ }
+ else if ((bitsToCopy + writeBitOffset) > 24) // >= 4 bytes available
+ {
+ // Write byte 1 through 4
+ *(uint*)(&(((byte*)voidDst)[writeByteOffset])) &= ~(uint)writeMask;
+ *(uint*)(&(((byte*)voidDst)[writeByteOffset])) |= (uint)(buffer & writeMask);
+
+ if ((bitsToCopy + writeBitOffset) > 40) // >= 6 bytes available
+ {
+ // Write byte 5 and 6
+ *(ushort*)(&(((byte*)voidDst)[writeByteOffset + 4])) &= (ushort)~(writeMask >> 4 * 8);
+ *(ushort*)(&(((byte*)voidDst)[writeByteOffset + 4])) |= (ushort)((buffer >> 4 * 8) & (writeMask >> 4 * 8));
+ }
+ else if ((bitsToCopy + writeBitOffset) > 32) // >= 5 bytes available
+ {
+ // Write byte 5
+ *(&(((byte*)voidDst)[writeByteOffset + 4])) &= (byte)~(writeMask >> 4 * 8);
+ *(&(((byte*)voidDst)[writeByteOffset + 4])) |= (byte)((buffer >> 4 * 8) & (writeMask >> 4 * 8));
+ }
+
+ if ((bitsToCopy + writeBitOffset) > 48) // >= 7 bytes available
+ {
+ // Write byte 7
+ *(&(((byte*)voidDst)[writeByteOffset + 6])) &= (byte)~(writeMask >> 6 * 8);
+ *(&(((byte*)voidDst)[writeByteOffset + 6])) |= (byte)((buffer >> 6 * 8) & (writeMask >> 6 * 8));
+ }
+ }
+ else if ((bitsToCopy + writeBitOffset) > 8) // >= 2 bytes available
+ {
+ // Write byte 1 and 2
+ *(ushort*)(&(((byte*)voidDst)[writeByteOffset])) &= (ushort)~writeMask;
+ *(ushort*)(&(((byte*)voidDst)[writeByteOffset])) |= (ushort)(buffer & writeMask);
+
+ if ((bitsToCopy + writeBitOffset) > 16) // >= 3 bytes available
+ {
+ // Write byte 3
+ *(&(((byte*)voidDst)[writeByteOffset + 2])) &= (byte)~(writeMask >> 2 * 8);
+ *(&(((byte*)voidDst)[writeByteOffset + 2])) |= (byte)((buffer >> 2 * 8) & (writeMask >> 2 * 8));
+ }
+ }
+ else // >= 1 byte available
+ {
+ // Write byte 1
+ *(&(((byte*)voidDst)[writeByteOffset])) &= (byte)~writeMask;
+ *(&(((byte*)voidDst)[writeByteOffset])) |= (byte)(buffer & writeMask);
+ }
+ }
+
+ byte bytesRead = Math.Min(bytesToRead, (byte)7);
+ bitsToCopy -= Math.Min(bitsToCopy, (ulong)(bytesRead * 8));
+ readByteOffset += bytesRead;
+ writeByteOffset += bytesRead;
+ }
+
+ return bitsToCopyOriginal;
+ }
+ }
+ }
+}
diff --git a/src/BitSet/Single.cs b/src/BitSet/Single.cs
new file mode 100644
index 0000000..1fec6f8
--- /dev/null
+++ b/src/BitSet/Single.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float ReadSingle(byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float ReadSingle(byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static float ReadSingle(byte* buffer, int startByte = 0)
+ {
+ return *(float*)(&buffer[startByte]);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static float ReadSingle(byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ public static partial class BitWriter
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteSingle(float value, byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteSingle(float value, byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteSingle(float value, byte* buffer, int startByte = 0)
+ {
+ *(float*)(&buffer[startByte]) = value;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteSingle(float value, byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteSingle(double value, byte[] buffer, int startByte = 0)
+ {
+ WriteSingle((float)value, buffer, startByte);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteSingle(double value, byte[] buffer, int startByte, byte bitOffset)
+ {
+ WriteSingle((float)value, buffer, startByte, bitOffset);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteSingle(double value, byte* buffer, int startByte = 0)
+ {
+ WriteSingle((float)value, buffer, startByte);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteSingle(double value, byte* buffer, int startByte, byte bitOffset)
+ {
+ WriteSingle((float)value, buffer, startByte, bitOffset);
+ }
+ }
+}
diff --git a/src/BitSet/UInt.cs b/src/BitSet/UInt.cs
new file mode 100644
index 0000000..67cce3a
--- /dev/null
+++ b/src/BitSet/UInt.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Linq;
+using System.Text;
+
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ public static ulong ReadUIntBits(byte[] buffer, ref ulong bitOffset, byte bits)
+ {
+#if false
+ switch (bits)
+ {
+ case 1: return ReadUInt1(buffer, ref bitOffset);
+ case 2: return ReadUInt2(buffer, ref bitOffset);
+ case 3: return ReadUInt3(buffer, ref bitOffset);
+ case 4: return ReadUInt4(buffer, ref bitOffset);
+ case 5: return ReadUInt5(buffer, ref bitOffset);
+ case 6: return ReadUInt6(buffer, ref bitOffset);
+
+ case 16: return ReadUInt16(buffer, ref bitOffset);
+
+ case 20: return ReadUInt20(buffer, ref bitOffset);
+
+ case 32: return ReadUInt32(buffer, ref bitOffset);
+
+ case 48: return ReadUInt48(buffer, ref bitOffset);
+
+ default: throw new NotImplementedException();
+ }
+#endif
+
+ byte[] temp = new byte[8];
+ CopyBits(buffer, bits, ref bitOffset, temp);
+ return BitConverter.ToUInt64(temp, 0);
+ }
+
+ public static float ReadSingle(byte[] buffer, ref ulong bitOffset)
+ {
+ return BitConverter.ToSingle(BitConverter.GetBytes(ReadUInt(buffer, ref bitOffset)), 0);
+ }
+
+ public static ulong ReadULong(byte[] buffer, ref ulong bitOffset)
+ {
+ return ReadUIntBits(buffer, ref bitOffset, sizeof(ulong) << 3);
+ }
+ public static uint ReadUInt(byte[] buffer, ref ulong bitOffset)
+ {
+ return (uint)ReadUIntBits(buffer, ref bitOffset, sizeof(uint) << 3);
+ }
+ public static int ReadInt(byte[] buffer, ref ulong bitOffset)
+ {
+ return unchecked((int)ReadUIntBits(buffer, ref bitOffset, sizeof(int) << 3));
+ }
+ public static ushort ReadUShort(byte[] buffer, ref ulong bitOffset)
+ {
+ return (ushort)ReadUIntBits(buffer, ref bitOffset, sizeof(ushort) << 3);
+ }
+ public static short ReadShort(byte[] buffer, ref ulong bitOffset)
+ {
+ return unchecked((short)ReadUIntBits(buffer, ref bitOffset, sizeof(short) << 3));
+ }
+ public static char ReadChar(byte[] buffer, ref ulong bitOffset)
+ {
+ return Encoding.ASCII.GetChars(new byte[] { ReadByte(buffer, ref bitOffset) }).Single();
+ }
+ public static byte ReadByte(byte[] buffer, ref ulong bitOffset)
+ {
+ return (byte)ReadUIntBits(buffer, ref bitOffset, sizeof(byte) << 3);
+ }
+ public static bool ReadBool(byte[] buffer, ref ulong bitOffset)
+ {
+ return ReadUIntBits(buffer, ref bitOffset, 1) != 0;
+ }
+ }
+}
diff --git a/src/BitSet/UInt1.cs b/src/BitSet/UInt1.cs
new file mode 100644
index 0000000..2116286
--- /dev/null
+++ b/src/BitSet/UInt1.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt1(byte[] buffer, ref ulong startBit)
+ {
+ var retVal = ReadUInt1(buffer, startBit);
+ startBit++;
+ return retVal;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt1(byte[] buffer, ulong startBit)
+ {
+ return ReadUInt1(buffer, (int)(startBit / 8), (byte)(startBit % 8));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt1(byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt1(byte[] buffer, int startByte, byte bitOffset)
+ {
+ if (bitOffset > 7)
+ throw new ArgumentOutOfRangeException(nameof(bitOffset));
+
+ return (byte)(((buffer[startByte] & (1 << bitOffset)) != 0) ? 1 : 0);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static byte ReadUInt1(byte* buffer, ulong startBit)
+ {
+ return ReadUInt1(buffer, (int)(startBit / 8), (byte)(startBit % 8));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static byte ReadUInt1(byte* buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static byte ReadUInt1(byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ public static partial class BitWriter
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt1(byte value, byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt1(byte value, byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt1(byte value, byte* buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt1(byte value, byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/BitSet/UInt10.cs b/src/BitSet/UInt10.cs
new file mode 100644
index 0000000..c5c08e3
--- /dev/null
+++ b/src/BitSet/UInt10.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ushort ReadUInt10(byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ushort ReadUInt10(byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static ushort ReadUInt10(byte* buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static ushort ReadUInt10(byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ public static partial class BitWriter
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt10(ushort value, byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt10(ushort value, byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt10(ushort value, byte* buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt10(ushort value, byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/BitSet/UInt16.cs b/src/BitSet/UInt16.cs
new file mode 100644
index 0000000..a2a8cc5
--- /dev/null
+++ b/src/BitSet/UInt16.cs
@@ -0,0 +1,99 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint ReadUInt16(byte[] buffer, ref ulong startBit)
+ {
+ var retVal = ReadUInt16(buffer, startBit);
+ startBit += 16;
+ return retVal;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint ReadUInt16(byte[] buffer, ulong startBit)
+ {
+ return ReadUInt16(buffer, (int)(startBit / 8), (byte)(startBit % 8));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ushort ReadUInt16(byte[] buffer, int startByte = 0)
+ {
+ return (ushort)(buffer[startByte] | (buffer[startByte + 1] << 8));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ushort ReadUInt16(byte[] buffer, int startByte, byte bitOffset)
+ {
+ switch (bitOffset)
+ {
+ case 0: return ReadUInt16(buffer, startByte);
+
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ return (ushort)((ReadUInt24(buffer, startByte) >> bitOffset) & 0xFFFF);
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(bitOffset));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ushort ReadUInt16(IReadOnlyList buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ushort ReadUInt16(IReadOnlyList buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static ushort ReadUInt16(byte* buffer, int startByte = 0)
+ {
+ return *(ushort*)(&buffer[startByte]);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static ushort ReadUInt16(byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ public static partial class BitWriter
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt16(ushort value, byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt16(ushort value, byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt16(ushort value, IList buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt16(ushort value, IList buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt16(ushort value, byte* buffer, int startByte = 0)
+ {
+ *(ushort*)(&buffer[startByte]) = value;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt16(ushort value, byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/BitSet/UInt2.cs b/src/BitSet/UInt2.cs
new file mode 100644
index 0000000..d8b1266
--- /dev/null
+++ b/src/BitSet/UInt2.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt2(byte[] buffer, ref ulong startBit)
+ {
+ var retVal = ReadUInt2(buffer, startBit);
+ startBit += 2;
+ return retVal;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt2(byte[] buffer, ulong startBit)
+ {
+ return ReadUInt2(buffer, (int)(startBit / 8), (byte)(startBit % 8));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt2(byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt2(byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static byte ReadUInt2(byte* buffer, int startByte = 0)
+ {
+ return (byte)(buffer[startByte] & 0x03);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static byte ReadUInt2(byte* buffer, int startByte, byte bitOffset)
+ {
+ switch (bitOffset)
+ {
+ case 0: return ReadUInt2(buffer, startByte);
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5: return (byte)((buffer[startByte] >> bitOffset) & 0x03);
+ case 6: return (byte)(buffer[startByte] >> bitOffset);
+
+ case 7:
+ throw new NotImplementedException();
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(bitOffset));
+ }
+ }
+ public static partial class BitWriter
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt2(byte value, byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt2(byte value, byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt2(byte value, byte* buffer, int startByte = 0)
+ {
+ buffer[startByte] = (byte)((buffer[startByte] & ~0x03) | (value & 0x03));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt2(byte value, byte* buffer, int startByte, byte bitOffset)
+ {
+ switch (bitOffset)
+ {
+ case 0: WriteUInt2(value, buffer, startByte); return;
+ case 1: buffer[startByte] = (byte)((buffer[startByte] & ~0x06) | ((value << 1) & 0x06)); return;
+ case 2: buffer[startByte] = (byte)((buffer[startByte] & ~0x0C) | ((value << 2) & 0x0C)); return;
+ case 3: buffer[startByte] = (byte)((buffer[startByte] & ~0x18) | ((value << 3) & 0x18)); return;
+ case 4: buffer[startByte] = (byte)((buffer[startByte] & ~0x30) | ((value << 4) & 0x30)); return;
+ case 5: buffer[startByte] = (byte)((buffer[startByte] & ~0x60) | ((value << 5) & 0x60)); return;
+ case 6: buffer[startByte] = (byte)((buffer[startByte] & ~0xC0) | (value << 6)); return;
+
+ case 7:
+ throw new NotImplementedException();
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(bitOffset));
+ }
+ }
+}
diff --git a/src/BitSet/UInt20.cs b/src/BitSet/UInt20.cs
new file mode 100644
index 0000000..e1ef27a
--- /dev/null
+++ b/src/BitSet/UInt20.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint ReadUInt20(byte[] buffer, ref ulong startBit)
+ {
+ var retVal = ReadUInt20(buffer, startBit);
+ startBit += 20;
+ return retVal;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint ReadUInt20(byte[] buffer, ulong startBit)
+ {
+ return ReadUInt20(buffer, (int)(startBit / 8), (byte)(startBit % 8));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint ReadUInt20(byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint ReadUInt20(byte[] buffer, int startByte, byte bitOffset)
+ {
+ switch (bitOffset)
+ {
+ case 0: return ReadUInt20(buffer, startByte);
+
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ return (ReadUInt24(buffer, startByte) >> bitOffset) & 0xFFFFF;
+
+ case 5:
+ case 6:
+ case 7:
+ return (ReadUInt32(buffer, startByte) >> bitOffset) & 0xFFFFF;
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(bitOffset));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static uint ReadUInt20(byte* buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static uint ReadUInt20(byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ public static partial class BitWriter
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt20(uint value, byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt20(uint value, byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt20(uint value, byte* buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt20(uint value, byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/BitSet/UInt24.cs b/src/BitSet/UInt24.cs
new file mode 100644
index 0000000..3b416bd
--- /dev/null
+++ b/src/BitSet/UInt24.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint ReadUInt24(byte[] buffer, ref ulong startBit)
+ {
+ var retVal = ReadUInt24(buffer, startBit);
+ startBit += 24;
+ return retVal;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint ReadUInt24(byte[] buffer, ulong startBit)
+ {
+ return ReadUInt24(buffer, (int)(startBit / 8), (byte)(startBit % 8));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint ReadUInt24(byte[] buffer, int startByte = 0)
+ {
+ return (uint)(ReadUInt16(buffer, startByte) | (ReadUInt8(buffer, startByte + 2) << 16));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint ReadUInt24(byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static uint ReadUInt24(byte* buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static uint ReadUInt24(byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ public static partial class BitWriter
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt24(uint value, byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt24(uint value, byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt24(uint value, byte* buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt24(uint value, byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/BitSet/UInt3.cs b/src/BitSet/UInt3.cs
new file mode 100644
index 0000000..d1e8ee8
--- /dev/null
+++ b/src/BitSet/UInt3.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt3(byte[] buffer, ref ulong startBit)
+ {
+ var retVal = ReadUInt3(buffer, startBit);
+ startBit += 3;
+ return retVal;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt3(byte[] buffer, ulong startBit)
+ {
+ return ReadUInt3(buffer, (int)(startBit / 8), (byte)(startBit % 8));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt3(byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt3(byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static byte ReadUInt3(byte* buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static byte ReadUInt3(byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ public static partial class BitWriter
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt3(byte value, byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt3(byte value, byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt3(byte value, byte* buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt3(byte value, byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/BitSet/UInt32.cs b/src/BitSet/UInt32.cs
new file mode 100644
index 0000000..ef894f8
--- /dev/null
+++ b/src/BitSet/UInt32.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint ReadUInt32(byte[] buffer, ref ulong startBit)
+ {
+ var retVal = ReadUInt32(buffer, startBit);
+ startBit += 32;
+ return retVal;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint ReadUInt32(byte[] buffer, ulong startBit)
+ {
+ return ReadUInt32(buffer, (int)(startBit / 8), (byte)(startBit % 8));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint ReadUInt32(byte[] buffer, int startByte = 0)
+ {
+ return BitConverter.ToUInt32(buffer, startByte);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint ReadUInt32(byte[] buffer, int startByte, byte bitOffset)
+ {
+ switch (bitOffset)
+ {
+ case 0: return ReadUInt32(buffer, startByte);
+
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ return (uint)((ReadUInt48(buffer, startByte) >> bitOffset) & 0xFFFFFFFF);
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(bitOffset));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static uint ReadUInt32(byte* buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static uint ReadUInt32(byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ public static partial class BitWriter
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt32(uint value, byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt32(uint value, byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt32(uint value, byte* buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt32(uint value, byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/BitSet/UInt4.cs b/src/BitSet/UInt4.cs
new file mode 100644
index 0000000..847f756
--- /dev/null
+++ b/src/BitSet/UInt4.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt4(byte[] buffer, ref ulong startBit)
+ {
+ var retVal = ReadUInt4(buffer, startBit);
+ startBit += 4;
+ return retVal;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt4(byte[] buffer, ulong startBit)
+ {
+ return ReadUInt4(buffer, (int)(startBit / 8), (byte)(startBit % 8));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt4(byte[] buffer, int startByte = 0)
+ {
+ return (byte)(buffer[startByte] & 0xF);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt4(byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static byte ReadUInt4(byte* buffer, int startByte = 0)
+ {
+ return (byte)(buffer[startByte] & 0xF);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static byte ReadUInt4(byte* buffer, int startByte, byte bitOffset)
+ {
+ switch (bitOffset)
+ {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ return (byte)((buffer[startByte] >> bitOffset) & 0xF);
+
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ return (byte)((*(ushort*)(&buffer[startByte]) >> bitOffset) & 0xF);
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(bitOffset));
+ }
+ }
+ public static partial class BitWriter
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt4(byte value, byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt4(byte value, byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt4(byte value, byte* buffer, int startByte = 0)
+ {
+ buffer[startByte] = (byte)((buffer[startByte] & ~0x0F) | (value & 0x0F));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt4(byte value, byte* buffer, int startByte, byte bitOffset)
+ {
+ switch (bitOffset)
+ {
+ case 0: WriteUInt4(value, buffer, startByte); return;
+
+ case 4: buffer[startByte] = (byte)((buffer[startByte] & 0x0F) | (value << 4)); return;
+
+ case 1:
+ case 2:
+ case 3:
+ case 5:
+ case 6:
+ case 7:
+ throw new NotImplementedException();
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(bitOffset));
+ }
+ }
+}
diff --git a/src/BitSet/UInt48.cs b/src/BitSet/UInt48.cs
new file mode 100644
index 0000000..39402c8
--- /dev/null
+++ b/src/BitSet/UInt48.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace BitSet
+{ public static partial class BitReader
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ulong ReadUInt48(byte[] buffer, ref ulong startBit)
+ {
+ var retVal = ReadUInt48(buffer, startBit);
+ startBit += 48;
+ return retVal;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ulong ReadUInt48(byte[] buffer, ulong startBit)
+ {
+ return ReadUInt48(buffer, (int)(startBit / 8), (byte)(startBit % 8));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ulong ReadUInt48(byte[] buffer, int startByte = 0)
+ {
+ return ReadUInt32(buffer, startByte) | ((ulong)ReadUInt8(buffer, startByte + 4) << 32);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ulong ReadUInt48(byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static ulong ReadUInt48(byte* buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static ulong ReadUInt48(byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ public static partial class BitWriter
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt48(ulong value, byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt48(ulong value, byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt48(ulong value, byte* buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt48(ulong value, byte* buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/BitSet/UInt5.cs b/src/BitSet/UInt5.cs
new file mode 100644
index 0000000..3f41173
--- /dev/null
+++ b/src/BitSet/UInt5.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt5(byte[] buffer, ref ulong startBit)
+ {
+ var retVal = ReadUInt5(buffer, startBit);
+ startBit += 5;
+ return retVal;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt5(byte[] buffer, ulong startBit)
+ {
+ return ReadUInt5(buffer, (int)(startBit / 8), (byte)(startBit % 8));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt5(byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt5(byte[] buffer, int startByte, byte bitOffset)
+ {
+ switch (bitOffset)
+ {
+ case 0: return ReadUInt5(buffer, startByte);
+
+ case 1:
+ case 2:
+ case 3:
+ return (byte)((buffer[startByte] >> bitOffset) & 0x1F);
+
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ return (byte)((ReadUInt16(buffer, startByte) >> bitOffset) & 0x1F);
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(bitOffset));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static byte ReadUInt5(byte* buffer, int startByte = 0)
+ {
+ return (byte)(buffer[startByte] & 0x1F);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static byte ReadUInt5(byte* buffer, int startByte, byte bitOffset)
+ {
+ switch (bitOffset)
+ {
+ case 0: return ReadUInt5(buffer, startByte);
+
+ case 2: return (byte)((buffer[startByte] & 0x7C) >> 2);
+ case 3: return (byte)((buffer[startByte] & 0xF8) >> 3);
+
+ case 5: return (byte)(((buffer[startByte + 1] & 0x03) << 3) | ((buffer[startByte] & 0xE0) >> 5));
+
+ case 1:
+
+ case 4:
+
+ case 6:
+ case 7:
+ throw new NotImplementedException();
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(bitOffset));
+ }
+ }
+ public static partial class BitWriter
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt5(byte value, byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt5(byte value, byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt5(byte value, byte* buffer, int startByte = 0)
+ {
+ buffer[startByte] = (byte)((buffer[startByte] & ~0x1F) | (value & 0x1F));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt5(byte value, byte* buffer, int startByte, byte bitOffset)
+ {
+ switch (bitOffset)
+ {
+ case 0: WriteUInt5(value, buffer, startByte); return;
+
+ case 2: buffer[startByte] = (byte)((buffer[startByte] & ~0x7C) | ((value << 2) & 0x7C)); return;
+ case 3: buffer[startByte] = (byte)((buffer[startByte] & ~0xF8) | ((value << 3) & 0xF8)); return;
+
+ case 5:
+ {
+ buffer[startByte + 1] = (byte)((buffer[startByte + 1] & ~0x03) | ((value >> 3) & 0x03));
+ buffer[startByte] = (byte)((buffer[startByte] & ~0xE0) | ((value << 5) & 0xE0));
+ Debug.Assert(((*(ushort*)(&buffer[startByte]) & 0x3E0) >> 5) == (value & 0x1F));
+ return;
+ }
+
+ case 1:
+
+ case 4:
+
+ case 6:
+ case 7:
+ throw new NotImplementedException();
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(bitOffset));
+ }
+ }
+}
diff --git a/src/BitSet/UInt6.cs b/src/BitSet/UInt6.cs
new file mode 100644
index 0000000..3fdb51f
--- /dev/null
+++ b/src/BitSet/UInt6.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt6(byte[] buffer, ref ulong startBit)
+ {
+ var retVal = ReadUInt6(buffer, startBit);
+ startBit += 6;
+ return retVal;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt6(byte[] buffer, ulong startBit)
+ {
+ return ReadUInt6(buffer, (int)(startBit / 8), (byte)(startBit % 8));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt6(byte[] buffer, int startByte = 0)
+ {
+ return (byte)(buffer[startByte] & 0x3F);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt6(byte[] buffer, int startByte, byte bitOffset)
+ {
+ switch (bitOffset)
+ {
+ case 0: return (byte)(buffer[startByte] & 0x3F);
+ case 1: return (byte)((buffer[startByte] & 0x7E) >> 1);
+ case 2: return (byte)((buffer[startByte] & 0xFC) >> 2);
+
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ return (byte)((ReadUInt16(buffer, startByte) >> bitOffset) & 0x3F);
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(bitOffset));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static byte ReadUInt6(byte* buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static byte ReadUInt6(byte* buffer, int startByte, byte bitOffset)
+ {
+ switch (bitOffset)
+ {
+ case 5: return (byte)(((buffer[startByte + 1] & 0x07) << 3) | ((buffer[startByte] & 0xE0) >> 5));
+
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 6:
+ case 7:
+ throw new NotImplementedException();
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(bitOffset));
+ }
+ }
+ public static partial class BitWriter
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt6(byte value, byte[] buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt6(byte value, byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt6(byte value, byte* buffer, int startByte = 0)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt6(byte value, byte* buffer, int startByte, byte bitOffset)
+ {
+ switch (bitOffset)
+ {
+ case 5:
+ {
+ buffer[startByte + 1] = (byte)((buffer[startByte + 1] & ~0x07) | ((value >> 3) & 0x07));
+ buffer[startByte] = (byte)((buffer[startByte] & ~0xE0) | ((value << 5) & 0xE0 ));
+ Debug.Assert(((*(ushort*)(&buffer[startByte]) & 0x7E0) >> 5) == (value & 0x3F));
+ return;
+ }
+
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 6:
+ case 7:
+ throw new NotImplementedException();
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(bitOffset));
+ }
+ }
+}
diff --git a/src/BitSet/UInt8.cs b/src/BitSet/UInt8.cs
new file mode 100644
index 0000000..b125316
--- /dev/null
+++ b/src/BitSet/UInt8.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt8(byte[] buffer, ref ulong startBit)
+ {
+ var retVal = ReadUInt8(buffer, startBit);
+ startBit += 8;
+ return retVal;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt8(byte[] buffer, ulong startBit)
+ {
+ return ReadUInt8(buffer, (int)(startBit / 8), (byte)(startBit % 8));
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt8(byte[] buffer, int startByte = 0)
+ {
+ return buffer[startByte];
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte ReadUInt8(byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static byte ReadUInt8(byte* buffer, int startByte = 0)
+ {
+ return buffer[startByte];
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static byte ReadUInt8(byte* buffer, int startByte, byte bitOffset)
+ {
+ switch (bitOffset)
+ {
+ case 0: return ReadUInt8(buffer, startByte);
+
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ throw new NotImplementedException();
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(bitOffset));
+ }
+ }
+ public static partial class BitWriter
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt8(byte value, byte[] buffer, int startByte = 0)
+ {
+ buffer[startByte] = value;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteUInt8(byte value, byte[] buffer, int startByte, byte bitOffset)
+ {
+ throw new NotImplementedException();
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt8(byte value, byte* buffer, int startByte = 0)
+ {
+ buffer[startByte] = value;
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static void WriteUInt8(byte value, byte* buffer, int startByte, byte bitOffset)
+ {
+ switch (bitOffset)
+ {
+ case 0: WriteUInt8(value, buffer, startByte); return;
+
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ throw new NotImplementedException();
+ }
+
+ throw new ArgumentOutOfRangeException(nameof(bitOffset));
+ }
+ }
+}
diff --git a/src/BitSet/VarInt.cs b/src/BitSet/VarInt.cs
new file mode 100644
index 0000000..0546fbb
--- /dev/null
+++ b/src/BitSet/VarInt.cs
@@ -0,0 +1,23 @@
+namespace BitSet
+{
+ public static partial class BitReader
+ {
+ const int MAX_VARINT_BITS = 35;
+
+ public static uint ReadVarInt(byte[] buffer, ref ulong bitOffset)
+ {
+ uint dest = 0;
+
+ for (byte run = 0; run < 35; run += 7)
+ {
+ byte oneByte = (byte)ReadUIntBits(buffer, ref bitOffset, 8);
+ dest |= ((oneByte & (uint)0x7F) << run);
+
+ if ((oneByte >> 7) == 0)
+ break;
+ }
+
+ return dest;
+ }
+ }
+}
diff --git a/src/DemoCommands.cs b/src/DemoCommands.cs
new file mode 100644
index 0000000..fe6b00c
--- /dev/null
+++ b/src/DemoCommands.cs
@@ -0,0 +1,299 @@
+using System.Diagnostics;
+using System.Text;
+using BitSet;
+using DemoParser;
+using TF2Net;
+using TF2Net.Data;
+using TF2Net.NetMessages;
+
+namespace DemoLib.Commands
+{
+ public enum DemoCommandType : byte {
+
+ dem_invalid = 0,
+
+ // it's a startup message, process as fast as possible
+ dem_signon = 1,
+ // it's a normal network packet that we stored off
+ dem_packet,
+ // sync client clock to demo tick
+ dem_synctick,
+ // console command
+ dem_consolecmd,
+ // user input command
+ dem_usercmd,
+ // network data tables
+ dem_datatables,
+ // end of time.
+ dem_stop,
+
+ dem_stringtables,
+
+ // Last command -- not necessary in C#
+ //dem_lastcmd = dem_stringtables
+ }
+
+ public class DemoCommand
+ {
+ public DemoCommandType Type { get; protected set; } = DemoCommandType.dem_invalid;
+ }
+
+ class TimestampedDemoCommand : DemoCommand
+ {
+ public int Tick { get; set; }
+
+ public TimestampedDemoCommand(Stream input)
+ {
+ using (BinaryReader reader = new BinaryReader(input, Encoding.Default, true))
+ {
+ Tick = reader.ReadInt32();
+ }
+ }
+ }
+
+ sealed class DemoConsoleCommand : TimestampedDemoCommand
+ {
+ public string Command { get; set; }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private string DebuggerDisplayAttributeValue
+ {
+ get { return Command.Replace('"', '\''); }
+ }
+
+ public DemoConsoleCommand(Stream input) : base(input)
+ {
+ Type = DemoCommandType.dem_consolecmd;
+
+ using (BinaryReader reader = new BinaryReader(input, Encoding.ASCII, true))
+ Command = new string(reader.ReadChars(reader.ReadInt32())).TrimEnd('\0');
+ }
+ }
+
+ class DemoDataTablesCommand : TimestampedDemoCommand {
+ const int PROPINFOBITS_NUMPROPS = 10;
+ const int PROPINFOBITS_TYPE = 5;
+ const int PROPINFOBITS_FLAGS = SPROP_NUMFLAGBITS_NETWORKED;
+ const int PROPINFOBITS_NUMELEMENTS = 10;
+ const int PROPINFOBITS_NUMBITS = 7;
+
+ const int SPROP_NUMFLAGBITS_NETWORKED = 16;
+
+ public IList SendTables { get; set; } = new List();
+ public IList ServerClasses { get; set; } = new List();
+
+ public DemoDataTablesCommand(Stream input) : base(input)
+ {
+ Type = DemoCommandType.dem_datatables;
+
+ BitStream stream;
+ using (BinaryReader reader = new BinaryReader(input, Encoding.ASCII, true))
+ {
+ int length = reader.ReadInt32();
+ stream = new BitStream(reader.ReadBytes(length));
+ }
+
+ while (stream.ReadBool())
+ SendTables.Add(ParseSendTable(stream));
+
+ // Link referenced datatables
+ foreach (SendTable table in SendTables)
+ {
+ foreach (SendPropDefinition dtProp in table.Properties)
+ {
+ if (dtProp.Type == SendPropType.Datatable)
+ {
+ dtProp.Table = SendTables.Single(t => t.NetTableName == dtProp.ExcludeName);
+ dtProp.ExcludeName = null;
+ }
+ }
+ }
+
+ short serverClasses = stream.ReadShort();
+ Debug.Assert(serverClasses > 0);
+
+ ServerClasses = new List(serverClasses);
+
+ for (int i = 0; i < serverClasses; i++)
+ {
+ short classID = stream.ReadShort();
+ if (classID >= serverClasses)
+ throw new Exception("Demo parsing failed: Invalid server class ID");
+
+ ServerClass sc = new ServerClass();
+ sc.Classname = stream.ReadCString();
+ sc.DatatableName = stream.ReadCString();
+ ServerClasses.Add(sc);
+ }
+
+ Debug.Assert((stream.Length - stream.Cursor) < 8);
+ }
+
+ static SendTable ParseSendTable(BitStream stream)
+ {
+ SendTable table = new SendTable();
+
+ table.Unknown1 = stream.ReadBool();
+
+ table.NetTableName = stream.ReadCString();
+
+ int propertyCount = (int)stream.ReadULong(PROPINFOBITS_NUMPROPS);
+
+ SendPropDefinition arrayElementProp = null;
+
+ for (int i = 0; i < propertyCount; i++)
+ {
+ SendPropDefinition prop = new SendPropDefinition(table);
+
+ prop.Type = (SendPropType)stream.ReadULong(PROPINFOBITS_TYPE);
+ Debug.Assert(Enum.GetValues(typeof(SendPropType)).Cast().Contains(prop.Type));
+
+ Debug.Assert(prop.Type == SendPropType.Datatable ? prop.Flags == 0 : true);
+
+ prop.Name = stream.ReadCString();
+
+ prop.Flags = (SendPropFlags)stream.ReadULong(PROPINFOBITS_FLAGS);
+
+ if (prop.Type == SendPropType.Datatable)
+ {
+ prop.ExcludeName = stream.ReadCString();
+ }
+ else
+ {
+ if ((prop.Flags & SendPropFlags.Exclude) != 0)
+ prop.ExcludeName = stream.ReadCString();
+ else if (prop.Type == SendPropType.Array)
+ prop.ArrayElements = (int)stream.ReadULong(PROPINFOBITS_NUMELEMENTS);
+ else
+ {
+ prop.LowValue = stream.ReadSingle();
+ prop.HighValue = stream.ReadSingle();
+
+ prop.BitCount = stream.ReadULong(PROPINFOBITS_NUMBITS);
+ }
+ }
+
+ if (prop.Flags.HasFlag(SendPropFlags.NoScale))
+ {
+ if (prop.Type == SendPropType.Float)
+ prop.BitCount = 32;
+ else if (prop.Type == SendPropType.Vector)
+ {
+ if (!prop.Flags.HasFlag(SendPropFlags.Normal))
+ prop.BitCount = 32 * 3;
+ }
+ }
+
+ if (arrayElementProp != null)
+ {
+ Debug.Assert(prop.Type == SendPropType.Array);
+ prop.ArrayProperty = arrayElementProp;
+ arrayElementProp = null;
+ }
+
+ if (prop.Flags.HasFlag(SendPropFlags.InsideArray))
+ {
+ Debug.Assert(arrayElementProp == null);
+ Debug.Assert(!prop.Flags.HasFlag(SendPropFlags.ChangesOften));
+ arrayElementProp = prop;
+ }
+ else
+ {
+ table.Properties.Add(prop);
+ }
+ }
+
+ return table;
+ }
+ }
+
+ sealed class DemoUserCommand : TimestampedDemoCommand
+ {
+ public int OutgoingSequence { get; set; }
+
+ public byte[] Data { get; set; }
+
+ public DemoUserCommand(Stream input) : base(input)
+ {
+ Type = DemoCommandType.dem_usercmd;
+
+ using (BinaryReader reader = new BinaryReader(input, Encoding.ASCII, true))
+ {
+ OutgoingSequence = reader.ReadInt32();
+
+ Data = reader.ReadBytes(reader.ReadInt32());
+ }
+ }
+ }
+
+ sealed class DemoSyncTickCommand : TimestampedDemoCommand
+ {
+ public DemoSyncTickCommand(Stream input) : base(input)
+ {
+ Type = DemoCommandType.dem_synctick;
+ }
+ }
+
+ sealed class DemoStringTablesCommand : TimestampedDemoCommand
+ {
+ public BitStream Data { get; set; }
+
+ public DemoStringTablesCommand(Stream input) : base(input)
+ {
+ Type = DemoCommandType.dem_stringtables;
+
+ using (BinaryReader reader = new BinaryReader(input, Encoding.ASCII, true))
+ {
+ int dataLength = reader.ReadInt32();
+ Data = new BitStream(reader.ReadBytes(dataLength));
+ }
+ }
+ }
+
+ sealed class DemoSignonCommand : DemoPacketCommand
+ {
+ public DemoSignonCommand(Stream input, ulong signonLength) : base(input)
+ {
+ Type = DemoCommandType.dem_signon;
+ }
+ }
+
+ class DemoPacketCommand : TimestampedDemoCommand
+ {
+ public DemoViewpoint Viewpoint { get; set; }
+
+ public int SequenceIn { get; set; }
+ public int SequenceOut { get; set; }
+
+ public IList Messages { get; set; }
+
+ public DemoPacketCommand(Stream input) : base(input)
+ {
+ Type = DemoCommandType.dem_packet;
+
+ using (BinaryReader r = new BinaryReader(input, Encoding.ASCII, true))
+ {
+ Viewpoint = new DemoViewpoint();
+
+ Viewpoint.ViewpointFlags = (DemoViewpoint.Flags)r.ReadInt32();
+
+ Viewpoint.ViewOrigin1 = new Vector(r.ReadSingle(), r.ReadSingle(), r.ReadSingle());
+ Viewpoint.ViewAngles1 = new Vector(r.ReadSingle(), r.ReadSingle(), r.ReadSingle());
+ Viewpoint.LocalViewAngles1 = new Vector(r.ReadSingle(), r.ReadSingle(), r.ReadSingle());
+
+ Viewpoint.ViewOrigin2 = new Vector(r.ReadSingle(), r.ReadSingle(), r.ReadSingle());
+ Viewpoint.ViewAngles2 = new Vector(r.ReadSingle(), r.ReadSingle(), r.ReadSingle());
+ Viewpoint.LocalViewAngles2 = new Vector(r.ReadSingle(), r.ReadSingle(), r.ReadSingle());
+
+ SequenceIn = r.ReadInt32();
+ SequenceOut = r.ReadInt32();
+
+ BitStream data = new BitStream(r.ReadBytes((int)r.ReadUInt32()));
+ Messages = NetMessageCoder.Decode(data).ToArray();
+ }
+ }
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/src/Program.cs b/src/Program.cs
index 5d3e1a1..02d385d 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -1,6 +1,9 @@
// See https://aka.ms/new-console-template for more information
using System.Text;
+using DemoLib.Commands;
using DemoParser;
+using TF2Net;
+using TF2Net.Data;
Console.WriteLine("Hello, World!");
//TODO: Config
@@ -11,8 +14,8 @@ var demoFilePaths = Directory.GetFiles(_DemoPath, "*.dem");
foreach(var currentDemoPath in demoFilePaths){
var demo = File.OpenRead(Path.Combine(_DemoPath, currentDemoPath));
- var info = DemoParsing.ReadDemoHeader(demo);
- Console.WriteLine($"{currentDemoPath}: Header parsed ");
+ var demoInfo = new DemoReader(demo);
+ Console.WriteLine("Successfully read demo!");
}
@@ -22,35 +25,77 @@ foreach(var currentDemoPath in demoFilePaths){
namespace DemoParser{
- public record demoHeader(
- int demoProtocol,
- int networkProtocol,
- string ServerName,
- string ClientName,
- string MapName,
- string GameDirectory,
- float playbackTime,
- int Ticks,
- int Frames,
- int SignOnLength
- );
-
- public class DemoParsing{
- public static demoHeader ReadDemoHeader(FileStream stream){
- // 8 chars
- // int
- // int
- // 260 chars
- // 260 chars
- // 260 chars
- // 260 chars
- //float
- //int
- //int
- //int
-
- //Read the header.
+ class DemoViewpoint {
+ [Flags]
+ public enum Flags
+ {
+ None = 0,
+ UseOrigin2 = (1 << 0),
+ UseAngles2 = (1 << 1),
+ NoInterp = (1 << 2),
+ }
+
+ public Flags ViewpointFlags { get; set; }
+
+ public Vector ViewOrigin1 { get; set; }
+ public Vector ViewAngles1 { get; set; }
+ public Vector LocalViewAngles1 { get; set; }
+
+ public Vector ViewOrigin2 { get; set; }
+ public Vector ViewAngles2 { get; set; }
+ public Vector LocalViewAngles2 { get; set; }
+ }
+
+ public class DemoHeader {
+ public int demoProtocol {get; set;}
+ public int networkProtocol {get; set;}
+ public string ServerName {get; set;}
+ public string ClientName {get; set;}
+ public string MapName {get; set;}
+ public string GameDirectory {get; set;}
+ public float playbackTime {get; set;}
+ public int Ticks {get; set;}
+ public int Frames {get; set;}
+ public int SignOnLength {get; set;}
+
+ public DemoHeader(){}
+ public DemoHeader(Stream stream){
+ // Note; order is important.
+ int demoProtocol = DemoReader.readInt(stream);
+ int networkProtocol = DemoReader.readInt(stream);
+ string ServerName = DemoReader.readString(stream, 260, Encoding.ASCII);
+ string ClientNam = DemoReader.readString(stream, 260, Encoding.ASCII);
+ string MapName = DemoReader.readString(stream, 260, Encoding.ASCII);
+ string GameDirectory= DemoReader.readString(stream, 260, Encoding.ASCII);
+ float playbackTime = DemoReader.readFloat(stream);
+ int Ticks = DemoReader.readInt(stream);
+ int Frames = DemoReader.readInt(stream);
+ int SignOnLength = DemoReader.readInt(stream);
+ }
+ };
+
+ public class DemoReader {
+
+ public DemoHeader Header { get; private set; }
+
+ public IReadOnlyList Commands { get; private set; }
+ readonly WorldEvents m_Events = new WorldEvents();
+ public IWorldEvents Events { get { return m_Events; } }
+
+ public DemoReader(Stream input)
+ {
+ Header = new DemoHeader(input);
+
+ List commands = new List();
+ Commands = commands;
+
+ DemoCommand cmd = null;
+ while ((cmd = ParseCommand(input)) != null)
+ commands.Add(cmd);
+ }
+
+ public static DemoHeader ReadDemoHeader(Stream stream){
// If the first 8 chars are not "HL2DEMO" then it is not valid.
var buffer = new Byte[8];
@@ -60,36 +105,45 @@ namespace DemoParser{
throw new Exception($"DemoFile not valid! Filestamp: {Encoding.ASCII.GetString(buffer)}");
}
- return new demoHeader(
- readInt(stream),
- readInt(stream),
- readString(stream, 260, Encoding.ASCII),
- readString(stream, 260, Encoding.ASCII),
- readString(stream, 260, Encoding.ASCII),
- readString(stream, 260, Encoding.ASCII),
- readFloat(stream),
- readInt(stream),
- readInt(stream),
- readInt(stream)
- );
+ return new DemoHeader(stream);
}
- public static int readInt(FileStream stream, int offset = 0){
+
+ DemoCommand ParseCommand(Stream input){
+ DemoCommandType cmdType = (DemoCommandType)input.ReadByte();
+
+ switch (cmdType)
+ {
+ case DemoCommandType.dem_signon: return new DemoSignonCommand(input, (ulong)Header.SignOnLength);
+ case DemoCommandType.dem_packet: return new DemoPacketCommand(input);
+ case DemoCommandType.dem_synctick: return new DemoSyncTickCommand(input);
+ case DemoCommandType.dem_consolecmd: return new DemoConsoleCommand(input);
+ case DemoCommandType.dem_usercmd: return new DemoUserCommand(input);
+ case DemoCommandType.dem_datatables: return new DemoDataTablesCommand(input);
+ case DemoCommandType.dem_stop: return null;
+ case DemoCommandType.dem_stringtables: return new DemoStringTablesCommand(input);
+
+ default:
+ throw new NotImplementedException(string.Format("Unknown command type {0}", cmdType));
+ }
+ }
+
+ public static int readInt(Stream stream, int offset = 0){
var buffer = new Byte[4];
stream.Read(buffer, offset, 4);
return BitConverter.ToInt32(buffer,0);
}
- public static float readFloat(FileStream stream, int offset = 0){
+ public static float readFloat(Stream stream, int offset = 0){
var buffer = new Byte[4];
stream.Read(buffer, offset, 4);
return BitConverter.ToSingle(buffer,0);
}
//Reads until end of string
- public static string readString(FileStream stream, int maxLength, Encoding encoding,int offset = 0){
+ public static string readString(Stream stream, int maxLength, Encoding encoding,int offset = 0){
var buffer = new Byte[maxLength];
var readBytes = stream.Read(buffer, offset, maxLength);
@@ -101,12 +155,4 @@ namespace DemoParser{
}
-
- // static class helper{
-
- // public static string ReadCString(this BinaryReader reader, int length, Encoding encoding)
- // {
- // return encoding.GetString(reader.ReadBytes(length)).Split(new char[] { '\0' }, 2)[0];
- // }
- // }
}
\ No newline at end of file
diff --git a/src/TF2Net/ConditionalHashSet.cs b/src/TF2Net/ConditionalHashSet.cs
new file mode 100644
index 0000000..983444b
--- /dev/null
+++ b/src/TF2Net/ConditionalHashSet.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+
+namespace TF2Net
+{
+ sealed class ConditionalHashSet where T : class
+ {
+ private readonly object locker = new object();
+ private readonly List weakList = new List();
+ private readonly ConditionalWeakTable weakDictionary =
+ new ConditionalWeakTable();
+
+ public void Add(T item)
+ {
+ lock (locker)
+ {
+ var reference = new WeakReference(item);
+ weakDictionary.Add(item, reference);
+ weakList.Add(reference);
+ Shrink();
+ }
+ }
+
+ public void Remove(T item)
+ {
+ lock (locker)
+ {
+ WeakReference reference;
+
+ if (weakDictionary.TryGetValue(item, out reference))
+ {
+ reference.Target = null;
+ weakDictionary.Remove(item);
+ }
+ }
+ }
+
+ public T[] ToArray()
+ {
+ lock (locker)
+ {
+ return (
+ from weakReference in weakList
+ let item = (T)weakReference.Target
+ where item != null
+ select item)
+ .ToArray();
+ }
+ }
+
+ private void Shrink()
+ {
+ // This method prevents the List from growing indefinitely, but
+ // might also cause a performance problem in some cases.
+ if (weakList.Capacity == weakList.Count)
+ {
+ weakList.RemoveAll(weak => !weak.IsAlive);
+ }
+ }
+ }
+}
diff --git a/src/TF2Net/Data/BaselineIndex.cs b/src/TF2Net/Data/BaselineIndex.cs
new file mode 100644
index 0000000..d04ccab
--- /dev/null
+++ b/src/TF2Net/Data/BaselineIndex.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TF2Net.Data
+{
+ public enum BaselineIndex
+ {
+ Baseline0 = 0,
+ Baseline1 = 1,
+ }
+}
diff --git a/src/TF2Net/Data/Class.cs b/src/TF2Net/Data/Class.cs
new file mode 100644
index 0000000..694bd84
--- /dev/null
+++ b/src/TF2Net/Data/Class.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TF2Net.Data
+{
+ public enum Class
+ {
+ Undefined = 0,
+
+ Scout = 1,
+ Sniper = 2,
+ Soldier = 3,
+ Demo = 4,
+ Medic = 5,
+ Heavy = 6,
+ Pyro = 7,
+ Spy = 8,
+ Engie = 9,
+
+ Civilian = 10,
+ }
+}
diff --git a/src/TF2Net/Data/ClientFrame.cs b/src/TF2Net/Data/ClientFrame.cs
new file mode 100644
index 0000000..9d56a1a
--- /dev/null
+++ b/src/TF2Net/Data/ClientFrame.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TF2Net.Data
+{
+ [DebuggerDisplay("ClientFrame: tick {ServerTick,nq}")]
+ public class ClientFrame
+ {
+ public ClientFrame(ulong tick)
+ {
+ ServerTick = tick;
+ }
+
+ public int LastEntityIndex { get; set; }
+ public ulong ServerTick { get; }
+
+ public BitArray TransmitEntity { get; } = new BitArray(SourceConstants.MAX_EDICTS);
+ public BitArray FromBaseline { get; } = new BitArray(SourceConstants.MAX_EDICTS);
+ public BitArray TransmitAlways { get; } = new BitArray(SourceConstants.MAX_EDICTS);
+ }
+}
diff --git a/src/TF2Net/Data/ConnectionState.cs b/src/TF2Net/Data/ConnectionState.cs
new file mode 100644
index 0000000..91c626a
--- /dev/null
+++ b/src/TF2Net/Data/ConnectionState.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TF2Net.Data
+{
+ public enum ConnectionState : byte
+ {
+ ///
+ /// no state yet, about to connect
+ ///
+ None = 0,
+
+ ///
+ /// client challenging server, all OOB packets
+ ///
+ Challenge = 1,
+
+ ///
+ /// client is connected to server, netchans ready
+ ///
+ Connected = 2,
+
+ ///
+ /// just got serverinfo and string tables
+ ///
+ New = 3,
+
+ ///
+ /// received signon buffers
+ ///
+ Prespawn = 4,
+
+ ///
+ /// ready to receive entity packets
+ ///
+ Spawn = 5,
+
+ ///
+ /// we are fully connected, first non-delta packet received
+ ///
+ Full = 6,
+
+ ///
+ /// server is changing level, please wait
+ ///
+ Changelevel = 7,
+ }
+}
diff --git a/src/TF2Net/Data/EHandle.cs b/src/TF2Net/Data/EHandle.cs
new file mode 100644
index 0000000..6656c5d
--- /dev/null
+++ b/src/TF2Net/Data/EHandle.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TF2Net.Entities;
+
+namespace TF2Net.Data
+{
+ [DebuggerDisplay("{Entity,nq}")]
+ public class EHandle
+ {
+ public WorldState World { get; }
+ public uint EntityIndex { get; }
+ public uint SerialNumber { get; }
+
+ public Entity Entity
+ {
+ get
+ {
+ Entity potential = World.Entities[EntityIndex];
+ if (potential?.SerialNumber == SerialNumber)
+ return potential;
+
+ return null;
+ }
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ Entity DebugEntity { get { return Entity; } }
+
+ public EHandle(WorldState ws, uint handle)
+ {
+ World = ws;
+
+ EntityIndex = handle & ((1 << SourceConstants.MAX_EDICT_BITS) - 1);
+ SerialNumber = handle >> SourceConstants.MAX_EDICT_BITS;
+ }
+
+ public override string ToString()
+ {
+ return Entity?.ToString() ?? string.Format("null {0}", nameof(EHandle));
+ }
+ }
+}
diff --git a/src/TF2Net/Data/EntityData.cs b/src/TF2Net/Data/EntityData.cs
new file mode 100644
index 0000000..48b9439
--- /dev/null
+++ b/src/TF2Net/Data/EntityData.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TF2Net.Data
+{
+ public class EntityData
+ {
+ public uint Tick { get; }
+
+ public EntityData(uint tick)
+ {
+ Tick = tick;
+ }
+ }
+}
diff --git a/src/TF2Net/Data/EntityUpdateType.cs b/src/TF2Net/Data/EntityUpdateType.cs
new file mode 100644
index 0000000..2419611
--- /dev/null
+++ b/src/TF2Net/Data/EntityUpdateType.cs
@@ -0,0 +1,15 @@
+namespace DemoLib.DataExtraction
+{
+ public enum EntityUpdateType
+ {
+ EnterPVS = 0, // Entity came back into pvs, create new entity if one doesn't exist
+
+ LeavePVS, // Entity left pvs
+
+ DeltaEnt, // There is a delta for this entity.
+ PreserveEnt, // Entity stays alive but no delta ( could be LOD, or just unchanged )
+
+ Finished, // finished parsing entities successfully
+ Failed, // parsing error occured while reading entities
+ };
+}
diff --git a/src/TF2Net/Data/FlattenedProp.cs b/src/TF2Net/Data/FlattenedProp.cs
new file mode 100644
index 0000000..4f25e64
--- /dev/null
+++ b/src/TF2Net/Data/FlattenedProp.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TF2Net.Data
+{
+ [DebuggerDisplay("{ToString(),nq}")]
+ public class FlattenedProp
+ {
+ public string FullName { get; set; }
+
+ public SendPropDefinition Property { get; set; }
+
+ public override string ToString()
+ {
+ string bitCount = (Property.BitCount.HasValue && Property.BitCount.Value > 0) ? string.Format("[{0}]", Property.BitCount.Value) : string.Empty;
+
+ return string.Format("{0}{1} \"{2}\" ({3})", Property.Type, bitCount, FullName, Property.Flags);
+ }
+ }
+}
diff --git a/src/TF2Net/Data/GameEvent.cs b/src/TF2Net/Data/GameEvent.cs
new file mode 100644
index 0000000..4056557
--- /dev/null
+++ b/src/TF2Net/Data/GameEvent.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TF2Net.Data
+{
+ [DebuggerDisplay("Game event: {Declaration.Name}")]
+ public class GameEvent
+ {
+ public IReadOnlyGameEventDeclaration Declaration { get; set; }
+ public IDictionary Values { get; set; }
+ }
+}
diff --git a/src/TF2Net/Data/GameEventDeclaration.cs b/src/TF2Net/Data/GameEventDeclaration.cs
new file mode 100644
index 0000000..61486a4
--- /dev/null
+++ b/src/TF2Net/Data/GameEventDeclaration.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using TF2Net.Extensions;
+using TF2Net;
+
+namespace TF2Net.Data
+{
+ public enum GameEventDataType
+ {
+ Local = 0, // not networked
+ String, // zero terminated ASCII string
+ Float, // float 32 bit
+ Long, // signed int 32 bit
+ Short, // signed int 16 bit
+ Byte, // unsigned int 8 bit
+ Bool, // unsigned int 1 bit
+ };
+
+ [DebuggerDisplay("Game event declaration: {Name}")]
+ public class GameEventDeclaration : IReadOnlyGameEventDeclaration
+ {
+ public int ID { get; set; }
+ public string Name { get; set; }
+
+ public IDictionary Values { get; set; }
+ IReadOnlyDictionary IReadOnlyGameEventDeclaration.Values { get { return (IReadOnlyDictionary)Values; } }
+ }
+
+ public interface IReadOnlyGameEventDeclaration
+ {
+ int ID { get; }
+ string Name { get; }
+
+ IReadOnlyDictionary Values { get; }
+ }
+}
diff --git a/src/TF2Net/Data/IReadOnlyVector.cs b/src/TF2Net/Data/IReadOnlyVector.cs
new file mode 100644
index 0000000..ef619d0
--- /dev/null
+++ b/src/TF2Net/Data/IReadOnlyVector.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TF2Net.Data
+{
+ public interface IReadOnlyVector : ICloneable
+ {
+ double X { get; }
+ double Y { get; }
+ double Z { get; }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static class IReadOnlyVectorExtensions
+ {
+ public static Vector Clone(this IReadOnlyVector v)
+ {
+ return new Vector(v.X, v.Y, v.Z);
+ }
+ }
+}
diff --git a/src/TF2Net/Data/LifeState.cs b/src/TF2Net/Data/LifeState.cs
new file mode 100644
index 0000000..36384c1
--- /dev/null
+++ b/src/TF2Net/Data/LifeState.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TF2Net.Data
+{
+ public enum LifeState
+ {
+ Alive = 0,
+
+ ///
+ /// playing death animation or still falling off of a ledge waiting to hit ground
+ ///
+ Dying = 1,
+
+ ///
+ /// dead. lying still.
+ ///
+ Dead = 2,
+ Respawnable = 3,
+ DiscardBody = 4,
+ }
+}
diff --git a/src/TF2Net/Data/Player.cs b/src/TF2Net/Data/Player.cs
new file mode 100644
index 0000000..352ca86
--- /dev/null
+++ b/src/TF2Net/Data/Player.cs
@@ -0,0 +1,415 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using BitSet;
+using TF2Net.Entities;
+using TF2Net.Monitors;
+
+namespace TF2Net.Data
+{
+ [DebuggerDisplay("{ToString(),nq}")]
+ public class Player
+ {
+ public WorldState World { get; }
+
+ public uint EntityIndex { get; }
+ public Entity Entity { get { return World.Entities[EntityIndex]; } }
+ public bool InPVS { get { return Entity != null && Entity.InPVS; } }
+
+ public UserInfo Info { get; set; }
+
+ public IPlayerPropertyMonitor Position { get; }
+ public IPlayerPropertyMonitor Team { get; }
+ public IPlayerPropertyMonitor Class { get; }
+ public IPlayerPropertyMonitor IsDead { get; }
+ public IPlayerPropertyMonitor Health { get; }
+ public IPlayerPropertyMonitor MaxHealth { get; }
+ public IPlayerPropertyMonitor MaxBuffedHealth { get; }
+ public IPlayerPropertyMonitor Ping { get; }
+ public IPlayerPropertyMonitor Score { get; }
+ public IPlayerPropertyMonitor Deaths { get; }
+ public IPlayerPropertyMonitor Connected { get; }
+ public IPlayerPropertyMonitor PlayerState { get; }
+ public IPlayerPropertyMonitor Damage { get; }
+
+ public ImmutableArray> Weapons { get; }
+
+ SingleEvent> m_EnteredPVS { get; } = new SingleEvent>();
+ public event Action EnteredPVS
+ {
+ add
+ {
+ if (!m_EnteredPVS.Add(value))
+ return;
+
+ if (InPVS)
+ value?.Invoke(this);
+ }
+ remove { m_EnteredPVS.Remove(value); }
+ }
+
+ public SingleEvent> LeftPVS { get; } = new SingleEvent>();
+ public SingleEvent> PropertiesUpdated { get; } = new SingleEvent>();
+
+ public Player(UserInfo info, WorldState ws, uint entityIndex)
+ {
+ EntityIndex = entityIndex;
+ Info = info;
+ World = ws;
+
+ #region Property Monitors
+ Position = new PlayerPositionPropertyMonitor(this);
+
+ Team = new MultiPlayerPropertyMonitor(this,
+ new IPropertyMonitor[] {
+ new PlayerResourcePropertyMonitor("m_iTeam", this, o => (Team)Convert.ToInt32(o)),
+ new PlayerPropertyMonitor("DT_BaseEntity.m_iTeamNum", this, o => (Team)Convert.ToInt32(o))
+ });
+
+ IsDead = new MultiPlayerPropertyMonitor(this,
+ new IPropertyMonitor[] {
+ new PlayerResourcePropertyMonitor("m_bAlive", this, o => Convert.ToInt32(o) == 0),
+ new PlayerPropertyMonitor("DT_BasePlayer.m_lifeState", this, o => (LifeState)Convert.ToInt32(o) != LifeState.Alive)
+ });
+
+ Health = new MultiPlayerPropertyMonitor(this,
+ new IPropertyMonitor[] {
+ new PlayerResourcePropertyMonitor("m_iHealth", this, o => Convert.ToInt32(o)),
+ new PlayerPropertyMonitor("DT_BasePlayer.m_iHealth", this, o => Convert.ToInt32(o)),
+ });
+
+ Class = new MultiPlayerPropertyMonitor(this,
+ new IPropertyMonitor[] {
+ new PlayerResourcePropertyMonitor("m_iPlayerClass", this, o => (Class)Convert.ToInt32(o)),
+ new PlayerPropertyMonitor("DT_TFPlayerClassShared.m_iClass", this, o => (Class)Convert.ToInt32(o))
+ });
+
+ PlayerState = new PlayerPropertyMonitor("DT_TFPlayerShared.m_nPlayerState", this, o => (PlayerState)(uint)(o));
+ MaxHealth = new PlayerResourcePropertyMonitor("m_iMaxHealth", this, o => (uint)o);
+ MaxBuffedHealth = new PlayerResourcePropertyMonitor("m_iMaxBuffedHealth", this, o => (uint)o);
+ Ping = new PlayerResourcePropertyMonitor("m_iPing", this, o => (uint)o);
+ Score = new PlayerResourcePropertyMonitor("m_iScore", this, o => (int)o);
+ Deaths = new PlayerResourcePropertyMonitor("m_iDeaths", this, o => (int)o);
+ Connected = new PlayerResourcePropertyMonitor("m_bConnected", this, o => (uint)o != 0);
+ Damage = new PlayerResourcePropertyMonitor("m_iDamage", this, o => (uint)o);
+
+ // weapons
+ {
+ IPlayerPropertyMonitor[] array = new IPlayerPropertyMonitor[48];
+ for (int i = 0; i < 48; i++)
+ array[i] = new PlayerPropertyMonitor(string.Format("m_hMyWeapons.{0:D3}", i), this, o => new EHandle(ws, (uint)o));
+ Weapons = ImmutableArray.Create(array);
+ }
+ #endregion
+
+ World.Listeners.EntityEnteredPVS.Add(Listeners_EntityEnteredPVS);
+ World.Listeners.EntityLeftPVS.Add(Listeners_EntityLeftPVS);
+
+ if (InPVS)
+ Listeners_EntityEnteredPVS(Entity);
+ }
+
+ private void Listeners_EntityLeftPVS(Entity e)
+ {
+ Debug.Assert(e != null);
+ if (e != Entity)
+ return;
+ Debug.Assert(ReferenceEquals(e, Entity));
+
+ e.PropertiesUpdated.Remove(Entity_PropertiesUpdated);
+
+ LeftPVS.Invoke(this);
+ }
+
+ private void Listeners_EntityEnteredPVS(Entity e)
+ {
+ Debug.Assert(e != null);
+ if (e != Entity)
+ return;
+ Debug.Assert(ReferenceEquals(e, Entity));
+
+ e.PropertiesUpdated.Add(Entity_PropertiesUpdated);
+
+ m_EnteredPVS?.Invoke(this);
+ }
+
+ private void Entity_PropertiesUpdated(IPropertySet e)
+ {
+ Debug.Assert(ReferenceEquals(e, Entity));
+ PropertiesUpdated.Invoke(this);
+ }
+
+ public override string ToString()
+ {
+ return string.Format("\"{0}\": {1}", Info.Name, Info.GUID);
+ }
+
+ [DebuggerDisplay("{Value}")]
+ class PlayerPropertyMonitor : IPlayerPropertyMonitor
+ {
+ bool m_ValueChanged = false;
+ public T Value { get; private set; }
+ object IPropertyMonitor.Value { get { return Value; } }
+ public SendProp Property { get; private set; }
+
+ public string PropertyName { get; }
+ public Player Player { get; }
+ public Entity Entity { get { return Player.Entity; } }
+
+ object DebugValue
+ {
+ get
+ {
+ SendProp prop = Player.Entity.Properties.SingleOrDefault(p => p.Definition.FullName == PropertyName);
+
+ if (prop != null)
+ return Decoder(prop.Value);
+ else
+ return null;
+ }
+ }
+
+ Func