Compare commits

...

3 Commits

398
.gitignore vendored

@ -0,0 +1,398 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml

@ -26,4 +26,6 @@ Only intrested after the start of the official match. Warmup should not count.
Big up for (DemoInfo)[https://github.com/StatsHelix/demoinfo/tree/master/DemoInfo] for providing some inspiration
And thanks to the lads at (demostf)[https://github.com/demostf/parser] for TF2 specific code
And thanks to the lads at (demostf)[https://github.com/demostf/parser] for TF2 specific code
Another shoutout to (demolib)[https://github.com/PazerOP/DemoLib] for the big combo of C# code and tf2 info.

@ -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;
}
}
}

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{8F8A6380-5B10-4891-AC21-3D03494B3E44}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BitSet</RootNamespace>
<AssemblyName>BitSet</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="BitInfo.cs" />
<Compile Include="BitStream.cs" />
<Compile Include="CopyBits.cs" />
<Compile Include="CString.cs" />
<Compile Include="UInt.cs" />
<Compile Include="UInt1.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UInt16.cs" />
<Compile Include="UInt2.cs" />
<Compile Include="UInt20.cs" />
<Compile Include="UInt24.cs" />
<Compile Include="UInt32.cs" />
<Compile Include="UInt48.cs" />
<Compile Include="UInt6.cs" />
<Compile Include="UInt5.cs" />
<Compile Include="UInt4.cs" />
<Compile Include="UInt3.cs" />
<Compile Include="Single.cs" />
<Compile Include="UInt10.cs" />
<Compile Include="UInt8.cs" />
<Compile Include="VarInt.cs" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

@ -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<Tuple<ulong, bool, ulong>> 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<bool> 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);
}
}
}

@ -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();
}
}
}

@ -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;
}
}
}
}

@ -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);
}
}
}

@ -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;
}
}
}

@ -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();
}
}
}

@ -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();
}
}
}

@ -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<byte> buffer, int startByte = 0)
{
throw new NotImplementedException();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ReadUInt16(IReadOnlyList<byte> 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<byte> buffer, int startByte = 0)
{
throw new NotImplementedException();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteUInt16(ushort value, IList<byte> 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();
}
}
}

@ -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));
}
}
}

@ -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();
}
}
}

@ -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();
}
}
}

@ -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();
}
}
}

@ -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();
}
}
}

@ -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));
}
}
}

@ -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();
}
}
}

@ -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));
}
}
}

@ -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));
}
}
}

@ -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));
}
}
}

@ -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;
}
}
}

@ -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<SendTable> SendTables { get; set; } = new List<SendTable>();
public IList<ServerClass> ServerClasses { get; set; } = new List<ServerClass>();
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<ServerClass>(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<SendPropType>().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<INetMessage> 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();
}
}
}
}

@ -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<DemoCommand> 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<DemoCommand> commands = new List<DemoCommand>();
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];
// }
// }
}

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
namespace TF2Net
{
sealed class ConditionalHashSet<T> where T : class
{
private readonly object locker = new object();
private readonly List<WeakReference> weakList = new List<WeakReference>();
private readonly ConditionalWeakTable<T, WeakReference> weakDictionary =
new ConditionalWeakTable<T, WeakReference>();
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<T> from growing indefinitely, but
// might also cause a performance problem in some cases.
if (weakList.Capacity == weakList.Count)
{
weakList.RemoveAll(weak => !weak.IsAlive);
}
}
}
}

@ -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,
}
}

@ -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,
}
}

@ -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);
}
}

@ -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
{
/// <summary>
/// no state yet, about to connect
/// </summary>
None = 0,
/// <summary>
/// client challenging server, all OOB packets
/// </summary>
Challenge = 1,
/// <summary>
/// client is connected to server, netchans ready
/// </summary>
Connected = 2,
/// <summary>
/// just got serverinfo and string tables
/// </summary>
New = 3,
/// <summary>
/// received signon buffers
/// </summary>
Prespawn = 4,
/// <summary>
/// ready to receive entity packets
/// </summary>
Spawn = 5,
/// <summary>
/// we are fully connected, first non-delta packet received
/// </summary>
Full = 6,
/// <summary>
/// server is changing level, please wait
/// </summary>
Changelevel = 7,
}
}

@ -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));
}
}
}

@ -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;
}
}
}

@ -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
};
}

@ -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);
}
}
}

@ -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<string, object> Values { get; set; }
}
}

@ -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<string, GameEventDataType> Values { get; set; }
IReadOnlyDictionary<string, GameEventDataType> IReadOnlyGameEventDeclaration.Values { get { return (IReadOnlyDictionary<string, GameEventDataType>)Values; } }
}
public interface IReadOnlyGameEventDeclaration
{
int ID { get; }
string Name { get; }
IReadOnlyDictionary<string, GameEventDataType> Values { get; }
}
}

@ -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);
}
}
}

@ -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,
/// <summary>
/// playing death animation or still falling off of a ledge waiting to hit ground
/// </summary>
Dying = 1,
/// <summary>
/// dead. lying still.
/// </summary>
Dead = 2,
Respawnable = 3,
DiscardBody = 4,
}
}

