Compare commits

...

5 Commits
main ... csharp

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

@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/src/bin/Debug/net7.0/src.dll",
"args": [],
"cwd": "${workspaceFolder}/src",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}

41
.vscode/tasks.json vendored

@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/src/src.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/src/src.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/src/src.csproj"
],
"problemMatcher": "$msCompile"
}
]
}

@ -22,3 +22,10 @@ Analysis of TF2 Demo files. To help me get more _data driven_ approaches to get
Parsing the demo files. Hopefully once, maybe multiple times. Parsing the demo files. Hopefully once, maybe multiple times.
Only intrested after the start of the official match. Warmup should not count. 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
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();
}
}
}
}

@ -0,0 +1,158 @@
// 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
const string _DemoPath = "/home/sherwood/projects/tf2DemoAnalysis/demos/";
var demoFilePaths = Directory.GetFiles(_DemoPath, "*.dem");
foreach(var currentDemoPath in demoFilePaths){
var demo = File.OpenRead(Path.Combine(_DemoPath, currentDemoPath));
var demoInfo = new DemoReader(demo);
Console.WriteLine("Successfully read demo!");
}
namespace DemoParser{
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];
stream.Read(buffer, 0, 8);
if(Encoding.ASCII.GetString(buffer) != "HL2DEMO\0"){
throw new Exception($"DemoFile not valid! Filestamp: {Encoding.ASCII.GetString(buffer)}");
}
return new DemoHeader(stream);
}
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(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(Stream stream, int maxLength, Encoding encoding,int offset = 0){
var buffer = new Byte[maxLength];
var readBytes = stream.Read(buffer, offset, maxLength);
var readString = encoding.GetString(buffer).Split('\0')[0];
return readString;
}
}
}

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

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

Loading…
Cancel
Save