@ -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<Vector> Position { get; }
public IPlayerPropertyMonitor<Team?> Team { get; }
public IPlayerPropertyMonitor<Class?> Class { get; }
public IPlayerPropertyMonitor<bool?> IsDead { get; }
public IPlayerPropertyMonitor<int?> Health { get; }
public IPlayerPropertyMonitor<uint?> MaxHealth { get; }
public IPlayerPropertyMonitor<uint?> MaxBuffedHealth { get; }
public IPlayerPropertyMonitor<uint?> Ping { get; }
public IPlayerPropertyMonitor<int?> Score { get; }
public IPlayerPropertyMonitor<int?> Deaths { get; }
public IPlayerPropertyMonitor<bool?> Connected { get; }
public IPlayerPropertyMonitor<PlayerState?> PlayerState { get; }
public IPlayerPropertyMonitor<uint?> Damage { get; }
public ImmutableArray<IPlayerPropertyMonitor<EHandle>> Weapons { get; }
SingleEvent<Action<Player>> m_EnteredPVS { get; } = new SingleEvent<Action<Player>>();
public event Action<Player> EnteredPVS
{
add
{
if (!m_EnteredPVS.Add(value))
return;
if (InPVS)
value?.Invoke(this);
}
remove { m_EnteredPVS.Remove(value); }
}
public SingleEvent<Action<Player>> LeftPVS { get; } = new SingleEvent<Action<Player>>();
public SingleEvent<Action<Player>> PropertiesUpdated { get; } = new SingleEvent<Action<Player>>();
public Player(UserInfo info, WorldState ws, uint entityIndex)
{
EntityIndex = entityIndex;
Info = info;
World = ws;
#region Property Monitors
Position = new PlayerPositionPropertyMonitor(this);
Team = new MultiPlayerPropertyMonitor<Team?>(this,
new IPropertyMonitor<Team?>[] {
new PlayerResourcePropertyMonitor<Team?>("m_iTeam", this, o => (Team)Convert.ToInt32(o)),
new PlayerPropertyMonitor<Team?>("DT_BaseEntity.m_iTeamNum", this, o => (Team)Convert.ToInt32(o))
});
IsDead = new MultiPlayerPropertyMonitor<bool?>(this,
new IPropertyMonitor<bool?>[] {
new PlayerResourcePropertyMonitor<bool?>("m_bAlive", this, o => Convert.ToInt32(o) == 0),
new PlayerPropertyMonitor<bool?>("DT_BasePlayer.m_lifeState", this, o => (LifeState)Convert.ToInt32(o) != LifeState.Alive)
});
Health = new MultiPlayerPropertyMonitor<int?>(this,
new IPropertyMonitor<int?>[] {
new PlayerResourcePropertyMonitor<int?>("m_iHealth", this, o => Convert.ToInt32(o)),
new PlayerPropertyMonitor<int?>("DT_BasePlayer.m_iHealth", this, o => Convert.ToInt32(o)),
});
Class = new MultiPlayerPropertyMonitor<Class?>(this,
new IPropertyMonitor<Class?>[] {
new PlayerResourcePropertyMonitor<Class?>("m_iPlayerClass", this, o => (Class)Convert.ToInt32(o)),
new PlayerPropertyMonitor<Class?>("DT_TFPlayerClassShared.m_iClass", this, o => (Class)Convert.ToInt32(o))
});
PlayerState = new PlayerPropertyMonitor<PlayerState?>("DT_TFPlayerShared.m_nPlayerState", this, o => (PlayerState)(uint)(o));
MaxHealth = new PlayerResourcePropertyMonitor<uint?>("m_iMaxHealth", this, o => (uint)o);
MaxBuffedHealth = new PlayerResourcePropertyMonitor<uint?>("m_iMaxBuffedHealth", this, o => (uint)o);
Ping = new PlayerResourcePropertyMonitor<uint?>("m_iPing", this, o => (uint)o);
Score = new PlayerResourcePropertyMonitor<int?>("m_iScore", this, o => (int)o);
Deaths = new PlayerResourcePropertyMonitor<int?>("m_iDeaths", this, o => (int)o);
Connected = new PlayerResourcePropertyMonitor<bool?>("m_bConnected", this, o => (uint)o != 0);
Damage = new PlayerResourcePropertyMonitor<uint?>("m_iDamage", this, o => (uint)o);
// weapons
{
IPlayerPropertyMonitor<EHandle>[] array = new IPlayerPropertyMonitor<EHandle>[48];
for (int i = 0; i < 48; i++)
array[i] = new PlayerPropertyMonitor<EHandle>(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<T> : IPlayerPropertyMonitor<T>
{
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<object, T> Decoder { get; }
SingleEvent<Action<IPropertyMonitor>> IPropertyMonitor.ValueChanged { get; } = new SingleEvent<Action<IPropertyMonitor>>();
SingleEvent<Action<IPropertyMonitor<T>>> IPropertyMonitor<T>.ValueChanged { get; } = new SingleEvent<Action<IPropertyMonitor<T>>>();
SingleEvent<Action<IEntityPropertyMonitor>> IEntityPropertyMonitor.ValueChanged { get; } = new SingleEvent<Action<IEntityPropertyMonitor>>();
SingleEvent<Action<IEntityPropertyMonitor<T>>> IEntityPropertyMonitor<T>.ValueChanged { get; } = new SingleEvent<Action<IEntityPropertyMonitor<T>>>();
SingleEvent<Action<IPlayerPropertyMonitor>> IPlayerPropertyMonitor.ValueChanged { get; } = new SingleEvent<Action<IPlayerPropertyMonitor>>();
public SingleEvent<Action<IPlayerPropertyMonitor<T>>> ValueChanged { get; } = new SingleEvent<Action<IPlayerPropertyMonitor<T>>>();
public PlayerPropertyMonitor(string propertyName, Player player, Func<object, T> decoder)
{
ValueChanged.Add((self) => ((IPropertyMonitor)self).ValueChanged.Invoke(self));
ValueChanged.Add((self) => ((IPropertyMonitor<T>)self).ValueChanged.Invoke(self));
ValueChanged.Add((self) => ((IEntityPropertyMonitor)self).ValueChanged.Invoke(self));
ValueChanged.Add((self) => ((IEntityPropertyMonitor<T>)self).ValueChanged.Invoke(self));
ValueChanged.Add((self) => ((IPlayerPropertyMonitor)self).ValueChanged.Invoke(self));
Player = player;
PropertyName = propertyName;
Decoder = decoder;
player.EnteredPVS += Player_EnteredPVS;
player.LeftPVS.Add(Player_LeftPVS);
player.PropertiesUpdated.Add(Player_PropertiesUpdated);
}
private void Player_PropertiesUpdated(Player p)
{
if (m_ValueChanged)
{
ValueChanged.Invoke(this);
m_ValueChanged = false;
}
}
private void Player_EnteredPVS(Player p)
{
Entity e = p.Entity;
e.PropertyAdded.Add(Entity_PropertyAdded);
foreach (SendProp prop in e.Properties)
Entity_PropertyAdded(prop);
}
private void Entity_PropertyAdded(SendProp prop)
{
if (prop.Definition.FullName == PropertyName)
{
Property = prop;
if (prop.ValueChanged.Add(Prop_ValueChanged))
{
// First add only
if (prop.Value != null)
Prop_ValueChanged(prop);
}
}
}
private void Prop_ValueChanged(SendProp prop)
{
Debug.Assert(ReferenceEquals(prop.Entity, Entity));
Debug.Assert((!Entity.InPVS && Property == null) || prop == Property);
var newValue = Decoder(prop.Value);
//Debug.Assert(Value?.Equals(newValue) != true);
Value = newValue;
m_ValueChanged = true;
}
private void Player_LeftPVS(Player p)
{
p.Entity.PropertyAdded.Remove(Entity_PropertyAdded);
Property = null;
}
}
[DebuggerDisplay("{Value}")]
class PlayerPositionPropertyMonitor : IPlayerPropertyMonitor<Vector>
{
readonly Vector m_Value = new Vector();
public Vector Value { get { return m_Value.Clone(); } }
object IPropertyMonitor.Value { get { return Value; } }
public SendProp Property { get { return null; } }
public Player Player { get; }
public Entity Entity { get { return Player.Entity; } }
public string PropertyName { get { return null; } }
IPlayerPropertyMonitor<Vector> LocalOriginXY { get; }
IPlayerPropertyMonitor<double> LocalOriginZ { get; }
IPlayerPropertyMonitor<Vector> NonLocalOriginXY { get; }
IPlayerPropertyMonitor<double> NonLocalOriginZ { get; }
SingleEvent<Action<IPropertyMonitor>> IPropertyMonitor.ValueChanged { get; } = new SingleEvent<Action<IPropertyMonitor>>();
SingleEvent<Action<IPropertyMonitor<Vector>>> IPropertyMonitor<Vector>.ValueChanged { get; } = new SingleEvent<Action<IPropertyMonitor<Vector>>>();
SingleEvent<Action<IEntityPropertyMonitor>> IEntityPropertyMonitor.ValueChanged { get; } = new SingleEvent<Action<IEntityPropertyMonitor>>();
SingleEvent<Action<IEntityPropertyMonitor<Vector>>> IEntityPropertyMonitor<Vector>.ValueChanged { get; } = new SingleEvent<Action<IEntityPropertyMonitor<Vector>>>();
SingleEvent<Action<IPlayerPropertyMonitor>> IPlayerPropertyMonitor.ValueChanged { get; } = new SingleEvent<Action<IPlayerPropertyMonitor>>();
public SingleEvent<Action<IPlayerPropertyMonitor<Vector>>> ValueChanged { get; } = new SingleEvent<Action<IPlayerPropertyMonitor<Vector>>>();
bool m_PositionChanged = false;
public PlayerPositionPropertyMonitor(Player player)
{
ValueChanged.Add(self => ((IPropertyMonitor)self).ValueChanged.Invoke(self));
ValueChanged.Add(self => ((IPropertyMonitor<Vector>)self).ValueChanged.Invoke(self));
ValueChanged.Add(self => ((IEntityPropertyMonitor)self).ValueChanged.Invoke(self));
ValueChanged.Add(self => ((IEntityPropertyMonitor<Vector>)self).ValueChanged.Invoke(self));
ValueChanged.Add(self => ((IPlayerPropertyMonitor)self).ValueChanged.Invoke(self));
Player = player;
LocalOriginXY = new PlayerPropertyMonitor<Vector>("DT_TFLocalPlayerExclusive.m_vecOrigin", Player, o => (Vector)o);
LocalOriginZ = new PlayerPropertyMonitor<double>("DT_TFLocalPlayerExclusive.m_vecOrigin[2]", Player, o => (double)o);
NonLocalOriginXY = new PlayerPropertyMonitor<Vector>("DT_TFNonLocalPlayerExclusive.m_vecOrigin", Player, o => (Vector)o);
NonLocalOriginZ = new PlayerPropertyMonitor<double>("DT_TFNonLocalPlayerExclusive.m_vecOrigin[2]", Player, o => (double)o);
LocalOriginXY.ValueChanged.Add(OriginXY_ValueChanged);
LocalOriginZ.ValueChanged.Add(OriginZ_ValueChanged);
NonLocalOriginXY.ValueChanged.Add(OriginXY_ValueChanged);
NonLocalOriginZ.ValueChanged.Add(OriginZ_ValueChanged);
Player.PropertiesUpdated.Add(Player_PropertiesUpdated);
}
private void OriginZ_ValueChanged(IPlayerPropertyMonitor<double> z)
{
m_Value.Z = z.Value;
m_PositionChanged = true;
}
private void OriginXY_ValueChanged(IPlayerPropertyMonitor<Vector> xy)
{
var value = xy.Value;
m_Value.X = value.X;
m_Value.Y = value.Y;
m_PositionChanged = true;
}
private void Player_PropertiesUpdated(Player p)
{
Debug.Assert(Player == p);
if (m_PositionChanged)
{
ValueChanged.Invoke(this);
m_PositionChanged = false;
}
}
}
[DebuggerDisplay("{Value}")]
class PlayerResourcePropertyMonitor<T> : IPlayerPropertyMonitor<T>
{
public Player Player { get; }
public Entity Entity { get { return Player.Entity; } }
public string PropertyName { get; }
public SendProp Property { get { return InternalPropertyMonitor.Property; } }
Func<object, T> Decoder { get; }
public T Value { get { return InternalPropertyMonitor.Value; } }
object IPropertyMonitor.Value { get { return Value; } }
Entity PlayerResourceEntity { get; }
EntityPropertyMonitor<T> InternalPropertyMonitor { get; }
SingleEvent<Action<IPropertyMonitor>> IPropertyMonitor.ValueChanged { get; } = new SingleEvent<Action<IPropertyMonitor>>();
SingleEvent<Action<IPropertyMonitor<T>>> IPropertyMonitor<T>.ValueChanged { get; } = new SingleEvent<Action<IPropertyMonitor<T>>>();
SingleEvent<Action<IEntityPropertyMonitor>> IEntityPropertyMonitor.ValueChanged { get; } = new SingleEvent<Action<IEntityPropertyMonitor>>();
SingleEvent<Action<IEntityPropertyMonitor<T>>> IEntityPropertyMonitor<T>.ValueChanged { get; } = new SingleEvent<Action<IEntityPropertyMonitor<T>>>();
SingleEvent<Action<IPlayerPropertyMonitor>> IPlayerPropertyMonitor.ValueChanged { get; } = new SingleEvent<Action<IPlayerPropertyMonitor>>();
public SingleEvent<Action<IPlayerPropertyMonitor<T>>> ValueChanged { get; } = new SingleEvent<Action<IPlayerPropertyMonitor<T>>>();
public PlayerResourcePropertyMonitor(string propertyName, Player player, Func<object, T> decoder)
{
ValueChanged.Add(self => ((IPropertyMonitor)self).ValueChanged.Invoke(self));
ValueChanged.Add(self => ((IPropertyMonitor<T>)self).ValueChanged.Invoke(self));
ValueChanged.Add(self => ((IEntityPropertyMonitor)self).ValueChanged.Invoke(self));
ValueChanged.Add(self => ((IEntityPropertyMonitor<T>)self).ValueChanged.Invoke(self));
ValueChanged.Add(self => ((IPlayerPropertyMonitor)self).ValueChanged.Invoke(self));
PropertyName = propertyName;
Player = player;
Decoder = decoder;
PlayerResourceEntity = player.World.Entities.Single(e => e?.Class.Classname == "CTFPlayerResource");
string specificProperty = string.Format("{0}.{1:D3}", PropertyName, Player.EntityIndex);
var props = PlayerResourceEntity.Properties.Select(prop => prop.Definition.FullName.Remove(prop.Definition.FullName.Length - 4))
.Except("m_iHealth")
.Except("m_iPing")
.Except("m_iScore")
.Except("m_iDeaths")
.Except("m_bConnected")
.Except("m_iTeam")
.Except("m_bAlive")
.Distinct();
InternalPropertyMonitor = new EntityPropertyMonitor<T>(specificProperty, PlayerResourceEntity, Decoder);
InternalPropertyMonitor.ValueChanged.Add(InternalValueChanged);
}
private void InternalValueChanged(IPropertyMonitor p)
{
Debug.Assert(InternalPropertyMonitor == p);
ValueChanged.Invoke(this);
}
}
[DebuggerDisplay("{Value}")]
class MultiPlayerPropertyMonitor<T> : MultiPropertyMonitor<T>, IPlayerPropertyMonitor<T>
{
public Player Player { get; }
public Entity Entity { get { return Player.Entity; } }
SingleEvent<Action<IEntityPropertyMonitor>> IEntityPropertyMonitor.ValueChanged { get; } = new SingleEvent<Action<IEntityPropertyMonitor>>();
SingleEvent<Action<IEntityPropertyMonitor<T>>> IEntityPropertyMonitor<T>.ValueChanged { get; } = new SingleEvent<Action<IEntityPropertyMonitor<T>>>();
SingleEvent<Action<IPlayerPropertyMonitor>> IPlayerPropertyMonitor.ValueChanged { get; } = new SingleEvent<Action<IPlayerPropertyMonitor>>();
public new SingleEvent<Action<IPlayerPropertyMonitor<T>>> ValueChanged { get; } = new SingleEvent<Action<IPlayerPropertyMonitor<T>>>();
public MultiPlayerPropertyMonitor(Player p, IEnumerable<IPropertyMonitor<T>> propertyMonitors) : base(propertyMonitors)
{
//ValueChanged.Add(self => ((IPropertyMonitor)self).ValueChanged.Invoke(self));
//ValueChanged.Add(self => ((IPropertyMonitor<T>)self).ValueChanged.Invoke(self));
//ValueChanged.Add(self => ((IEntityPropertyMonitor)self).ValueChanged.Invoke(self));
//ValueChanged.Add(self => ((IEntityPropertyMonitor<T>)self).ValueChanged.Invoke(self));
//ValueChanged.Add(self => ((IPlayerPropertyMonitor)self).ValueChanged.Invoke(self));
Player = p;
IPropertyMonitor<T> self = this;
self.ValueChanged.Add(s => ((IEntityPropertyMonitor)s).ValueChanged.Invoke(this));
self.ValueChanged.Add(s => ((IEntityPropertyMonitor<T>)s).ValueChanged.Invoke(this));
self.ValueChanged.Add(s => ((IPlayerPropertyMonitor)s).ValueChanged.Invoke(this));
self.ValueChanged.Add(s => ((IPlayerPropertyMonitor<T>)s).ValueChanged.Invoke(this));
}
}
}
}

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TF2Net.Data
{
public enum PlayerState
{
/// <summary>
/// Happily running around in the game.
/// </summary>
Active = 0,
/// <summary>
/// First entering the server (shows level intro screen).
/// </summary>
Welcome = 1,
/// <summary>
/// Game observer mode.
/// </summary>
Observer = 2,
/// <summary>
/// Player is dying.
/// </summary>
Dying = 3,
}
}

@ -0,0 +1,103 @@
using System;
using System.Diagnostics;
using System.Linq;
using TF2Net.Entities;
namespace TF2Net.Data
{
[DebuggerDisplay("{Definition,nq} :: {Value,nq}")]
public class SendProp : ICloneable, IDisposable
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
readonly IEntity m_Entity;
public IEntity Entity
{
get
{
CheckDisposed();
return m_Entity;
}
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
readonly SendPropDefinition m_Definition;
public SendPropDefinition Definition
{
get
{
CheckDisposed();
return m_Definition;
}
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
ulong m_LastChangedTick;
public ulong LastChangedTick
{
get
{
CheckDisposed();
return m_LastChangedTick;
}
}
public SingleEvent<Action<SendProp>> ValueChanged { get; } = new SingleEvent<Action<SendProp>>();
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
object m_Value;
public object Value
{
get
{
CheckDisposed();
return m_Value;
}
set
{
CheckDisposed();
if (value?.GetHashCode() != m_Value?.GetHashCode() || !value.Equals(m_Value))
{
Debug.Assert(value?.Equals(m_Value) != true);
m_Value = value;
m_LastChangedTick = Entity.World.Tick;
ValueChanged.Invoke(this);
}
}
}
public SendProp(IEntity e, SendPropDefinition definition)
{
m_Entity = e;
m_Definition = definition;
}
public SendProp Clone(IEntity forEnt)
{
CheckDisposed();
SendProp cloned = new SendProp(forEnt, Definition);
cloned.m_Value = Value;
cloned.m_LastChangedTick = LastChangedTick;
return cloned;
}
public SendProp Clone()
{
CheckDisposed();
return (SendProp)MemberwiseClone();
}
object ICloneable.Clone() { return Clone(); }
void CheckDisposed()
{
if (m_Disposed)
throw new ObjectDisposedException(nameof(SendProp));
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
bool m_Disposed = false;
public void Dispose()
{
CheckDisposed();
m_Disposed = true;
}
}
}

@ -0,0 +1,344 @@
using System;
using System.Diagnostics;
using System.Text;
using BitSet;
namespace TF2Net.Data
{
[DebuggerDisplay("{ToString(),nq}")]
public class SendPropDefinition : ICloneable
{
private SendPropDefinition() { }
public SendPropDefinition(SendTable parent)
{
Parent = parent;
}
public SendTable Parent { get; }
public SendPropType Type { get; set; }
public string Name { get; set; }
public SendPropFlags Flags { get; set; }
public string ExcludeName { get; set; }
public int? ArrayElements { get; set; }
public SendPropDefinition ArrayProperty { get; set; }
public double? LowValue { get; set; }
public double? HighValue { get; set; }
public ulong? BitCount { get; set; }
// If we're SendPropType.Datatable
public SendTable Table { get; set; }
public string FullName { get { return string.Format("{0}.{1}", Parent.NetTableName, Name); } }
public object Decode(BitStream stream)
{
switch (Type)
{
case SendPropType.Int: return ReadInt(stream);
case SendPropType.Vector: return ReadVector(stream);
case SendPropType.Float: return ReadFloat(stream);
case SendPropType.String: return ReadString(stream);
case SendPropType.Array: return ReadArray(stream);
case SendPropType.VectorXY: return ReadVectorXY(stream);
default:
throw new NotImplementedException();
}
}
object[] ReadArray(BitStream stream)
{
int maxElements = ArrayElements.Value;
byte numBits = 1;
while ((maxElements >>= 1) != 0)
numBits++;
uint elementCount = stream.ReadUInt(numBits);
object[] retVal = new object[elementCount];
for (int i = 0; i < elementCount; i++)
retVal[i] = ArrayProperty.Decode(stream);
return retVal;
}
string ReadString(BitStream stream)
{
ulong chars = stream.ReadULong(9);
byte[] raw = stream.ReadBytes(chars);
return Encoding.ASCII.GetString(raw);
}
object ReadInt(BitStream stream)
{
if (Flags.HasFlag(SendPropFlags.VarInt))
{
if (Flags.HasFlag(SendPropFlags.Unsigned))
return stream.ReadVarUInt();
else
return stream.ReadVarInt();
}
else
{
if (Flags.HasFlag(SendPropFlags.Unsigned))
return stream.ReadUInt((byte)BitCount.Value);
else
return stream.ReadInt((byte)BitCount.Value);
}
}
double ReadBitCoord(BitStream stream, bool isIntegral, bool isLowPrecision)
{
double value = 0;
bool isNegative = false;
bool inBounds = stream.ReadBool();
if (isIntegral)
{
bool hasIntVal = stream.ReadBool();
if (hasIntVal)
{
isNegative = stream.ReadBool();
if (inBounds)
value = stream.ReadULong(SourceConstants.COORD_INTEGER_BITS_MP) + 1;
else
{
value = stream.ReadULong(SourceConstants.COORD_INTEGER_BITS) + 1;
if (value < (1 << SourceConstants.COORD_INTEGER_BITS_MP))
throw new FormatException("Something's fishy...");
}
}
}
else
{
bool hasIntVal = stream.ReadBool();
isNegative = stream.ReadBool();
if (hasIntVal)
{
if (inBounds)
value = stream.ReadULong(SourceConstants.COORD_INTEGER_BITS_MP) + 1;
else
{
value = stream.ReadULong(SourceConstants.COORD_INTEGER_BITS) + 1;
if (value < (1 << SourceConstants.COORD_INTEGER_BITS_MP))
throw new FormatException("Something's fishy...");
}
}
var fractVal = stream.ReadULong(isLowPrecision ? (byte)SourceConstants.COORD_FRACTIONAL_BITS_MP_LOWPRECISION : (byte)SourceConstants.COORD_FRACTIONAL_BITS);
value = value + fractVal * (isLowPrecision ? SourceConstants.COORD_RESOLUTION_LOWPRECISION : SourceConstants.COORD_RESOLUTION);
}
if (isNegative)
value = -value;
return value;
}
bool ReadSpecialFloat(BitStream stream, out double retVal)
{
if (Flags.HasFlag(SendPropFlags.Coord))
{
throw new NotImplementedException();
//return true;
}
else if (Flags.HasFlag(SendPropFlags.CoordMP))
{
retVal = ReadBitCoord(stream, false, false);
return true;
}
else if (Flags.HasFlag(SendPropFlags.CoordMPLowPrecision))
{
retVal = ReadBitCoord(stream, false, true);
return true;
}
else if (Flags.HasFlag(SendPropFlags.CoordMPIntegral))
{
retVal = ReadBitCoord(stream, true, false);
return true;
}
else if (Flags.HasFlag(SendPropFlags.NoScale))
{
retVal = stream.ReadSingle();
return true;
}
else if (Flags.HasFlag(SendPropFlags.Normal))
{
throw new NotImplementedException();
//return true;
}
retVal = default(double);
return false;
}
double ReadFloat(BitStream stream)
{
double retVal;
if (ReadSpecialFloat(stream, out retVal))
return retVal;
ulong raw = stream.ReadULong((byte)BitCount.Value);
double percentage = (double)raw / ((1UL << (byte)BitCount.Value) - 1);
retVal = LowValue.Value + (HighValue.Value - LowValue.Value) * percentage;
return retVal;
}
Vector ReadVector(BitStream stream)
{
Vector retVal = new Vector();
retVal.X = ReadFloat(stream);
retVal.Y = ReadFloat(stream);
if (!Flags.HasFlag(SendPropFlags.Normal))
retVal.Z = ReadFloat(stream);
else
{
throw new NotImplementedException();
}
return retVal;
}
Vector ReadVectorXY(BitStream stream)
{
Vector retVal = new Vector();
retVal.X = ReadFloat(stream);
retVal.Y = ReadFloat(stream);
return retVal;
}
public override string ToString()
{
string bitCount = (BitCount.HasValue && BitCount.Value > 0) ? string.Format("[{0}]", BitCount.Value) : string.Empty;
return string.Format("{0}{1} \"{2}\" ({3})", Type, bitCount, FullName, Flags);
}
public SendPropDefinition Clone()
{
SendPropDefinition retVal = (SendPropDefinition)MemberwiseClone();
return retVal;
}
object ICloneable.Clone() { return Clone(); }
}
public enum SendPropType
{
Int = 0,
Float,
Vector,
VectorXY,
String,
Array,
Datatable,
//Quaternion,
//Int64,
}
[Flags]
public enum SendPropFlags
{
/// <summary>
/// Unsigned integer data.
/// </summary>
Unsigned = (1 << 0),
/// <summary>
/// If this is set, the float/vector is treated like a world coordinate.
/// Note that the bit count is ignored in this case.
/// </summary>
Coord = (1 << 1),
/// <summary>
/// For floating point, don't scale into range, just take value as is.
/// </summary>
NoScale = (1 << 2),
/// <summary>
/// For floating point, limit high value to range minus one bit unit
/// </summary>
RoundDown = (1 << 3),
/// <summary>
/// For floating point, limit low value to range minus one bit unit
/// </summary>
RoundUp = (1 << 4),
/// <summary>
/// If this is set, the vector is treated like a normal (only valid for vectors)
/// </summary>
Normal = (1 << 5),
/// <summary>
/// This is an exclude prop (not excludED, but it points at another prop to be excluded).
/// </summary>
Exclude = (1 << 6),
/// <summary>
/// Use XYZ/Exponent encoding for vectors.
/// </summary>
EncodeXYZE = (1 << 7),
/// <summary>
/// This tells us that the property is inside an array, so it shouldn't be put into the
/// flattened property list. Its array will point at it when it needs to.
/// </summary>
InsideArray = (1 << 8),
/// <summary>
/// Set for datatable props using one of the default datatable proxies like
/// SendProxy_DataTableToDataTable that always send the data to all clients.
/// </summary>
ProxyAlwaysYes = (1 << 9),
/// <summary>
/// this is an often changed field, moved to head of sendtable so it gets a small index
/// </summary>
ChangesOften = (1 << 10),
/// <summary>
/// Set automatically if SPROP_VECTORELEM is used.
/// </summary>
IsVectorElement = (1 << 11),
/// <summary>
/// Set automatically if it's a datatable with an offset of 0 that doesn't change the pointer
/// (ie: for all automatically-chained base classes).
/// In this case, it can get rid of this SendPropDataTable altogether and spare the
/// trouble of walking the hierarchy more than necessary.
/// </summary>
Collapsible = (1 << 12),
/// <summary>
/// Like SPROP_COORD, but special handling for multiplayer games
/// </summary>
CoordMP = (1 << 13),
/// <summary>
/// Like SPROP_COORD, but special handling for multiplayer games where the fractional component only gets a 3 bits instead of 5
/// </summary>
CoordMPLowPrecision = (1 << 14),
/// <summary>
/// SPROP_COORD_MP, but coordinates are rounded to integral boundaries
/// </summary>
CoordMPIntegral = (1 << 15),
/// <summary>
/// reuse existing flag so we don't break demo. note you want to include SPROP_UNSIGNED if needed, its more efficient
/// </summary>
VarInt = Normal,
}
}

@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
namespace TF2Net.Data
{
[DebuggerDisplay("SendTable {NetTableName}, {Properties.Count} SendProps")]
public class SendTable
{
public SendTable()
{
m_FlattenedProps = new Lazy<ImmutableArray<SendPropDefinition>>(
() => ImmutableArray.Create(SetupFlatPropertyArray().ToArray()));
}
/// <summary>
/// The name matched between client and server.
/// </summary>
public string NetTableName { get; set; }
public IList<SendPropDefinition> Properties { get; set; } = new List<SendPropDefinition>();
private Lazy<ImmutableArray<SendPropDefinition>> m_FlattenedProps;
public ImmutableArray<SendPropDefinition> FlattenedProps { get { return m_FlattenedProps.Value; } }
public bool Unknown1 { get; set; }
IEnumerable<SendPropDefinition> Excludes
{
get
{
foreach (SendPropDefinition prop in Properties)
{
if (prop.Flags.HasFlag(SendPropFlags.Exclude))
yield return prop;
else if (prop.Type == SendPropType.Datatable)
{
foreach (SendPropDefinition childExclude in prop.Table.Excludes)
yield return childExclude;
}
}
}
}
IEnumerable<FlattenedProp> Flatten(IEnumerable<SendPropDefinition> excludes)
{
var datatablesFirst = Properties.OrderByDescending(p => p.Type,
Comparer<SendPropType>.Create((p1, p2) =>
{
bool isDT1 = p1 == SendPropType.Datatable;
bool isDT2 = p2 == SendPropType.Datatable;
if (isDT1 == isDT2)
return 0;
if (isDT1)
return 1;
else if (isDT2)
return -1;
throw new InvalidOperationException();
}));
foreach (SendPropDefinition prop in datatablesFirst)
{
if (excludes.Any(e => e.Name == prop.Name && e.ExcludeName == prop.Parent.NetTableName))
continue;
if (excludes.Contains(prop))
continue;
Debug.Assert(!prop.Flags.HasFlag(SendPropFlags.Exclude));
if (prop.Type == SendPropType.Datatable)
{
foreach (FlattenedProp childProp in prop.Table.Flatten(excludes))
{
childProp.FullName = childProp.FullName.Insert(0, NetTableName + '.');
yield return childProp;
}
}
else
{
FlattenedProp flatProp = new FlattenedProp();
flatProp.Property = prop;
flatProp.FullName = flatProp.Property.Name.Insert(0, NetTableName + '.');
yield return flatProp;
}
}
}
List<SendPropDefinition> SetupFlatPropertyArray()
{
var excludes = Excludes;
List<SendPropDefinition> props = new List<SendPropDefinition>();
SendTable_BuildHierarchy(excludes, props);
SendTable_SortByPriority(props);
return props;
}
void SendTable_BuildHierarchy(IEnumerable<SendPropDefinition> excludes, List<SendPropDefinition> allProperties)
{
List<SendPropDefinition> localProperties = new List<SendPropDefinition>();
SendTable_BuildHierarchy_IterateProps(excludes, localProperties, allProperties);
allProperties.AddRange(localProperties);
}
IEnumerable<SendPropDefinition> TestSortedProps
{
get { return SetupFlatPropertyArray(); }
}
void SendTable_SortByPriority(List<SendPropDefinition> props)
{
int start = 0;
for (int i = start; i < props.Count; i++)
{
if (props[i].Flags.HasFlag(SendPropFlags.ChangesOften))
{
if (i != start)
{
var temp = props[i];
props[i] = props[start];
props[start] = temp;
}
start++;
continue;
}
}
}
void SendTable_BuildHierarchy_IterateProps(IEnumerable<SendPropDefinition> excludes, List<SendPropDefinition> localProperties, List<SendPropDefinition> childDTProperties)
{
foreach (var prop in Properties)
{
if (prop.Flags.HasFlag(SendPropFlags.Exclude) || excludes.Contains(prop))
{
continue;
}
if (excludes.Any(e => e.Name == prop.Name && e.ExcludeName == prop.Parent.NetTableName))
continue;
if (prop.Type == SendPropType.Datatable)
{
if (prop.Flags.HasFlag(SendPropFlags.Collapsible))
prop.Table.SendTable_BuildHierarchy_IterateProps(excludes, localProperties, childDTProperties);
else
{
prop.Table.SendTable_BuildHierarchy(excludes, childDTProperties);
}
}
else
{
localProperties.Add(prop);
}
}
}
}
}

@ -0,0 +1,37 @@
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 ServerClass
{
public string Classname { get; set; }
public string DatatableName { get; set; }
public override string ToString()
{
return string.Format("{0} ({1})", Classname, DatatableName);
}
public bool Equals(ServerClass other)
{
return (
Classname == other.Classname &&
DatatableName == other.DatatableName);
}
public override bool Equals(object obj)
{
ServerClass cast = obj as ServerClass;
return (cast != null ? Equals(cast) : false);
}
public override int GetHashCode()
{
return unchecked(Classname.GetHashCode() + DatatableName.GetHashCode());
}
}
}

@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TF2Net.Data
{
[DebuggerDisplay("{Hostname}: {MapName}")]
public class ServerInfo
{
/// <summary>
/// protocol version
/// </summary>
public short Protocol { get; set; }
/// <summary>
/// number of changelevels since server start
/// </summary>
public int ServerCount { get; set; }
/// <summary>
/// dedicated server?
/// </summary>
public bool IsDedicated { get; set; }
/// <summary>
/// HLTV server?
/// </summary>
public bool IsHLTV { get; set; }
public enum OperatingSystem
{
Unknown,
Linux,
Windows,
}
public OperatingSystem OS { get; set; }
/// <summary>
/// server map CRC
/// </summary>
//public uint MapCRC { get; set; }
/// <summary>
/// client.dll CRC server is using
/// </summary>
public uint ClientCRC { get; set; }
/// <summary>
/// max number of clients on server
/// </summary>
public byte MaxClients { get; set; }
/// <summary>
/// max number of server classes
/// </summary>
public ushort MaxClasses { get; set; }
/// <summary>
/// our client slot number
/// </summary>
public int PlayerSlot { get; set; }
/// <summary>
/// server tick interval
/// </summary>
public double TickInterval { get; set; }
/// <summary>
/// game directory eg "tf2"
/// </summary>
public string GameDirectory { get; set; }
public string MapName { get; set; }
/// <summary>
/// Current skybox name
/// </summary>
public string SkyName { get; set; }
/// <summary>
/// Server name
/// </summary>
public string Hostname { get; set; }
}
}

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TF2Net.Data
{
public class SignonState
{
public ConnectionState State { get; set; }
public int SpawnCount { get; set; }
}
}

@ -0,0 +1,52 @@
namespace TF2Net.Data
{
public static class SourceConstants
{
public const int MAX_OSPATH = 260;
internal const int NETMSG_TYPE_BITS = 6;
internal const int EVENT_INDEX_BITS = 8;
internal const int MAX_EVENT_BITS = 9;
internal const int NET_MAX_PAYLOAD_BITS = 17;
internal const int NET_MAX_PAYLOAD = (1 << NET_MAX_PAYLOAD_BITS);
internal const int MAX_DECAL_INDEX_BITS = 9;
internal const int MAX_EDICT_BITS = 11;
internal const int MAX_EDICTS = (1 << MAX_EDICT_BITS);
internal const int MAX_USER_MSG_LENGTH_BITS = 11;
internal const int MAX_USER_MSG_LENGTH = (1 << MAX_USER_MSG_LENGTH_BITS);
internal const int MAX_ENTITY_MSG_LENGTH_BITS = 11;
internal const int MAX_ENTITY_MSG_LENGTH = (1 << MAX_ENTITY_MSG_LENGTH_BITS);
internal const int MAX_SERVER_CLASS_BITS = 9;
internal const int MAX_SERVER_CLASSES = (1 << MAX_SERVER_CLASS_BITS);
internal const int MAX_SOUND_INDEX_BITS = 14;
internal const int SP_MODEL_INDEX_BITS = 11;
internal const int NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS = 10;
internal const int NUM_NETWORKED_EHANDLE_BITS = (MAX_EDICT_BITS + NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS);
internal const int INVALID_NETWORKED_EHANDLE_VALUE = ((1 << NUM_NETWORKED_EHANDLE_BITS) - 1);
internal const int MAX_DATATABLES = 1024;
internal const int MAX_DATATABLE_PROPS = 4096;
internal const int COORD_INTEGER_BITS = 14;
internal const int COORD_FRACTIONAL_BITS = 5;
internal const int COORD_DENOMINATOR = 1 << COORD_FRACTIONAL_BITS;
internal const double COORD_RESOLUTION = 1.0 / COORD_DENOMINATOR;
internal const int COORD_INTEGER_BITS_MP = 11;
internal const int COORD_FRACTIONAL_BITS_MP_LOWPRECISION = 3;
internal const int COORD_DENOMINATOR_LOWPRECISION = 1 << COORD_FRACTIONAL_BITS_MP_LOWPRECISION;
internal const double COORD_RESOLUTION_LOWPRECISION = 1.0 / COORD_DENOMINATOR_LOWPRECISION;
internal const int SPROP_NUMFLAGBITS_NETWORKED = 16;
internal const int SPROP_NUMFLAGBITS = 17;
}
}

@ -0,0 +1,98 @@
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("Stringtable: {TableName} ({Entries.Count,nq}/{MaxEntries,nq})")]
public class StringTable : IEnumerable<StringTableEntry>
{
public WorldState World { get; }
public string TableName { get; }
public ushort MaxEntries { get; }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
readonly SortedAutoList<StringTableEntry> m_Entries;
public IReadOnlyList<StringTableEntry> Entries { get { return m_Entries; } }
public ushort? UserDataSize { get; }
public byte? UserDataSizeBits { get; }
public SingleEvent<Action<StringTable>> StringTableUpdated { get; } = new SingleEvent<Action<StringTable>>();
public StringTable(WorldState ws, string tableName, ushort maxEntries, ushort? userDataSize, byte? userDataSizeBits)
{
World = ws;
TableName = tableName;
MaxEntries = maxEntries;
UserDataSize = userDataSize;
UserDataSizeBits = userDataSizeBits;
m_Entries = new SortedAutoList<StringTableEntry>(
Comparer<StringTableEntry>.Create(
(lhs, rhs) =>
{
Debug.Assert(lhs.ID != rhs.ID);
return Comparer<int>.Default.Compare(lhs.ID, rhs.ID);
}));
}
public void Add(StringTableEntry entry)
{
Debug.Assert(entry.Table == this);
m_Entries.Add(entry);
entry.EntryChanged.Add(Entry_EntryChanged);
}
private void Entry_EntryChanged(StringTableEntry entry)
{
StringTableUpdated.Invoke(this);
}
private class SortedAutoList<T> : SortedSet<T>, IList<T>, IReadOnlyList<T>
{
public T this[int index]
{
get
{
return this.ElementAt(index);
}
set
{
Remove(this.ElementAt(index));
Add(value);
}
}
public int IndexOf(T item)
{
throw new NotSupportedException();
}
public void Insert(int index, T item)
{
throw new NotSupportedException();
}
public void RemoveAt(int index)
{
Remove(this.ElementAt(index));
}
public SortedAutoList(IComparer<T> comparer) : base(comparer) { }
}
public IEnumerator<StringTableEntry> GetEnumerator()
{
return Entries.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
}

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitSet;
namespace TF2Net.Data
{
[DebuggerDisplay("{ID,nq}: {Value}")]
public class StringTableEntry
{
public StringTable Table { get; }
public SingleEvent<Action<StringTableEntry>> EntryChanged { get; } = new SingleEvent<Action<StringTableEntry>>();
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
ushort m_ID;
public ushort ID
{
get { return m_ID; }
set
{
if (m_ID != value)
{
m_ID = value;
EntryChanged?.Invoke(this);
}
}
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
string m_Value;
public string Value
{
get { return m_Value; }
set
{
if (m_Value != value)
{
m_Value = value;
EntryChanged?.Invoke(this);
}
}
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
BitStream m_UserData;
public BitStream UserData
{
get { return m_UserData?.Clone(); }
set
{
if (m_UserData != value)
{
m_UserData = value;
EntryChanged?.Invoke(this);
}
}
}
public StringTableEntry(StringTable table)
{
Table = table;
}
}
}

@ -0,0 +1,11 @@
namespace TF2Net.Data
{
public enum Team
{
Invalid = -1,
Unassigned = 0,
Spectator = 1,
Red = 2,
Blue = 3,
}
}

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitSet;
namespace TF2Net.Data
{
public class UserInfo
{
public string Name { get; set; }
public int? UserID { get; set; }
public string GUID { get; set; }
public uint? FriendsID { get; set; }
public string FriendsName { get; set; }
public bool? IsFakePlayer { get; set; }
public bool? IsHLTV { get; set; }
public uint?[] CustomFiles { get; } = new uint?[4];
public uint? FilesDownloaded { get; set; }
public UserInfo(BitStream stream)
{
Name = Encoding.ASCII.GetString(stream.ReadBytes(32)).TrimEnd('\0');
UserID = stream.ReadInt();
GUID = Encoding.ASCII.GetString(stream.ReadBytes(33)).TrimEnd('\0');
FriendsID = stream.ReadUInt();
FriendsName = Encoding.ASCII.GetString(stream.ReadBytes(32)).TrimEnd('\0');
IsFakePlayer = stream.ReadByte() > 0 ? true : false;
IsHLTV = stream.ReadByte() > 0 ? true : false;
for (byte i = 0; i < 4; i++)
CustomFiles[i] = stream.ReadUInt();
FilesDownloaded = stream.ReadByte();
}
}
}

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TF2Net.Data
{
public enum UserMessageType
{
Geiger = 0,
Train = 1,
HudText = 2,
SayText = 3,
SayText2 = 4,
TextMsg = 5,
ResetHUD = 6,
GameTitle = 7,
ItemPickup = 8,
ShowMenu = 9,
Shake = 10,
HudNotifyCustom = 27,
BreakModel = 41,
CheapBreakModel = 42,
MVMResetPlayerStats = 57,
}
}

@ -0,0 +1,75 @@
using System;
using System.Diagnostics;
namespace TF2Net.Data
{
[DebuggerDisplay("{ToString(),nq}")]
public class Vector : IReadOnlyVector, ICloneable
{
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
public Vector() { }
public Vector(double x, double y, double z = 0)
{
X = x;
Y = y;
Z = z;
}
public Vector(double[] xyz)
{
if (xyz == null)
throw new ArgumentNullException(nameof(xyz));
if (xyz.Length != 3)
throw new ArgumentException("Array is not of length 3", nameof(xyz));
X = xyz[0];
Y = xyz[1];
Z = xyz[2];
}
public Vector(IReadOnlyVector v)
{
X = v.X;
Y = v.Y;
Z = v.Z;
}
public double this[int i]
{
get
{
switch (i)
{
case 0: return X;
case 1: return Y;
case 2: return Z;
}
throw new ArgumentOutOfRangeException(nameof(i));
}
set
{
switch (i)
{
case 0: X = value; return;
case 1: Y = value; return;
case 2: Z = value; return;
}
throw new ArgumentOutOfRangeException(nameof(i));
}
}
public override string ToString()
{
return string.Format("{0}: ({1} {2} {3})", nameof(Vector), X, Y, Z);
}
public Vector Clone()
{
return (Vector)MemberwiseClone();
}
object ICloneable.Clone() { return Clone(); }
}
}

@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TF2Net.Data
{
public enum WeaponType
{
TF_WEAPON_NONE = 0,
TF_WEAPON_BAT,
TF_WEAPON_BAT_WOOD,
TF_WEAPON_BOTTLE,
TF_WEAPON_FIREAXE,
TF_WEAPON_CLUB,
TF_WEAPON_CROWBAR,
TF_WEAPON_KNIFE,
TF_WEAPON_FISTS,
TF_WEAPON_SHOVEL,
TF_WEAPON_WRENCH,
TF_WEAPON_BONESAW,
TF_WEAPON_SHOTGUN_PRIMARY,
TF_WEAPON_SHOTGUN_SOLDIER,
TF_WEAPON_SHOTGUN_HWG,
TF_WEAPON_SHOTGUN_PYRO,
TF_WEAPON_SCATTERGUN,
TF_WEAPON_SNIPERRIFLE,
TF_WEAPON_MINIGUN,
TF_WEAPON_SMG,
TF_WEAPON_SYRINGEGUN_MEDIC,
TF_WEAPON_TRANQ,
TF_WEAPON_ROCKETLAUNCHER,
TF_WEAPON_GRENADELAUNCHER,
TF_WEAPON_PIPEBOMBLAUNCHER,
TF_WEAPON_FLAMETHROWER,
TF_WEAPON_GRENADE_NORMAL,
TF_WEAPON_GRENADE_CONCUSSION,
TF_WEAPON_GRENADE_NAIL,
TF_WEAPON_GRENADE_MIRV,
TF_WEAPON_GRENADE_MIRV_DEMOMAN,
TF_WEAPON_GRENADE_NAPALM,
TF_WEAPON_GRENADE_GAS,
TF_WEAPON_GRENADE_EMP,
TF_WEAPON_GRENADE_CALTROP,
TF_WEAPON_GRENADE_PIPEBOMB,
TF_WEAPON_GRENADE_SMOKE_BOMB,
TF_WEAPON_GRENADE_HEAL,
TF_WEAPON_GRENADE_STUNBALL,
TF_WEAPON_GRENADE_JAR,
TF_WEAPON_GRENADE_JAR_MILK,
TF_WEAPON_PISTOL,
TF_WEAPON_PISTOL_SCOUT,
TF_WEAPON_REVOLVER,
TF_WEAPON_NAILGUN,
TF_WEAPON_PDA,
TF_WEAPON_PDA_ENGINEER_BUILD,
TF_WEAPON_PDA_ENGINEER_DESTROY,
TF_WEAPON_PDA_SPY,
TF_WEAPON_BUILDER,
TF_WEAPON_MEDIGUN,
TF_WEAPON_GRENADE_MIRVBOMB,
TF_WEAPON_FLAMETHROWER_ROCKET,
TF_WEAPON_GRENADE_DEMOMAN,
TF_WEAPON_SENTRY_BULLET,
TF_WEAPON_SENTRY_ROCKET,
TF_WEAPON_DISPENSER,
TF_WEAPON_INVIS,
TF_WEAPON_FLAREGUN,
TF_WEAPON_LUNCHBOX,
TF_WEAPON_JAR,
TF_WEAPON_COMPOUND_BOW,
TF_WEAPON_BUFF_ITEM,
TF_WEAPON_PUMPKIN_BOMB,
TF_WEAPON_SWORD,
TF_WEAPON_DIRECTHIT,
TF_WEAPON_LIFELINE,
TF_WEAPON_LASER_POINTER,
TF_WEAPON_DISPENSER_GUN,
TF_WEAPON_SENTRY_REVENGE,
TF_WEAPON_JAR_MILK,
TF_WEAPON_HANDGUN_SCOUT_PRIMARY,
TF_WEAPON_BAT_FISH,
TF_WEAPON_CROSSBOW,
TF_WEAPON_STICKBOMB,
TF_WEAPON_HANDGUN_SCOUT_SEC,
TF_WEAPON_SODA_POPPER,
TF_WEAPON_SNIPERRIFLE_DECAP,
TF_WEAPON_RAYGUN,
TF_WEAPON_PARTICLE_CANNON,
TF_WEAPON_MECHANICAL_ARM,
TF_WEAPON_DRG_POMSON,
TF_WEAPON_BAT_GIFTWRAP,
TF_WEAPON_GRENADE_ORNAMENT,
TF_WEAPON_RAYGUN_REVENGE,
TF_WEAPON_PEP_BRAWLER_BLASTER,
TF_WEAPON_CLEAVER,
TF_WEAPON_GRENADE_CLEAVER,
TF_WEAPON_STICKY_BALL_LAUNCHER,
TF_WEAPON_GRENADE_STICKY_BALL,
TF_WEAPON_SHOTGUN_BUILDING_RESCUE,
TF_WEAPON_CANNON,
TF_WEAPON_THROWABLE,
TF_WEAPON_GRENADE_THROWABLE,
TF_WEAPON_PDA_SPY_BUILD,
TF_WEAPON_GRENADE_WATERBALLOON,
TF_WEAPON_HARVESTER_SAW,
TF_WEAPON_SPELLBOOK,
TF_WEAPON_SPELLBOOK_PROJECTILE,
TF_WEAPON_SNIPERRIFLE_CLASSIC,
}
}

@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using BitSet;
using TF2Net.Entities;
namespace TF2Net.Data
{
public class WorldState
{
WorldEvents m_Listeners;
public WorldEvents Listeners
{
get { return m_Listeners; }
set
{
Debug.Assert(m_Listeners == null);
m_Listeners = value;
RegisterEventHandlers();
}
}
public ulong? EndTick { get; set; }
public ulong BaseTick { get; set; } = 0;
public ulong Tick { get; set; }
public double LastFrameTime { get; set; }
public double LastFrameTimeStdDev { get; set; }
public SignonState SignonState { get; set; }
public ServerInfo ServerInfo { get; set; }
public Entity[] Entities { get; } = new Entity[SourceConstants.MAX_EDICTS];
public IEnumerable<Entity> EntitiesInPVS
{
get
{
for (int i = 0; i < SourceConstants.MAX_EDICTS; i++)
{
Entity e = Entities[i];
if (e?.InPVS == true)
yield return e;
}
}
}
public IList<StringTable> StringTables { get; } = new List<StringTable>();
public IDictionary<string, string> ConVars { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public ushort? ViewEntity { get; set; }
public IList<ServerClass> ServerClasses { get; set; }
public IList<SendTable> SendTables { get; set; }
public byte ClassBits { get { return (byte)Math.Ceiling(Math.Log(ServerClasses.Count, 2)); } }
public IList<GameEventDeclaration> EventDeclarations { get; set; }
public IEnumerable<KeyValuePair<ServerClass, BitStream>> StaticBaselines
{
get
{
return StringTables.Single(st => st.TableName == "instancebaseline")
.Entries.Select(e => new KeyValuePair<ServerClass, BitStream>(ServerClasses[int.Parse(e.Value)], e.UserData));
}
}
public IList<SendProp>[][] InstanceBaselines { get; } = new IList<SendProp>[2][]
{
new IList<SendProp>[SourceConstants.MAX_EDICTS],
new IList<SendProp>[SourceConstants.MAX_EDICTS],
};
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
readonly List<Player> m_CachedPlayers = new List<Player>();
public IEnumerable<Player> Players
{
get
{
if (SignonState?.State != ConnectionState.Full)
yield break;
IEnumerable<StringTableEntry> table = StringTables.SingleOrDefault(st => st.TableName == "userinfo");
if (table == null)
{
lock (m_CachedPlayers)
m_CachedPlayers.Clear();
yield break;
}
List<Player> touched = new List<Player>();
foreach (var user in table)
{
var localData = user.UserData;
if (localData == null)
continue;
Debug.Assert(localData.Cursor == 0);
UserInfo decoded = new UserInfo(localData);
Player existing;
lock (m_CachedPlayers)
existing = m_CachedPlayers.SingleOrDefault(p => p.Info.GUID == decoded.GUID);
uint entityIndex = uint.Parse(user.Value) + 1;
Existing:
if (existing != null)
{
Debug.Assert(entityIndex == existing.EntityIndex);
existing.Info = decoded;
touched.Add(existing);
yield return existing;
}
else
{
Player newPlayer;
lock (m_CachedPlayers)
{
// Check again
existing = m_CachedPlayers.SingleOrDefault(p => p.Info.GUID == decoded.GUID);
if (existing != null)
goto Existing;
else
{
newPlayer = new Player(decoded, this, entityIndex);
Listeners.PlayerAdded.Invoke(newPlayer);
m_CachedPlayers.Add(newPlayer);
touched.Add(newPlayer);
}
}
yield return newPlayer;
}
}
Console.WriteLine(touched);
}
}
void RegisterEventHandlers()
{
if (Listeners == null)
throw new ArgumentNullException(nameof(Listeners));
}
}
}

@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using TF2Net.Data;
using TF2Net.Monitors;
namespace TF2Net.Entities
{
[DebuggerDisplay("{ToString(),nq}")]
public class Entity : IEntity, IDisposable, IEquatable<Entity>
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
readonly WorldState m_World;
public WorldState World { get { CheckDisposed(); return m_World; } }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
readonly ServerClass m_Class;
public ServerClass Class { get { CheckDisposed(); return m_Class; } }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
readonly SendTable m_NetworkTable;
public SendTable NetworkTable { get { CheckDisposed(); return m_NetworkTable; } }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
readonly List<SendProp> m_Properties = new List<SendProp>();
public IReadOnlyList<SendProp> Properties { get { CheckDisposed(); return m_Properties; } }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
readonly uint m_Index;
public uint Index { get { CheckDisposed(); return m_Index; } }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
readonly uint m_SerialNumber;
public uint SerialNumber { get { CheckDisposed(); return m_SerialNumber; } }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
bool m_InPVS;
public bool InPVS
{
get { CheckDisposed(); return m_InPVS; }
set
{
CheckDisposed();
var oldValue = m_InPVS;
m_InPVS = value;
if (value && !oldValue)
{
EnteredPVS.Invoke(this);
World.Listeners.EntityEnteredPVS.Invoke(this);
}
else if (!value && oldValue)
{
LeftPVS.Invoke(this);
World.Listeners.EntityLeftPVS.Invoke(this);
}
}
}
public SingleEvent<Action<Entity>> EnteredPVS { get; } = new SingleEvent<Action<Entity>>();
public SingleEvent<Action<Entity>> LeftPVS { get; } = new SingleEvent<Action<Entity>>();
public SingleEvent<Action<SendProp>> PropertyAdded { get; } = new SingleEvent<Action<SendProp>>();
public SingleEvent<Action<IPropertySet>> PropertiesUpdated { get; } = new SingleEvent<Action<IPropertySet>>();
public IEntityPropertyMonitor<EHandle> Owner { get; }
public IEntityPropertyMonitor<Team?> Team { get; }
public Entity(WorldState ws, ServerClass sClass, SendTable table, uint index, uint serialNumber)
{
m_World = ws;
m_Class = sClass;
m_NetworkTable = table;
m_Index = index;
m_SerialNumber = serialNumber;
Team = new EntityPropertyMonitor<Team?>("DT_BaseEntity.m_iTeamNum", this, o => (Team)(int)o);
Owner = new EntityPropertyMonitor<EHandle>("DT_BaseEntity.m_hOwnerEntity", this, o => new EHandle(ws, (uint)o));
}
public void AddProperty(SendProp newProp)
{
CheckDisposed();
Debug.Assert(!m_Properties.Any(p => ReferenceEquals(p.Definition, newProp.Definition)));
Debug.Assert(ReferenceEquals(newProp.Entity, this));
m_Properties.Add(newProp);
PropertyAdded.Invoke(newProp);
}
public override string ToString()
{
CheckDisposed();
return string.Format("{0}({1}): {2}", Index, SerialNumber, Class.Classname);
}
public bool Equals(Entity other)
{
CheckDisposed();
return (
other?.Index == Index &&
other.SerialNumber == SerialNumber);
}
public override bool Equals(object obj)
{
CheckDisposed();
if (GetHashCode() != obj.GetHashCode())
return false;
return Equals(obj as Entity);
}
public override int GetHashCode()
{
CheckDisposed();
return (int)(Index + (SerialNumber << SourceConstants.MAX_EDICT_BITS));
}
protected void CheckDisposed()
{
if (m_Disposed)
throw new ObjectDisposedException(nameof(Entity));
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
bool m_Disposed = false;
public void Dispose()
{
CheckDisposed();
m_Disposed = true;
foreach (SendProp prop in m_Properties)
prop.Dispose();
}
}
}

@ -0,0 +1,31 @@
using System;
namespace TF2Net.Entities
{
public abstract class BaseEntityWrapper
{
public IBaseEntity Entity { get; }
public BaseEntityWrapper(IBaseEntity e, string className)
{
if (e.Class.Classname != className)
throw new ArgumentException(string.Format("Invalid entity class for this {0}", nameof(BaseEntityWrapper)));
Entity = e;
}
}
public abstract class AbstractEntityWrapper : BaseEntityWrapper
{
public new IEntity Entity { get { return (IEntity)base.Entity; } }
public AbstractEntityWrapper(IEntity e, string className) : base(e, className) { }
}
public abstract class EntityWrapper : AbstractEntityWrapper
{
public new Entity Entity { get { return (Entity)base.Entity; } }
public EntityWrapper(Entity e, string className) : base(e, className) { }
}
}

@ -0,0 +1,12 @@
using TF2Net.Data;
namespace TF2Net.Entities
{
public interface IBaseEntity : IStaticPropertySet
{
WorldState World { get; }
ServerClass Class { get; }
SendTable NetworkTable { get; }
}
public interface IEntity : IBaseEntity, IPropertySet { }
}

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TF2Net.Data;
namespace TF2Net.Entities
{
public interface IStaticPropertySet
{
IReadOnlyList<SendProp> Properties { get; }
}
public interface IPropertySet : IStaticPropertySet
{
SingleEvent<Action<SendProp>> PropertyAdded { get; }
SingleEvent<Action<IPropertySet>> PropertiesUpdated { get; }
void AddProperty(SendProp prop);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static class IPropertySetExtensions
{
public static SendProp GetProperty(this IStaticPropertySet set, SendPropDefinition def)
{
var retVal = set.Properties.SingleOrDefault(x => x.Definition == def);
Debug.Assert(retVal == null || ReferenceEquals(retVal.Definition, def));
Debug.Assert(retVal == null || ReferenceEquals(retVal.Entity, set));
return retVal;
}
public static SendProp GetProperty(this IStaticPropertySet set, string propName)
{
var retVal = set.Properties.SingleOrDefault(x => x.Definition.FullName == propName);
return retVal;
}
}
}

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TF2Net.Data;
namespace TF2Net.Entities
{
public class Pill : EntityWrapper
{
public const string CLASSNAME = "CTFGrenadePipebombProjectile";
public Pill(Entity e) : base(e, CLASSNAME)
{
}
}
}

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TF2Net.Data;
using TF2Net.Monitors;
namespace TF2Net.Entities
{
public class TFRocket : EntityWrapper, IEquatable<TFRocket>, IEquatable<Entity>
{
public IEntityPropertyMonitor<Vector> Position { get; }
public IEntityPropertyMonitor<Vector> Angle { get; }
public IEntityPropertyMonitor<Team?> Team { get { return Entity.Team; } }
public IEntityPropertyMonitor<EHandle> Launcher { get; }
public TFRocket(Entity e) : base(e, "CTFProjectile_Rocket")
{
Position = new EntityPropertyMonitor<Vector>("DT_TFBaseRocket.m_vecOrigin", Entity, o => (Vector)o);
Angle = new EntityPropertyMonitor<Vector>("DT_TFBaseRocket.m_angRotation", Entity, o => (Vector)o);
Launcher = new EntityPropertyMonitor<EHandle>("DT_TFBaseRocket.m_hLauncher", Entity, o => new EHandle(e.World, (uint)o));
}
public bool Equals(TFRocket other)
{
return Entity.Equals(other?.Entity);
}
public override bool Equals(object obj)
{
// Entity
{
Entity e = obj as Entity;
if (e != null)
return Equals(e);
}
// TFRocket
{
TFRocket r = obj as TFRocket;
if (r != null)
return Equals(r);
}
return false;
}
public override int GetHashCode()
{
return Entity.GetHashCode();
}
public bool Equals(Entity other)
{
return ((IEquatable<Entity>)Entity).Equals(other);
}
}
}

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TF2Net.Data;
namespace TF2Net.Entities.TempEntities
{
public class FireBullets : BaseEntityWrapper
{
public Player Player { get; }
public WeaponType Weapon { get; }
public IReadOnlyVector Origin { get; }
public const string CLASSNAME = "CTEFireBullets";
public FireBullets(IBaseEntity e) : base(e, CLASSNAME)
{
Origin = (Vector)e.GetProperty("DT_TEFireBullets.m_vecOrigin")?.Value ?? new Vector();
{
uint playerIndex = (uint)e.GetProperty("DT_TEFireBullets.m_iPlayer").Value;
Player = e.World.Players.ElementAt((int)playerIndex);
}
Weapon = (WeaponType)(uint)e.GetProperty("DT_TEFireBullets.m_iWeaponID").Value;
}
}
}

@ -0,0 +1,27 @@
using TF2Net.Data;
namespace TF2Net.Entities.TempEntities
{
public class TFBlood : BaseEntityWrapper
{
public IReadOnlyVector Origin { get; }
public uint? TargetEntityIndex { get; }
public Entity TargetEntity { get { return TargetEntityIndex.HasValue ? Entity.World.Entities[TargetEntityIndex.Value] : null; } }
public const string CLASSNAME = "CTETFBlood";
public TFBlood(IBaseEntity e) : base(e, CLASSNAME)
{
{
Vector origin = new Vector();
// "DT_TETFBlood.m_vecOrigin[0]"
origin.X = (double?)e.GetProperty("DT_TETFBlood.m_vecOrigin[0]")?.Value ?? 0;
origin.Y = (double?)e.GetProperty("DT_TETFBlood.m_vecOrigin[1]")?.Value ?? 0;
origin.Z = (double?)e.GetProperty("DT_TETFBlood.m_vecOrigin[2]")?.Value ?? 0;
Origin = origin;
}
TargetEntityIndex = (uint?)e.GetProperty("DT_TETFBlood.entindex")?.Value;
}
}
}

@ -0,0 +1,24 @@
using TF2Net.Data;
namespace TF2Net.Entities.TempEntities
{
public class TFExplosion : BaseEntityWrapper
{
public IReadOnlyVector Origin { get; }
public IReadOnlyVector Normal { get; }
public const string CLASSNAME = "CTETFExplosion";
public TFExplosion(IBaseEntity e) : base(e, CLASSNAME)
{
{
Vector origin = new Vector();
origin.X = (double?)e.GetProperty("DT_TETFExplosion.m_vecOrigin[0]")?.Value ?? 0;
origin.Y = (double?)e.GetProperty("DT_TETFExplosion.m_vecOrigin[1]")?.Value ?? 0;
origin.Z = (double?)e.GetProperty("DT_TETFExplosion.m_vecOrigin[2]")?.Value ?? 0;
Origin = origin;
}
Normal = (Vector)e.GetProperty("DT_TETFExplosion.m_vecNormal").Value;
}
}
}

@ -0,0 +1,198 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace TF2Net.Extensions
{
class AutoDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>
{
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
readonly List<TValue> m_Contents = new List<TValue>();
public TValue this[TKey key]
{
get
{
TValue outValue;
if (!TryGetValue(key, out outValue))
throw new KeyNotFoundException();
return outValue;
}
set
{
for (uint i = 0; i < m_Contents.Count; i++)
{
TKey currentKey = m_KeySelector(m_Contents[(int)i]);
if (m_KeyComparer.Equals(currentKey, key))
{
m_Contents[(int)i] = value;
return;
}
}
Add(key, value);
}
}
public int Count { get { return m_Contents.Count; } }
public bool IsReadOnly { get { return false; } }
public ICollection<TKey> Keys { get { return new KeyCollection(m_Contents, m_KeySelector, m_KeyComparer); } }
public ICollection<TValue> Values { get { return m_Contents; } }
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys { get { return m_Contents.Select(v => m_KeySelector(v)); } }
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values { get { return Values; } }
readonly Func<TValue, TKey> m_KeySelector;
readonly IEqualityComparer<TKey> m_KeyComparer;
public AutoDictionary(Func<TValue, TKey> keySelector) : this(keySelector, EqualityComparer<TKey>.Default) { }
public AutoDictionary(Func<TValue, TKey> keySelector, IEqualityComparer<TKey> keyComparer)
{
if (keySelector == null)
throw new ArgumentNullException(nameof(keySelector));
if (keyComparer == null)
throw new ArgumentNullException(nameof(keyComparer));
m_KeySelector = keySelector;
m_KeyComparer = keyComparer;
}
public void Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}
public void Add(TKey key, TValue value)
{
Debug.Assert(m_KeyComparer.Equals(key, m_KeySelector(value)));
Add(value);
}
public void Add(TValue value)
{
m_Contents.Add(value);
}
public void Clear()
{
m_Contents.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
throw new NotImplementedException();
}
public bool ContainsKey(TKey key)
{
return m_Contents.SingleOrDefault(v => m_KeyComparer.Equals(key, m_KeySelector(v))) != null;
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
foreach (var kvPair in this)
array[arrayIndex++] = kvPair;
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return m_Contents
.Select(v => new KeyValuePair<TKey, TValue>(m_KeySelector(v), v))
.GetEnumerator();
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
throw new NotImplementedException();
}
public bool Remove(TKey key)
{
for (uint i = 0; i < m_Contents.Count; i++)
{
TKey currentKey = m_KeySelector(m_Contents[(int)i]);
if (m_KeyComparer.Equals(currentKey, key))
{
m_Contents.RemoveAt((int)i);
return true;
}
}
return false;
}
public bool TryGetValue(TKey key, out TValue value)
{
foreach (var v in m_Contents)
{
TKey currentKey = m_KeySelector(v);
if (m_KeyComparer.Equals(currentKey, key))
{
value = v;
return true;
}
}
value = default(TValue);
return false;
}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
class KeyCollection : ICollection<TKey>
{
readonly List<TValue> m_Values;
readonly Func<TValue, TKey> m_KeySelector;
readonly IEqualityComparer<TKey> m_KeyComparer;
public KeyCollection(List<TValue> values, Func<TValue, TKey> keySelector, IEqualityComparer<TKey> keyComparer)
{
m_Values = values;
m_KeySelector = keySelector;
m_KeyComparer = keyComparer;
}
public int Count { get { return m_Values.Count; } }
public bool IsReadOnly { get { return false; } }
public void Add(TKey item) { throw new NotSupportedException(); }
public void Clear() { m_Values.Clear(); }
public bool Contains(TKey item)
{
return this.Where(k => m_KeyComparer.Equals(item, k)).Any();
}
public void CopyTo(TKey[] array, int arrayIndex)
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
public IEnumerator<TKey> GetEnumerator()
{
return m_Values.Select(v => m_KeySelector(v)).GetEnumerator();
}
public bool Remove(TKey item)
{
for (uint i = 0; i < m_Values.Count; i++)
{
TKey currentKey = m_KeySelector(m_Values[(int)i]);
if (m_KeyComparer.Equals(currentKey, item))
{
m_Values.RemoveAt((int)i);
return true;
}
}
return false;
}
}
}
}

@ -0,0 +1,25 @@
using BitSet;
using TF2Net.Data;
using TF2Net.NetMessages;
namespace TF2Net.Extensions
{
public static class BitStreamExtensions
{
public static Vector ReadVector(this BitStream stream)
{
bool flagX = stream.ReadBool();
bool flagY = stream.ReadBool();
bool flagZ = stream.ReadBool();
Vector vector = new Vector();
if (flagX)
vector.X = BitCoord.Read(stream);
if (flagY)
vector.Y = BitCoord.Read(stream);
if (flagZ)
vector.Z = BitCoord.Read(stream);
return vector;
}
}
}

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace System.Collections
{
[EditorBrowsable(EditorBrowsableState.Never)]
public static class TF2Net_Extensions
{
public static uint? FindNextSetBit(this BitArray source, uint startIndex = 0)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
for (uint i = startIndex; i < source.Length; i++)
{
if (source[(int)i])
return i;
}
return null;
}
public static IDictionary<TKey, TValue> Clone<TKey, TValue>(this IDictionary<TKey, TValue> src)
{
return new Dictionary<TKey, TValue>(src);
}
public static void AddRange<T>(this IList<T> input, IEnumerable<T> range)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
if (range == null)
throw new ArgumentNullException(nameof(range));
foreach (var x in range)
input.Add(x);
}
}
}

@ -0,0 +1,89 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
namespace System.Linq
{
[EditorBrowsable(EditorBrowsableState.Never)]
public static class TF2Net_Extensions
{
class ListWrapper<T> : IReadOnlyList<T>
{
readonly IList<T> m_Source;
public ListWrapper(IList<T> src)
{
m_Source = src;
}
public T this[int index] { get { return m_Source[index]; } }
public int Count { get { return m_Source.Count; } }
public IEnumerator<T> GetEnumerator() { return m_Source.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
public static IReadOnlyList<T> AsReadOnly<T>(this IList<T> input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return new ListWrapper<T>(input);
}
class DictionaryWrapper<K, V> : IReadOnlyDictionary<K, V>
{
readonly IDictionary<K, V> m_Source;
public DictionaryWrapper(IDictionary<K, V> src)
{
m_Source = src;
}
public V this[K key] { get { return m_Source[key]; } }
public int Count { get { return m_Source.Count; } }
public IEnumerable<K> Keys { get { return m_Source.Keys; } }
public IEnumerable<V> Values { get { return m_Source.Values; } }
public bool ContainsKey(K key)
{
return m_Source.ContainsKey(key);
}
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
{
return m_Source.GetEnumerator();
}
public bool TryGetValue(K key, out V value)
{
return m_Source.TryGetValue(key, out value);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public static IReadOnlyDictionary<K, V> AsReadOnly<K, V>(this IDictionary<K, V> input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
return new DictionaryWrapper<K, V>(input);
}
public static IEnumerable<T> Except<T>(this IEnumerable<T> input, T without)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
foreach (var x in input)
{
if (x.Equals(without))
continue;
yield return x;
}
}
}
}

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using TF2Net.Data;
using TF2Net.Entities;
namespace TF2Net
{
public interface IWorldEvents
{
SingleEvent<Action<WorldState>> GameEventsListLoaded { get; }
SingleEvent<Action<WorldState, GameEvent>> GameEvent { get; }
SingleEvent<Action<WorldState>> ServerClassesLoaded { get; }
SingleEvent<Action<WorldState>> SendTablesLoaded { get; }
/// <summary>
/// A "Print Message" command from the server.
/// </summary>
SingleEvent<Action<WorldState, string>> ServerTextMessage { get; }
SingleEvent<Action<WorldState>> ServerInfoLoaded { get; }
SingleEvent<Action<WorldState>> NewTick { get; }
SingleEvent<Action<WorldState, KeyValuePair<string, string>>> ServerSetConVar { get; }
SingleEvent<Action<WorldState, string>> ServerConCommand { get; }
SingleEvent<Action<WorldState>> ViewEntityUpdated { get; }
SingleEvent<Action<StringTable>> StringTableCreated { get; }
SingleEvent<Action<WorldState, StringTable>> StringTableUpdated { get; }
SingleEvent<Action<Entity>> EntityCreated { get; }
SingleEvent<Action<Entity>> EntityEnteredPVS { get; }
SingleEvent<Action<Entity>> EntityLeftPVS { get; }
SingleEvent<Action<Entity>> EntityDeleted { get; }
SingleEvent<Action<Player>> PlayerAdded { get; }
SingleEvent<Action<Player>> PlayerRemoved { get; }
SingleEvent<Action<IBaseEntity>> TempEntityCreated { get; }
}
}

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TF2Net.Data;
using TF2Net.Entities;
namespace TF2Net.Monitors
{
public class EntityMonitor
{
public SingleEvent<Action<EntityMonitor, Entity>> EnteredPVS { get; } = new SingleEvent<Action<EntityMonitor, Entity>>();
public SingleEvent<Action<EntityMonitor, Entity>> LeftPVS { get; } = new SingleEvent<Action<EntityMonitor, Entity>>();
public WorldState World { get; }
public string ClassName { get; }
public EntityMonitor(WorldState ws, string classname)
{
World = ws;
ClassName = classname;
World.Listeners.EntityEnteredPVS.Add(Entity_EnteredPVS);
World.Listeners.EntityLeftPVS.Add(Entity_LeftPVS);
}
void Entity_EnteredPVS(Entity e)
{
if (e.Class.Classname == ClassName)
EnteredPVS.Invoke(this, e);
}
void Entity_LeftPVS(Entity e)
{
if (e.Class.Classname == ClassName)
LeftPVS.Invoke(this, e);
}
}
}

@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TF2Net.Data;
using TF2Net.Entities;
namespace TF2Net.Monitors
{
[DebuggerDisplay("{Value}")]
internal class EntityPropertyMonitor<T> : IEntityPropertyMonitor<T>
{
public Entity Entity { get; }
public string PropertyName { get; }
public SendProp Property { get; private set; }
Func<object, T> Decoder { get; }
bool m_ValueChanged;
public T Value { get; private set; }
object IPropertyMonitor.Value { get { return Value; } }
SingleEvent<Action<IPropertyMonitor>> IPropertyMonitor.ValueChanged { get; } = new SingleEvent<Action<IPropertyMonitor>>();
SingleEvent<Action<IPropertyMonitor<T>>> IPropertyMonitor<T>.ValueChanged { get; } = new SingleEvent<Action<IPropertyMonitor<T>>>();
SingleEvent<Action<IEntityPropertyMonitor>> IEntityPropertyMonitor.ValueChanged { get; } = new SingleEvent<Action<IEntityPropertyMonitor>>();
public SingleEvent<Action<IEntityPropertyMonitor<T>>> ValueChanged { get; } = new SingleEvent<Action<IEntityPropertyMonitor<T>>>();
public EntityPropertyMonitor(string propertyName, Entity e, Func<object, T> decoder)
{
ValueChanged.Add((self) => ((IPropertyMonitor)self).ValueChanged.Invoke(self));
ValueChanged.Add((self) => ((IPropertyMonitor<T>)self).ValueChanged.Invoke(self));
ValueChanged.Add((self) => ((IEntityPropertyMonitor)self).ValueChanged.Invoke(self));
PropertyName = propertyName;
Entity = e;
Decoder = decoder;
Entity.EnteredPVS.Add(Entity_EnteredPVS);
Entity.LeftPVS.Add(Entity_LeftPVS);
Entity.PropertiesUpdated.Add(Entity_PropertiesUpdated);
if (Entity.InPVS)
Entity_EnteredPVS(Entity);
}
private void Entity_PropertiesUpdated(IPropertySet e)
{
Debug.Assert(Entity == e);
if (m_ValueChanged)
{
ValueChanged.Invoke(this);
m_ValueChanged = false;
}
}
private void Entity_LeftPVS(Entity e)
{
Debug.Assert(Entity == e);
e.PropertyAdded.Remove(Entity_PropertyAdded);
Property = null;
}
private void Entity_EnteredPVS(Entity e)
{
Debug.Assert(Entity == e);
e.PropertyAdded.Add(Entity_PropertyAdded);
foreach (SendProp prop in e.Properties)
Entity_PropertyAdded(prop);
}
private void Entity_PropertyAdded(SendProp prop)
{
if (prop.Definition.FullName == PropertyName)
{
Property = prop;
if (prop.ValueChanged.Add(Prop_ValueChanged))
{
// First add only
if (prop.Value != null)
Prop_ValueChanged(prop);
}
}
}
private void Prop_ValueChanged(SendProp prop)
{
Debug.Assert(ReferenceEquals(prop.Entity, Entity));
Debug.Assert((!Entity.InPVS && Property == null) || prop == Property);
Value = Decoder(prop.Value);
m_ValueChanged = true;
}
}
}

@ -0,0 +1,39 @@
using System;
using TF2Net.Data;
using TF2Net.Entities;
namespace TF2Net.Monitors
{
public interface IPlayerPropertyMonitor<T> : IEntityPropertyMonitor<T>, IPlayerPropertyMonitor
{
new SingleEvent<Action<IPlayerPropertyMonitor<T>>> ValueChanged { get; }
}
public interface IPlayerPropertyMonitor : IEntityPropertyMonitor
{
Player Player { get; }
new SingleEvent<Action<IPlayerPropertyMonitor>> ValueChanged { get; }
}
public interface IEntityPropertyMonitor<T> : IPropertyMonitor<T>, IEntityPropertyMonitor
{
new SingleEvent<Action<IEntityPropertyMonitor<T>>> ValueChanged { get; }
}
public interface IEntityPropertyMonitor : IPropertyMonitor
{
Entity Entity { get; }
new SingleEvent<Action<IEntityPropertyMonitor>> ValueChanged { get; }
}
public interface IPropertyMonitor<T> : IPropertyMonitor
{
new T Value { get; }
new SingleEvent<Action<IPropertyMonitor<T>>> ValueChanged { get; }
}
public interface IPropertyMonitor
{
object Value { get; }
SendProp Property { get; }
string PropertyName { get; }
SingleEvent<Action<IPropertyMonitor>> ValueChanged { get; }
}
}

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TF2Net.Data;
namespace TF2Net.Monitors
{
[DebuggerDisplay("{Value}")]
class MultiPropertyMonitor<T> : IPropertyMonitor<T>
{
public string PropertyName { get { return string.Join("\n", PropertyMonitors.Select(pm => pm.PropertyName)); } }
public T Value { get; private set; }
object IPropertyMonitor.Value { get { return Value; } }
public SendProp Property { get; private set; }
SingleEvent<Action<IPropertyMonitor>> IPropertyMonitor.ValueChanged { get; } = new SingleEvent<Action<IPropertyMonitor>>();
public SingleEvent<Action<IPropertyMonitor<T>>> ValueChanged { get; } = new SingleEvent<Action<IPropertyMonitor<T>>>();
IEnumerable<IPropertyMonitor<T>> PropertyMonitors { get; }
public MultiPropertyMonitor(IEnumerable<IPropertyMonitor<T>> propertyMonitors)
{
ValueChanged.Add(self => ((IPropertyMonitor)self).ValueChanged.Invoke(self));
PropertyMonitors = propertyMonitors;
foreach (var prop in PropertyMonitors)
prop.ValueChanged.Add(PropValueChanged);
}
void PropValueChanged(IPropertyMonitor<T> propMonitor)
{
T newValue = propMonitor.Value;
if (!Value.Equals(newValue))
{
Value = newValue;
Property = propMonitor.Property;
if (!(Property == null || propMonitor.Property == null || propMonitor.Property.LastChangedTick >= Property.LastChangedTick))
Debugger.Break();
ValueChanged.Invoke(this);
}
}
}
}

@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitSet;
using TF2Net.Data;
using TF2Net.NetMessages;
namespace TF2Net
{
public static class NetMessageCoder
{
public static List<INetMessage> Decode(BitStream stream)
{
List<INetMessage> messages = new List<INetMessage>();
while (stream.Cursor < (stream.Length - SourceConstants.NETMSG_TYPE_BITS))
{
NetMessageType type = (NetMessageType)stream.ReadByte(SourceConstants.NETMSG_TYPE_BITS);
if (type == NetMessageType.NET_NOOP)
continue;
INetMessage newMsg = CreateNetMessage(type);
newMsg.ReadMsg(stream);
messages.Add(newMsg);
}
return messages;
}
static INetMessage CreateNetMessage(NetMessageType type)
{
switch (type)
{
case NetMessageType.NET_FILE: return new NetFileMessage();
case NetMessageType.NET_TICK: return new NetTickMessage();
case NetMessageType.NET_STRINGCMD: return new NetStringCmdMessage();
case NetMessageType.NET_SETCONVAR: return new NetSetConvarMessage();
case NetMessageType.NET_SIGNONSTATE: return new NetSignonStateMessage();
case NetMessageType.SVC_PRINT: return new NetPrintMessage();
case NetMessageType.SVC_SERVERINFO: return new NetServerInfoMessage();
case NetMessageType.SVC_CLASSINFO: return new NetClassInfoMessage();
case NetMessageType.SVC_SETPAUSE: return new NetSetPausedMessage();
case NetMessageType.SVC_CREATESTRINGTABLE: return new NetCreateStringTableMessage();
case NetMessageType.SVC_UPDATESTRINGTABLE: return new NetUpdateStringTableMessage();
case NetMessageType.SVC_VOICEINIT: return new NetVoiceInitMessage();
case NetMessageType.SVC_VOICEDATA: return new NetVoiceDataMessage();
case NetMessageType.SVC_SOUND: return new NetSoundMessage();
case NetMessageType.SVC_SETVIEW: return new NetSetViewMessage();
case NetMessageType.SVC_FIXANGLE: return new NetFixAngleMessage();
case NetMessageType.SVC_BSPDECAL: return new NetBspDecalMessage();
case NetMessageType.SVC_USERMESSAGE: return new NetUsrMsgMessage();
case NetMessageType.SVC_ENTITYMESSAGE: return new NetEntityMessage();
case NetMessageType.SVC_GAMEEVENT: return new NetGameEventMessage();
case NetMessageType.SVC_PACKETENTITIES: return new NetPacketEntitiesMessage();
case NetMessageType.SVC_TEMPENTITIES: return new NetTempEntityMessage();
case NetMessageType.SVC_PREFETCH: return new NetPrefetchMessage();
case NetMessageType.SVC_GAMEEVENTLIST: return new NetGameEventListMessage();
default: throw new NotImplementedException(string.Format("Unimplemented {0} \"{1}\"", typeof(NetMessageType).Name, type));
}
}
}
}

@ -0,0 +1,13 @@
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
public interface INetMessage
{
string Description { get; }
void ReadMsg(BitStream stream);
void ApplyWorldState(WorldState ws);
}
}

@ -0,0 +1,49 @@
using System;
using System.Diagnostics;
using BitSet;
using TF2Net.Data;
using TF2Net.Extensions;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetBspDecalMessage : INetMessage
{
const byte MaxDecalIndexBits = 9;
const byte MaxEdictBits = 11;
const byte SpModelIndexBits = 11;
public string Description => string.Format("svc_bspdecal: {0} {1} {2}", Position, DecalTextureIndex, EntIndex);
public Vector Position { get; set; }
public ulong DecalTextureIndex { get; set; }
public ulong EntIndex { get; set; }
public ulong ModelIndex { get; set; }
public bool LowPrioritiy { get; set; }
public void ReadMsg(BitStream stream)
{
Position = stream.ReadVector();
DecalTextureIndex = stream.ReadULong(MaxDecalIndexBits);
bool b = stream.ReadBool();
if (b)
{
EntIndex = stream.ReadULong(MaxEdictBits);
ModelIndex = stream.ReadULong(SpModelIndexBits);
}
else
{
EntIndex = 0;
ModelIndex = 0;
}
LowPrioritiy = stream.ReadBool();
}
public void ApplyWorldState(WorldState ws)
{
throw new NotImplementedException();
}
}
}

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetClassInfoMessage : INetMessage
{
public short ServerClassCount { get; set; }
public bool CreateOnClient { get; set; }
public class ServerClass
{
public ushort ClassID { get; set; }
public string DataTableName { get; set; }
public string ClassName { get; set; }
}
public IList<ServerClass> ServerClasses { get; set; }
public string Description
{
get
{
return string.Format("svc_ClassInfo: num {0}, {1}", ServerClassCount,
CreateOnClient ? "use client classes" : "full update");
}
}
public void ReadMsg(BitStream stream)
{
ServerClassCount = stream.ReadShort();
CreateOnClient = stream.ReadBool();
if (CreateOnClient)
return;
byte serverClassBits = (byte)(ExtMath.Log2(ServerClassCount) + 1);
ServerClasses = new List<ServerClass>();
for (int i = 0; i < ServerClassCount; i++)
{
ServerClass sc = new ServerClass();
sc.ClassID = stream.ReadUShort(serverClassBits);
sc.ClassName = stream.ReadCString();
sc.DataTableName = stream.ReadCString();
ServerClasses.Add(sc);
}
}
public void ApplyWorldState(WorldState ws)
{
if (!CreateOnClient)
throw new NotImplementedException();
}
}
}

@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using BitSet;
using Snappy;
using TF2Net.Data;
using TF2Net.NetMessages.Shared;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetCreateStringTableMessage : INetMessage
{
public BitStream Data { get; set; }
public string TableName { get; set; }
public ushort Entries { get; set; }
public ushort MaxEntries { get; set; }
public ushort? UserDataSize { get; set; }
public byte? UserDataSizeBits { get; set; }
public string Description
{
get
{
return string.Format("svc_CreateStringTable: table {0}, entries {1}, bytes {2} userdatasize {3} userdatabits {4}",
TableName, MaxEntries, BitInfo.BitsToBytes(Data.Length), UserDataSize, UserDataSizeBits);
}
}
public void ReadMsg(BitStream stream)
{
//bool isFilenames;
if (stream.ReadChar() == ':')
{
//isFilenames = true;
}
else
{
stream.Seek(-8, System.IO.SeekOrigin.Current);
//isFilenames = false;
}
TableName = stream.ReadCString();
MaxEntries = stream.ReadUShort();
int encodeBits = ExtMath.Log2(MaxEntries);
Entries = stream.ReadUShort((byte)(encodeBits + 1));
ulong bitCount = stream.ReadVarUInt();
// userdatafixedsize
if (stream.ReadBool())
{
UserDataSize = stream.ReadUShort(12);
UserDataSizeBits = stream.ReadByte(4);
}
bool isCompressedData = stream.ReadBool();
Data = stream.Subsection(stream.Cursor, stream.Cursor + bitCount);
stream.Seek(bitCount, System.IO.SeekOrigin.Current);
if (isCompressedData)
{
uint decompressedNumBytes = Data.ReadUInt();
uint compressedNumBytes = Data.ReadUInt();
byte[] compressedData = Data.ReadBytes(compressedNumBytes);
char[] magic = Encoding.ASCII.GetChars(compressedData, 0, 4);
if (
magic[0] != 'S' ||
magic[1] != 'N' ||
magic[2] != 'A' ||
magic[3] != 'P')
{
throw new FormatException("Unknown format for compressed stringtable");
}
int snappyDecompressedNumBytes = SnappyCodec.GetUncompressedLength(compressedData, 4, compressedData.Length - 4);
if (snappyDecompressedNumBytes != decompressedNumBytes)
throw new FormatException("Mismatching decompressed data lengths");
byte[] decompressedData = new byte[snappyDecompressedNumBytes];
if (SnappyCodec.Uncompress(compressedData, 4, compressedData.Length - 4, decompressedData, 0) != decompressedNumBytes)
throw new FormatException("Snappy didn't decode all the bytes");
Data = new BitStream(decompressedData);
}
}
public void ApplyWorldState(WorldState ws)
{
Data.Cursor = 0;
StringTable table = new StringTable(ws, TableName, MaxEntries, UserDataSize, UserDataSizeBits);
StringTableParser.ParseUpdate(Data, table, Entries);
StringTable foundTable = ws.StringTables.SingleOrDefault(t => t.TableName == table.TableName);
if (foundTable != null)
throw new InvalidOperationException("Attempted to create a stringtable that already exists!");
ws.StringTables.Add(table);
ws.Listeners.StringTableCreated.Invoke(table);
}
}
}

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitSet;
using TF2Net.Data;
using TF2Net.Entities;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetEntityMessage : INetMessage
{
const int DATA_LENGTH_BITS = 11;
public uint EntityIndex { get; set; }
public uint ClassID { get; set; }
public BitStream Data { get; set; }
public string Description
{
get
{
return string.Format("svc_EntityMessage: entity {0}, class {1}, bytes {2}",
EntityIndex, ClassID, BitInfo.BitsToBytes(Data.Length));
}
}
public void WriteMsg(byte[] buffer, ref ulong bitOffset)
{
throw new NotImplementedException();
}
public void ReadMsg(BitStream stream)
{
EntityIndex = stream.ReadUInt(SourceConstants.MAX_EDICT_BITS);
ClassID = stream.ReadUInt(SourceConstants.MAX_SERVER_CLASS_BITS);
ulong bitCount = stream.ReadULong(DATA_LENGTH_BITS);
Data = stream.Subsection(stream.Cursor, stream.Cursor + bitCount);
stream.Seek(bitCount, System.IO.SeekOrigin.Current);
}
public void ApplyWorldState(WorldState ws)
{
Entity target = ws.Entities[EntityIndex];
Console.WriteLine("hi");
}
}
}

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetFileMessage : INetMessage
{
public uint TransferID { get; set; }
public string Filename { get; set; }
public enum FileStatus
{
Denied = 0,
Requested = 1,
}
public FileStatus Status { get; set; }
public string Description
{
get
{
return string.Format("net_File: {0} {1}", Filename, Status);
}
}
public void ReadMsg(BitStream stream)
{
TransferID = stream.ReadUInt();
Filename = stream.ReadCString();
Status = (FileStatus)stream.ReadByte(1);
}
public void ApplyWorldState(WorldState ws)
{
}
}
}

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetFixAngleMessage : INetMessage
{
const int ANGLE_BITS = 16;
public bool Relative { get; set; }
public double[] Angle { get; set; }
public string Description
{
get
{
return string.Format("svc_FixAngle: {0} {1:N1} {2:N1} {3:N1}",
Relative ? "relative" : "absolute",
Angle[0], Angle[1], Angle[2]);
}
}
public void ReadMsg(BitStream stream)
{
Relative = stream.ReadBool();
Angle = new double[3];
Angle[0] = BitAngle.Read(stream, ANGLE_BITS);
Angle[1] = BitAngle.Read(stream, ANGLE_BITS);
Angle[2] = BitAngle.Read(stream, ANGLE_BITS);
}
public void ApplyWorldState(WorldState ws)
{
}
}
}

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetGameEventListMessage : INetMessage
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
ulong BitCount { get; set; }
//public byte[] Data { get; set; }
public IList<GameEventDeclaration> Events { get; set; }
public string Description
{
get
{
return string.Format("svc_GameEventList: number {0}, bytes {1}",
Events.Count, BitInfo.BitsToBytes(BitCount));
}
}
public void ReadMsg(BitStream stream)
{
ushort eventsCount = stream.ReadUShort(SourceConstants.MAX_EVENT_BITS);
BitCount = stream.ReadULong(20);
Events = new List<GameEventDeclaration>();
for (int i = 0; i < eventsCount; i++)
{
GameEventDeclaration e = new GameEventDeclaration();
e.ID = stream.ReadInt(SourceConstants.MAX_EVENT_BITS);
e.Name = stream.ReadCString();
GameEventDataType type;
e.Values = new Dictionary<string, GameEventDataType>();
while ((type = (GameEventDataType)stream.ReadUShort(3)) != GameEventDataType.Local)
{
string name = stream.ReadCString();
e.Values.Add(name, type);
}
Events.Add(e);
}
}
public void ApplyWorldState(WorldState ws)
{
ws.EventDeclarations = Events;
}
}
}

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetGameEventMessage : INetMessage
{
const int EVENT_LENGTH_BITS = 11;
public BitStream Data { get; set; }
public string Description
{
get
{
return string.Format("svc_GameEvent: bytes {0}", BitInfo.BitsToBytes(Data.Length));
}
}
public void ReadMsg(BitStream stream)
{
ulong bitCount = stream.ReadULong(EVENT_LENGTH_BITS);
Data = stream.Subsection(stream.Cursor, stream.Cursor + bitCount);
stream.Seek(bitCount, SeekOrigin.Current);
}
public void ApplyWorldState(WorldState ws)
{
GameEvent retVal = new GameEvent();
int eventID = Data.ReadInt(SourceConstants.MAX_EVENT_BITS);
retVal.Declaration = ws.EventDeclarations.Single(g => g.ID == eventID);
retVal.Values = new Dictionary<string, object>();
foreach (var value in retVal.Declaration.Values)
{
switch (value.Value)
{
case GameEventDataType.Local: break;
case GameEventDataType.Bool: retVal.Values.Add(value.Key, Data.ReadBool()); break;
case GameEventDataType.Byte: retVal.Values.Add(value.Key, Data.ReadByte()); break;
case GameEventDataType.Float: retVal.Values.Add(value.Key, Data.ReadSingle()); break;
case GameEventDataType.Long: retVal.Values.Add(value.Key, Data.ReadInt()); break;
case GameEventDataType.Short: retVal.Values.Add(value.Key, Data.ReadShort()); break;
case GameEventDataType.String: retVal.Values.Add(value.Key, Data.ReadCString()); break;
default:
throw new FormatException("Invalid GameEvent type");
}
}
ws.Listeners.GameEvent.Invoke(ws, retVal);
}
}
}

@ -0,0 +1,38 @@
namespace TF2Net.NetMessages
{
enum NetMessageType : byte
{
NET_NOOP = 0,
NET_DISCONNECT = 1,
NET_FILE = 2,
NET_TICK = 3,
NET_STRINGCMD = 4,
NET_SETCONVAR = 5,
NET_SIGNONSTATE = 6,
SVC_PRINT = 7,
SVC_SERVERINFO = 8,
SVC_SENDTABLE = 9,
SVC_CLASSINFO = 10,
SVC_SETPAUSE = 11,
SVC_CREATESTRINGTABLE = 12,
SVC_UPDATESTRINGTABLE = 13,
SVC_VOICEINIT = 14,
SVC_VOICEDATA = 15,
// 16
SVC_SOUND = 17,
SVC_SETVIEW = 18,
SVC_FIXANGLE = 19,
SVC_CROSSHAIRANGLE = 20,
SVC_BSPDECAL = 21,
// 22
SVC_USERMESSAGE = 23,
SVC_ENTITYMESSAGE = 24,
SVC_GAMEEVENT = 25,
SVC_PACKETENTITIES = 26,
SVC_TEMPENTITIES = 27,
SVC_PREFETCH = 28,
SVC_MENU = 29,
SVC_GAMEEVENTLIST = 30,
SVC_GETCVARVALUE = 31,
}
}

@ -0,0 +1,250 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using BitSet;
using TF2Net.Data;
using TF2Net.Entities;
using TF2Net.NetMessages.Shared;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetPacketEntitiesMessage : INetMessage
{
const int DELTA_INDEX_BITS = 32;
const int DELTA_SIZE_BITS = 20;
public uint MaxEntries { get; set; }
public uint UpdatedEntries { get; set; }
public bool IsDelta { get; set; }
public bool UpdateBaseline { get; set; }
public BaselineIndex? Baseline { get; set; }
public int? DeltaFrom { get; set; }
public BitStream Data { get; set; }
public string Description
{
get
{
return string.Format("svc_PacketEntities: delta {0}, max {1}, changed {2},{3} bytes {4}",
DeltaFrom, MaxEntries, UpdatedEntries,
UpdateBaseline ? " BL update," : "",
BitInfo.BitsToBytes(Data.Length));
}
}
public void ReadMsg(BitStream stream)
{
MaxEntries = stream.ReadUInt(SourceConstants.MAX_EDICT_BITS);
IsDelta = stream.ReadBool();
if (IsDelta)
DeltaFrom = stream.ReadInt(DELTA_INDEX_BITS);
Baseline = (BaselineIndex)stream.ReadByte(1);
UpdatedEntries = stream.ReadUInt(SourceConstants.MAX_EDICT_BITS);
ulong bitCount = stream.ReadULong(DELTA_SIZE_BITS);
UpdateBaseline = stream.ReadBool();
Data = stream.Subsection(stream.Cursor, stream.Cursor + bitCount);
stream.Seek(bitCount, SeekOrigin.Current);
}
static uint ReadUBitInt(BitStream stream)
{
uint ret = stream.ReadUInt(6);
switch (ret & (16 | 32))
{
case 16:
ret = (ret & 15) | (stream.ReadUInt(4) << 4);
break;
case 32:
ret = (ret & 15) | (stream.ReadUInt(8) << 4);
break;
case 48:
ret = (ret & 15) | (stream.ReadUInt(32 - 4) << 4);
break;
}
return ret;
}
public void ApplyWorldState(WorldState ws)
{
if (ws.SignonState.State == ConnectionState.Spawn)
{
if (!IsDelta)
{
// We are done with signon sequence.
ws.SignonState.State = ConnectionState.Full;
}
else
throw new InvalidOperationException("eceived delta packet entities while spawing!");
}
//ClientFrame newFrame = new ClientFrame(ws.Tick);
//ws.Frames.Add(newFrame);
//ClientFrame oldFrame = null;
if (IsDelta)
{
if (ws.Tick == (ulong)DeltaFrom.Value)
throw new InvalidDataException("Update self-referencing");
//oldFrame = ws.Frames.Single(f => f.ServerTick == (ulong)DeltaFrom.Value);
}
if (UpdateBaseline)
{
if (Baseline.Value == BaselineIndex.Baseline0)
{
ws.InstanceBaselines[(int)BaselineIndex.Baseline1] = ws.InstanceBaselines[(int)BaselineIndex.Baseline0];
ws.InstanceBaselines[(int)BaselineIndex.Baseline0] = new IList<SendProp>[SourceConstants.MAX_EDICTS];
}
else if (Baseline.Value == BaselineIndex.Baseline1)
{
ws.InstanceBaselines[(int)BaselineIndex.Baseline0] = ws.InstanceBaselines[(int)BaselineIndex.Baseline1];
ws.InstanceBaselines[(int)BaselineIndex.Baseline1] = new IList<SendProp>[SourceConstants.MAX_EDICTS];
}
else
throw new ArgumentOutOfRangeException(nameof(Baseline));
}
Data.Seek(0, SeekOrigin.Begin);
int newEntity = -1;
for (int i = 0; i < UpdatedEntries; i++)
{
newEntity += 1 + (int)EntityCoder.ReadUBitVar(Data);
// Leave PVS flag
if (!Data.ReadBool())
{
// Enter PVS flag
if (Data.ReadBool())
{
Entity e = ReadEnterPVS(ws, Data, (uint)newEntity);
EntityCoder.ApplyEntityUpdate(e, Data);
if (ws.Entities[e.Index] != null && !ReferenceEquals(e, ws.Entities[e.Index]))
ws.Entities[e.Index].Dispose();
ws.Entities[e.Index] = e;
if (UpdateBaseline)
ws.InstanceBaselines[Baseline.Value == BaselineIndex.Baseline0 ? 1 : 0][e.Index] = new List<SendProp>(e.Properties.Select(sp => sp.Clone()));
e.InPVS = true;
}
else
{
// Preserve/update
Entity e = ws.Entities[(uint)newEntity];// ws.Entities.Single(ent => ent.Index == newEntity);
EntityCoder.ApplyEntityUpdate(e, Data);
}
}
else
{
bool shouldDelete = Data.ReadBool();
Entity e = ws.Entities[newEntity];
if (e != null)
e.InPVS = false;
ReadLeavePVS(ws, newEntity, shouldDelete);
}
}
if (IsDelta)
{
// Read explicit deletions
while (Data.ReadBool())
{
uint ent = Data.ReadUInt(SourceConstants.MAX_EDICT_BITS);
//Debug.Assert(ws.Entities[ent] != null);
if (ws.Entities[ent] != null)
ws.Entities[ent].Dispose();
ws.Entities[ent] = null;
}
}
//Console.WriteLine("Parsed {0}", Description);
}
class SendPropDefinitionComparer : IEqualityComparer<SendProp>
{
public static SendPropDefinitionComparer Instance { get; } = new SendPropDefinitionComparer();
private SendPropDefinitionComparer() { }
public bool Equals(SendProp x, SendProp y)
{
return x.Definition.Equals(y.Definition);
}
public int GetHashCode(SendProp obj)
{
return obj.Definition.GetHashCode();
}
}
Entity ReadEnterPVS(WorldState ws, BitStream stream, uint entityIndex)
{
ServerClass serverClass = ws.ServerClasses[(int)stream.ReadUInt(ws.ClassBits)];
SendTable networkTable = ws.SendTables.Single(st => st.NetTableName == serverClass.DatatableName);
uint serialNumber = stream.ReadUInt(SourceConstants.NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS);
Entity e;
{
Entity existing = ws.Entities[entityIndex];
e = (existing == null || existing.SerialNumber != serialNumber) ?
new Entity(ws, serverClass, networkTable, entityIndex, serialNumber) :
existing;
}
var decodedBaseline = ws.InstanceBaselines[(int)Baseline.Value][entityIndex];
if (decodedBaseline != null)
{
var propertiesToAdd =
decodedBaseline
.Except(e.Properties, SendPropDefinitionComparer.Instance)
.Select(sp => sp.Clone(e));
foreach (var p2a in propertiesToAdd)
e.AddProperty(p2a);
}
else
{
BitStream baseline = ws.StaticBaselines.SingleOrDefault(bl => bl.Key == e.Class).Value;
if (baseline != null)
{
baseline.Cursor = 0;
EntityCoder.ApplyEntityUpdate(e, baseline);
Debug.Assert((baseline.Length - baseline.Cursor) < 8);
}
}
return e;
}
void ReadLeavePVS(WorldState ws, int newEntity, bool delete)
{
if (delete)
{
//Debug.Assert(ws.Entities[newEntity] != null);
if (ws.Entities[newEntity] != null)
ws.Entities[newEntity].Dispose();
ws.Entities[newEntity] = null;
}
}
}
}

@ -0,0 +1,38 @@
using System;
using System.Diagnostics;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetPrefetchMessage : INetMessage
{
public enum PrefetchType
{
Sound = 0,
}
public PrefetchType Type { get; set; }
public int SoundIndex { get; set; }
public string Description
{
get
{
return string.Format("svc_Prefetch: type {0} index {1}", Type, SoundIndex);
}
}
public void ReadMsg(BitStream stream)
{
Type = PrefetchType.Sound;
SoundIndex = stream.ReadInt(SourceConstants.MAX_SOUND_INDEX_BITS);
}
public void ApplyWorldState(WorldState ws)
{
}
}
}

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetPrintMessage : INetMessage
{
public string Message { get; set; }
public string Description
{
get
{
return string.Format("svc_Print: \"{0}\"", Message);
}
}
public void ReadMsg(BitStream stream)
{
Message = stream.ReadCString();
}
public void ApplyWorldState(WorldState ws)
{
ws.Listeners.ServerTextMessage.Invoke(ws, Message);
}
}
}

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetServerInfoMessage : INetMessage
{
public ServerInfo Info { get; set; }
public string Description
{
get
{
return string.Format("svc_ServerInfo: game \"{0}\", map \"{1}\", max {2}",
Info.GameDirectory, Info.MapName, Info.MaxClients);
}
}
public void ReadMsg(BitStream stream)
{
Info = new ServerInfo();
Info.Protocol = stream.ReadShort();
Info.ServerCount = stream.ReadInt();
Info.IsHLTV = stream.ReadBool();
Info.IsDedicated = stream.ReadBool();
Info.ClientCRC = stream.ReadUInt();
Info.MaxClasses = stream.ReadUShort();
// Unknown
stream.Seek(16 * 8, System.IO.SeekOrigin.Current);
Info.PlayerSlot = stream.ReadByte();
Info.MaxClients = stream.ReadByte();
Info.TickInterval = stream.ReadSingle();
switch (stream.ReadChar())
{
case 'l':
case 'L':
Info.OS = ServerInfo.OperatingSystem.Linux;
break;
case 'w':
case 'W':
Info.OS = ServerInfo.OperatingSystem.Windows;
break;
default:
Info.OS = ServerInfo.OperatingSystem.Unknown;
break;
}
Info.GameDirectory = stream.ReadCString();
Info.MapName = stream.ReadCString();
Info.SkyName = stream.ReadCString();
Info.Hostname = stream.ReadCString();
// Unknown
stream.Seek(1, System.IO.SeekOrigin.Current);
}
public void ApplyWorldState(WorldState ws)
{
ws.ServerInfo = Info;
ws.Listeners.ServerInfoLoaded.Invoke(ws);
}
}
}

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetSetConvarMessage : INetMessage
{
public IList<KeyValuePair<string, string>> Cvars { get; set; }
public string Description
{
get
{
return string.Format("net_SetConVar: {0} cvars, \"{1}\"=\"{2}\"",
Cvars.Count, Cvars[0].Key, Cvars[0].Value);
}
}
public ulong Size
{
get
{
throw new NotImplementedException();
}
}
public void WriteMsg(byte[] buffer, ref ulong bitOffset)
{
throw new NotImplementedException();
}
public void ReadMsg(BitStream stream)
{
byte count = stream.ReadByte();
Cvars = new List<KeyValuePair<string, string>>(count);
for (int i = 0; i < count; i++)
{
string name = stream.ReadCString();
string value = stream.ReadCString();
Cvars.Add(new KeyValuePair<string, string>(name, value));
}
}
public void ApplyWorldState(WorldState ws)
{
foreach (var cvar in Cvars)
{
ws.ConVars[cvar.Key] = cvar.Value;
ws.Listeners.ServerSetConVar.Invoke(ws, cvar);
}
}
}
}

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetSetPausedMessage : INetMessage
{
public bool Paused { get; set; }
public string Description
{
get
{
return string.Format("svc_SetPause: {0}", Paused ? "Paused" : "Unpaused");
}
}
public void ReadMsg(BitStream stream)
{
Paused = stream.ReadBool();
}
public void ApplyWorldState(WorldState ws)
{
throw new NotImplementedException();
}
}
}

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetSetViewMessage : INetMessage
{
public ushort EntityIndex { get; set; }
public string Description
{
get
{
return string.Format("svc_SetView: view entity {0}", EntityIndex);
}
}
public void ReadMsg(BitStream stream)
{
EntityIndex = stream.ReadUShort(SourceConstants.MAX_EDICT_BITS);
}
public void ApplyWorldState(WorldState ws)
{
ws.ViewEntity = EntityIndex;
}
}
}

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetSignonStateMessage : INetMessage
{
public SignonState State { get; set; }
public string Description
{
get
{
return string.Format("net_SignonState: state {0}, count {1}", State.State, State.SpawnCount);
}
}
public void ReadMsg(BitStream stream)
{
State = new SignonState();
State.State = (ConnectionState)stream.ReadByte();
State.SpawnCount = stream.ReadInt();
}
public void ApplyWorldState(WorldState ws)
{
ws.SignonState = State;
}
}
}

@ -0,0 +1,54 @@
using System;
using System.Diagnostics;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetSoundMessage : INetMessage
{
const int SOUND_COUNT_BITS = 8;
const int RELIABLE_SIZE_BITS = 8;
const int UNRELIABLE_SIZE_BITS = 16;
public bool Reliable { get; set; }
public int SoundCount { get; set; }
public BitStream Data { get; set; }
public string Description
{
get
{
return string.Format("svc_Sounds: number {0},{1} bytes {2}",
SoundCount, Reliable ? " reliable," : "", BitInfo.BitsToBytes(Data.Length));
}
}
public void ReadMsg(BitStream stream)
{
Reliable = stream.ReadBool();
ulong bitCount;
if (Reliable)
{
SoundCount = 1;
bitCount = stream.ReadULong(RELIABLE_SIZE_BITS);
}
else
{
SoundCount = stream.ReadInt(SOUND_COUNT_BITS);
bitCount = stream.ReadULong(UNRELIABLE_SIZE_BITS);
}
Data = stream.Subsection(stream.Cursor, stream.Cursor + bitCount);
stream.Seek(bitCount, System.IO.SeekOrigin.Current);
}
public void ApplyWorldState(WorldState ws)
{
//throw new NotImplementedException();
}
}
}

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetStringCmdMessage : INetMessage
{
public string Command { get; set; }
public string Description
{
get
{
return string.Format("net_StringCmd: \"{0}\"", Command);
}
}
public void ReadMsg(BitStream stream)
{
Command = stream.ReadCString();
}
public void ApplyWorldState(WorldState ws)
{
ws.Listeners.ServerConCommand.Invoke(ws, Command);
}
}
}

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitSet;
using TF2Net.Data;
using TF2Net.Entities;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetTempEntityMessage : INetMessage
{
public int EntryCount { get; set; }
public BitStream Data { get; set; }
public string Description
{
get
{
return string.Format("svc_TempEntities: number {0}, bytes {1}", EntryCount, BitInfo.BitsToBytes(Data.Length));
}
}
public void ReadMsg(BitStream stream)
{
EntryCount = stream.ReadInt(SourceConstants.EVENT_INDEX_BITS);
ulong bitCount = stream.ReadVarUInt();
Data = stream.Subsection(stream.Cursor, stream.Cursor + bitCount);
stream.Seek(bitCount, System.IO.SeekOrigin.Current);
}
public void ApplyWorldState(WorldState ws)
{
List<IBaseEntity> tempents = new List<IBaseEntity>();
{
BitStream local = Data.Clone();
local.Cursor = 0;
TempEntity e = null;
for (int i = 0; i < EntryCount; i++)
{
double delay = 0;
if (local.ReadBool())
delay = local.ReadInt(8) / 100.0;
if (local.ReadBool())
{
uint classID = local.ReadUInt(ws.ClassBits);
ServerClass serverClass = ws.ServerClasses[(int)classID - 1];
SendTable sendTable = ws.SendTables.Single(st => st.NetTableName == serverClass.DatatableName);
var flattened = sendTable.FlattenedProps;
e = new TempEntity(ws, serverClass, sendTable);
EntityCoder.ApplyEntityUpdate(e, local);
tempents.Add(e);
}
else
{
Debug.Assert(e != null);
EntityCoder.ApplyEntityUpdate(e, local);
}
}
}
foreach (IBaseEntity te in tempents)
{
ws.Listeners.TempEntityCreated.Invoke(te);
}
}
[DebuggerDisplay("{Class,nq}")]
class TempEntity : IEntity
{
readonly List<SendProp> m_Properties = new List<SendProp>();
public IReadOnlyList<SendProp> Properties { get { return m_Properties; } }
public SingleEvent<Action<IPropertySet>> PropertiesUpdated { get; } = new SingleEvent<Action<IPropertySet>>();
public SingleEvent<Action<SendProp>> PropertyAdded { get; } = new SingleEvent<Action<SendProp>>();
public WorldState World { get; }
public ServerClass Class { get; }
public SendTable NetworkTable { get; }
public TempEntity(WorldState ws, ServerClass sClass, SendTable table)
{
World = ws;
Class = sClass;
NetworkTable = table;
}
public void AddProperty(SendProp newProp)
{
Debug.Assert(!m_Properties.Any(p => ReferenceEquals(p.Definition, newProp.Definition)));
Debug.Assert(ReferenceEquals(newProp.Entity, this));
m_Properties.Add(newProp);
PropertyAdded.Invoke(newProp);
}
}
}
}

@ -0,0 +1,44 @@
using System;
using System.Diagnostics;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetTickMessage : INetMessage
{
const int TICK_BITS = 32;
const int FLOAT_BITS = 16;
const double NET_TICK_SCALEUP = 100000.0;
public uint Tick { get; set; }
public double HostFrameTime { get; set; }
public double HostFrameTimeStdDev { get; set; }
public string Description
{
get
{
return string.Format("net_Tick: tick {0}", Tick);
}
}
public void ReadMsg(BitStream stream)
{
Tick = stream.ReadUInt(TICK_BITS);
HostFrameTime = stream.ReadUInt(FLOAT_BITS) / NET_TICK_SCALEUP;
HostFrameTimeStdDev = stream.ReadUInt(FLOAT_BITS) / NET_TICK_SCALEUP;
}
public void ApplyWorldState(WorldState ws)
{
ws.Tick = Tick;
ws.LastFrameTime = HostFrameTime;
ws.LastFrameTimeStdDev = HostFrameTimeStdDev;
ws.Listeners.NewTick.Invoke(ws);
}
}
}

@ -0,0 +1,54 @@
using System;
using System.Diagnostics;
using BitSet;
using TF2Net.Data;
using TF2Net.NetMessages.Shared;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetUpdateStringTableMessage : INetMessage
{
const int MAX_TABLE_BITS = 5;
const int MAX_TABLES = (1 << MAX_TABLE_BITS);
const int DATA_LENGTH_BITS = 20;
const int CHANGED_ENTRIES_BITS = 16;
public int TableID { get; set; }
public int ChangedEntries { get; set; }
public BitStream Data { get; set; }
public string Description
{
get
{
return string.Format("svc_UpdateStringTable: table {0}, changed {1}, bytes {2}",
TableID, ChangedEntries, BitInfo.BitsToBytes(Data.Length));
}
}
public void ReadMsg(BitStream stream)
{
TableID = stream.ReadInt(MAX_TABLE_BITS);
bool multipleChanged = stream.ReadBool();
if (!multipleChanged)
ChangedEntries = 1;
else
ChangedEntries = stream.ReadInt(CHANGED_ENTRIES_BITS);
ulong bitCount = stream.ReadULong(DATA_LENGTH_BITS);
Data = stream.Subsection(stream.Cursor, stream.Cursor + bitCount);
stream.Seek(bitCount, System.IO.SeekOrigin.Current);
}
public void ApplyWorldState(WorldState ws)
{
StringTable found = ws.StringTables[TableID];
StringTableParser.ParseUpdate(Data, found, (ushort)ChangedEntries);
ws.Listeners.StringTableUpdated.Invoke(ws, found);
}
}
}

@ -0,0 +1,42 @@
using System;
using System.Diagnostics;
using System.Linq;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetUsrMsgMessage : INetMessage
{
const int MAX_USER_MSG_TYPE_BITS = 8;
public UserMessageType Type { get; set; }
public BitStream Data { get; set; }
public string Description
{
get
{
return string.Format("svc_UserMessage: type {0}, bytes {1}", Type, BitInfo.BitsToBytes(Data.Length));
}
}
public void ReadMsg(BitStream stream)
{
Type = (UserMessageType)stream.ReadInt(MAX_USER_MSG_TYPE_BITS);
Debug.Assert(Enum.GetValues(typeof(UserMessageType)).Cast<UserMessageType>().Contains(Type));
ulong bitCount = stream.ReadULong(SourceConstants.MAX_USER_MSG_LENGTH_BITS);
Data = stream.Subsection(stream.Cursor, stream.Cursor + bitCount);
stream.Seek(bitCount, System.IO.SeekOrigin.Current);
}
public void ApplyWorldState(WorldState ws)
{
return;
//throw new NotImplementedException();
}
}
}

@ -0,0 +1,39 @@
using System;
using System.Diagnostics;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetVoiceDataMessage : INetMessage
{
public byte ClientIndex { get; set; }
public bool Proximity { get; set; }
public BitStream Data { get; set; }
public string Description
{
get
{
return string.Format("svc_VoiceData: client {0}, bytes {1}",
ClientIndex, BitInfo.BitsToBytes(Data.Length));
}
}
public void ReadMsg(BitStream stream)
{
ClientIndex = stream.ReadByte();
Proximity = stream.ReadByte() != 0;
ulong bitCount = stream.ReadULong(16);
Data = stream.Subsection(stream.Cursor, stream.Cursor + bitCount);
stream.Seek(bitCount, System.IO.SeekOrigin.Current);
}
public void ApplyWorldState(WorldState ws)
{
}
}
}

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BitSet;
using TF2Net.Data;
namespace TF2Net.NetMessages
{
[DebuggerDisplay("{Description, nq}")]
public class NetVoiceInitMessage : INetMessage
{
public string VoiceCodec { get; set; }
public byte Quality { get; set; }
public string Description
{
get
{
return string.Format("svc_VoiceInit: codec \"{0}\", qualitty {1}",
VoiceCodec, Quality);
}
}
public void ReadMsg(BitStream stream)
{
VoiceCodec = stream.ReadCString();
Quality = stream.ReadByte();
}
public void ApplyWorldState(WorldState ws)
{
//throw new NotImplementedException();
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save