Public version 1.0
This commit is contained in:
commit
ccd4b42ecb
331 changed files with 33996 additions and 0 deletions
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
331
.gitignore
vendored
Normal file
331
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
## 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/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Bb]uilds/
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# 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
|
||||
|
||||
# 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/
|
||||
**/Properties/launchSettings.json
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.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
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# 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
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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 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/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# 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/
|
||||
37
SRMP.sln
Normal file
37
SRMP.sln
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.28307.1267
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SRMP", "SRMP\SRMP.csproj", "{E1BF7CDA-F2AE-4042-A992-DCBF62E79238}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
SRML|Any CPU = SRML|Any CPU
|
||||
SRML NoVer|Any CPU = SRML NoVer|Any CPU
|
||||
Standalone|Any CPU = Standalone|Any CPU
|
||||
Standalone NoVer|Any CPU = Standalone NoVer|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.SRML|Any CPU.ActiveCfg = SRML|Any CPU
|
||||
{E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.SRML|Any CPU.Build.0 = SRML|Any CPU
|
||||
{E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.SRML NoVer|Any CPU.ActiveCfg = SRML NoVer|Any CPU
|
||||
{E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.SRML NoVer|Any CPU.Build.0 = SRML NoVer|Any CPU
|
||||
{E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.Standalone|Any CPU.ActiveCfg = Standalone|Any CPU
|
||||
{E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.Standalone|Any CPU.Build.0 = Standalone|Any CPU
|
||||
{E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.Standalone NoVer|Any CPU.ActiveCfg = Standalone NoVer|Any CPU
|
||||
{E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.Standalone NoVer|Any CPU.Build.0 = Standalone NoVer|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {6E1CE44C-14A2-4691-A970-B971E4861A66}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
188
SRMP/ChatUI.cs
Normal file
188
SRMP/ChatUI.cs
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SRMultiplayer;
|
||||
using SRMultiplayer.Networking;
|
||||
using SRMultiplayer.Packets;
|
||||
|
||||
public class ChatUI : SRSingleton<ChatUI>
|
||||
{
|
||||
private bool openChat;
|
||||
private string message;
|
||||
private float fadeTime;
|
||||
private List<ChatMessage> messages = new List<ChatMessage>();
|
||||
private Vector2 chatScroll;
|
||||
|
||||
public class ChatMessage
|
||||
{
|
||||
public string Text;
|
||||
public float FadeTime;
|
||||
public DateTime Time;
|
||||
|
||||
public ChatMessage(string msg)
|
||||
{
|
||||
Text = msg;
|
||||
FadeTime = 10f;
|
||||
Time = DateTime.Now;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!Globals.IsMultiplayer)
|
||||
{
|
||||
openChat = false;
|
||||
message = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (Input.GetKeyUp(KeyCode.Return))
|
||||
{
|
||||
StartCoroutine(FocusChat());
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (!Globals.IsMultiplayer) return;
|
||||
|
||||
if (openChat)
|
||||
{
|
||||
GUILayout.BeginArea(new Rect(20, Screen.height / 2, 500, 300), GUI.skin.box);
|
||||
chatScroll = GUILayout.BeginScrollView(chatScroll);
|
||||
var skin = GUI.skin.box;
|
||||
skin.wordWrap = true;
|
||||
skin.alignment = TextAnchor.MiddleLeft;
|
||||
foreach (var msg in messages)
|
||||
{
|
||||
GUILayout.Label(wrapString(msg.Text, 490), skin, GUILayout.MaxWidth(490));
|
||||
}
|
||||
GUILayout.EndScrollView();
|
||||
GUI.SetNextControlName("ChatInput");
|
||||
message = GUILayout.TextField(message);
|
||||
GUILayout.EndArea();
|
||||
|
||||
GUI.FocusControl("ChatInput");
|
||||
|
||||
Event e = Event.current;
|
||||
if (e.rawType == EventType.KeyUp && e.keyCode == KeyCode.Return)
|
||||
{
|
||||
openChat = !openChat;
|
||||
if (!string.IsNullOrWhiteSpace(message))
|
||||
{
|
||||
if (Globals.IsServer)
|
||||
{
|
||||
AddChatMessage(Globals.Username + ": " + message);
|
||||
new PacketPlayerChat()
|
||||
{
|
||||
message = Globals.Username + ": " + message
|
||||
}.Send();
|
||||
}
|
||||
else
|
||||
{
|
||||
new PacketPlayerChat()
|
||||
{
|
||||
message = message
|
||||
}.Send();
|
||||
}
|
||||
message = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.BeginArea(new Rect(20, Screen.height / 2, 500, 300));
|
||||
chatScroll = GUILayout.BeginScrollView(chatScroll);
|
||||
var skin = GUI.skin.box;
|
||||
skin.wordWrap = true;
|
||||
skin.alignment = TextAnchor.MiddleLeft;
|
||||
foreach (var msg in messages)
|
||||
{
|
||||
if (msg.FadeTime > 0f)
|
||||
{
|
||||
msg.FadeTime -= Time.deltaTime;
|
||||
var c = GUI.color;
|
||||
c.a = (msg.FadeTime / 5f);
|
||||
GUI.color = c;
|
||||
GUILayout.Label(wrapString(msg.Text, 490), skin, GUILayout.MaxWidth(490));
|
||||
}
|
||||
}
|
||||
GUILayout.EndScrollView();
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddChatMessage(string message)
|
||||
{
|
||||
fadeTime = 10f;
|
||||
messages.Add(new ChatMessage(message));
|
||||
chatScroll = new Vector2(0, 100000);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
messages.Clear();
|
||||
}
|
||||
|
||||
private IEnumerator FocusChat()
|
||||
{
|
||||
yield return new WaitForEndOfFrame();
|
||||
yield return new WaitForEndOfFrame();
|
||||
openChat = !openChat;
|
||||
GUI.FocusControl("ChatInput");
|
||||
}
|
||||
|
||||
string wrapString(string msg, int width)
|
||||
{
|
||||
string[] words = msg.Split(" "[0]);
|
||||
string retVal = ""; //returning string
|
||||
string NLstr = ""; //leftover string on new line
|
||||
for (int index = 0; index < words.Length; index++)
|
||||
{
|
||||
string word = words[index].Trim();
|
||||
//if word exceeds width
|
||||
if (words[index].Length >= width + 2)
|
||||
{
|
||||
string[] temp = new string[5];
|
||||
int i = 0;
|
||||
while (words[index].Length > width)
|
||||
{ //word exceeds width, cut it at widrh
|
||||
temp[i] = words[index].Substring(0, width) + "\n"; //cut the word at width
|
||||
words[index] = words[index].Substring(width); //keep remaining word
|
||||
i++;
|
||||
if (words[index].Length <= width)
|
||||
{ //the balance is smaller than width
|
||||
temp[i] = words[index];
|
||||
NLstr = temp[i];
|
||||
}
|
||||
}
|
||||
retVal += "\n";
|
||||
for (int x = 0; x < i + 1; x++)
|
||||
{ //loops through temp array
|
||||
retVal = retVal + temp[x];
|
||||
}
|
||||
}
|
||||
else if (index == 0)
|
||||
{
|
||||
retVal = words[0];
|
||||
NLstr = retVal;
|
||||
}
|
||||
else if (index > 0)
|
||||
{
|
||||
if (NLstr.Length + words[index].Length <= width)
|
||||
{
|
||||
retVal = retVal + " " + words[index];
|
||||
NLstr = NLstr + " " + words[index]; //add the current line length
|
||||
}
|
||||
else if (NLstr.Length + words[index].Length > width)
|
||||
{
|
||||
retVal = retVal + "\n" + words[index];
|
||||
NLstr = words[index]; //reset the line length
|
||||
print("newline! at word " + words[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
242
SRMP/Compression.cs
Normal file
242
SRMP/Compression.cs
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SRMultiplayer
|
||||
{
|
||||
/// <summary>
|
||||
/// Functions to Compress Quaternions and Floats
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Uncompressed Quaternion = 32 * 4 = 128 bits => send 16 bytes
|
||||
///
|
||||
/// <para>
|
||||
/// Quaternion is always normalized so we drop largest value and re-calculate it.
|
||||
/// We can encode which one is the largest using 2 bits
|
||||
/// <code>
|
||||
/// x^2 + y^2 + z^2 + w^2 = 1
|
||||
/// </code>
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// 2nd largest value has max size of 1/sqrt(2)
|
||||
/// We can encode the smallest three components in [-1/sqrt(2),+1/sqrt(2)] instead of [-1,+1]
|
||||
/// <code>
|
||||
/// c^2 + c^2 + 0 + 0 = 1
|
||||
/// </code>
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Sign of largest value doesn't matter
|
||||
/// <code>
|
||||
/// Q * vec3 == (-Q) * vec3
|
||||
/// </code>
|
||||
/// </para>
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <listheader><description>
|
||||
/// RotationPrecision <br/>
|
||||
/// <code>
|
||||
/// 2/sqrt(2) / (2^bitCount - 1)
|
||||
/// </code>
|
||||
/// </description></listheader>
|
||||
///
|
||||
/// <item><description>
|
||||
/// rotation precision +-0.00138 in range [-1,+1]
|
||||
/// <code>
|
||||
/// 10 bits per value
|
||||
/// 2 + 10 * 3 = 32 bits => send 4 bytes
|
||||
/// </code>
|
||||
/// </description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>
|
||||
/// Links for more info:
|
||||
/// <br/><see href="https://youtu.be/Z9X4lysFr64">GDC Talk</see>
|
||||
/// <br/><see href="https://gafferongames.com/post/snapshot_compression/">Post on Snapshot Compression</see>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class Compression
|
||||
{
|
||||
const float QuaternionMinValue = -1f / 1.414214f; // 1/ sqrt(2)
|
||||
const float QuaternionMaxValue = 1f / 1.414214f;
|
||||
|
||||
const int QuaternionBitLength = 10;
|
||||
// same as Mathf.Pow(2, targetBitLength) - 1
|
||||
const uint QuaternionUintRange = (1 << QuaternionBitLength) - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Used to Compress Quaternion into 4 bytes
|
||||
/// </summary>
|
||||
public static uint CompressQuaternion(Quaternion value)
|
||||
{
|
||||
// make sure value is normalized (don't trust user given value, and math here assumes normalized)
|
||||
value = value.normalized;
|
||||
|
||||
int largestIndex = FindLargestIndex(value);
|
||||
Vector3 small = GetSmallerDimensions(largestIndex, value);
|
||||
// largest needs to be positive to be calculated by reader
|
||||
// if largest is negative flip sign of others because Q = -Q
|
||||
if (value[largestIndex] < 0)
|
||||
{
|
||||
small *= -1;
|
||||
}
|
||||
|
||||
uint a = ScaleToUInt(small.x, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange);
|
||||
uint b = ScaleToUInt(small.y, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange);
|
||||
uint c = ScaleToUInt(small.z, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange);
|
||||
|
||||
// pack each 10 bits and extra 2 bits into uint32
|
||||
uint packed = a | b << 10 | c << 20 | (uint)largestIndex << 30;
|
||||
|
||||
return packed;
|
||||
}
|
||||
|
||||
internal static int FindLargestIndex(Quaternion q)
|
||||
{
|
||||
int index = 0;
|
||||
float current = 0;
|
||||
|
||||
// check each value to see which one is largest (ignoring +-)
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
float next = Mathf.Abs(q[i]);
|
||||
if (next > current)
|
||||
{
|
||||
index = i;
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
static Vector3 GetSmallerDimensions(int largestIndex, Quaternion value)
|
||||
{
|
||||
float x = value.x;
|
||||
float y = value.y;
|
||||
float z = value.z;
|
||||
float w = value.w;
|
||||
|
||||
switch (largestIndex)
|
||||
{
|
||||
case 0:
|
||||
return new Vector3(y, z, w);
|
||||
case 1:
|
||||
return new Vector3(x, z, w);
|
||||
case 2:
|
||||
return new Vector3(x, y, w);
|
||||
case 3:
|
||||
return new Vector3(x, y, z);
|
||||
default:
|
||||
throw new IndexOutOfRangeException("Invalid Quaternion index!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Used to read a Compressed Quaternion from 4 bytes
|
||||
/// <para>Quaternion is normalized</para>
|
||||
/// </summary>
|
||||
public static Quaternion DecompressQuaternion(uint packed)
|
||||
{
|
||||
// 10 bits
|
||||
const uint mask = 0b11_1111_1111;
|
||||
Quaternion result;
|
||||
|
||||
|
||||
uint a = packed & mask;
|
||||
uint b = (packed >> 10) & mask;
|
||||
uint c = (packed >> 20) & mask;
|
||||
uint largestIndex = (packed >> 30) & mask;
|
||||
|
||||
float x = ScaleFromUInt(a, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange);
|
||||
float y = ScaleFromUInt(b, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange);
|
||||
float z = ScaleFromUInt(c, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange);
|
||||
|
||||
Vector3 small = new Vector3(x, y, z);
|
||||
result = FromSmallerDimensions(largestIndex, small);
|
||||
return result;
|
||||
}
|
||||
|
||||
static Quaternion FromSmallerDimensions(uint largestIndex, Vector3 smallest)
|
||||
{
|
||||
float a = smallest.x;
|
||||
float b = smallest.y;
|
||||
float c = smallest.z;
|
||||
|
||||
float largest = Mathf.Sqrt(1 - a * a - b * b - c * c);
|
||||
switch (largestIndex)
|
||||
{
|
||||
case 0:
|
||||
return new Quaternion(largest, a, b, c).normalized;
|
||||
case 1:
|
||||
return new Quaternion(a, largest, b, c).normalized;
|
||||
case 2:
|
||||
return new Quaternion(a, b, largest, c).normalized;
|
||||
case 3:
|
||||
return new Quaternion(a, b, c, largest).normalized;
|
||||
default:
|
||||
throw new IndexOutOfRangeException("Invalid Quaternion index!");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Scales float from minFloat->maxFloat to minUint->maxUint
|
||||
/// <para>values out side of minFloat/maxFloat will return either 0 or maxUint</para>
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="minFloat"></param>
|
||||
/// <param name="maxFloat"></param>
|
||||
/// <param name="minUint">should be a power of 2, can be 0</param>
|
||||
/// <param name="maxUint">should be a power of 2, for example 1 << 8 for value to take up 8 bytes</param>
|
||||
/// <returns></returns>
|
||||
public static uint ScaleToUInt(float value, float minFloat, float maxFloat, uint minUint, uint maxUint)
|
||||
{
|
||||
// if out of range return min/max
|
||||
if (value > maxFloat) { return maxUint; }
|
||||
if (value < minFloat) { return minUint; }
|
||||
|
||||
float rangeFloat = maxFloat - minFloat;
|
||||
uint rangeUint = maxUint - minUint;
|
||||
|
||||
// scale value to 0->1 (as float)
|
||||
float valueRelative = (value - minFloat) / rangeFloat;
|
||||
// scale value to uMin->uMax
|
||||
float outValue = valueRelative * rangeUint + minUint;
|
||||
|
||||
return (uint)outValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales uint from minUint->maxUint to minFloat->maxFloat
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="minFloat"></param>
|
||||
/// <param name="maxFloat"></param>
|
||||
/// <param name="minUint">should be a power of 2, can be 0</param>
|
||||
/// <param name="maxUint">should be a power of 2, for example 1 << 8 for value to take up 8 bytes</param>
|
||||
/// <returns></returns>
|
||||
public static float ScaleFromUInt(uint value, float minFloat, float maxFloat, uint minUint, uint maxUint)
|
||||
{
|
||||
// if out of range return min/max
|
||||
if (value > maxUint) { return maxFloat; }
|
||||
if (value < minUint) { return minFloat; }
|
||||
|
||||
float rangeFloat = maxFloat - minFloat;
|
||||
uint rangeUint = maxUint - minUint;
|
||||
|
||||
// scale value to 0->1 (as float)
|
||||
// make sure divide is float
|
||||
float valueRelative = (value - minUint) / (float)rangeUint;
|
||||
// scale value to fMin->fMax
|
||||
float outValue = valueRelative * rangeFloat + minFloat;
|
||||
return outValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
103
SRMP/Console/ConsoleInput.cs
Normal file
103
SRMP/Console/ConsoleInput.cs
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace SRMultiplayer
|
||||
{
|
||||
public class ConsoleInput
|
||||
{
|
||||
public string inputString = "";
|
||||
public event Action<string> OnInputText;
|
||||
|
||||
public void ClearLine()
|
||||
{
|
||||
Console.CursorLeft = 0;
|
||||
Console.Write(new string(' ', Console.BufferWidth));
|
||||
Console.CursorTop--;
|
||||
Console.CursorLeft = 0;
|
||||
}
|
||||
|
||||
public void RedrawInputLine()
|
||||
{
|
||||
bool flag = Console.CursorLeft > 0;
|
||||
if (flag)
|
||||
{
|
||||
this.ClearLine();
|
||||
}
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.Write("> ");
|
||||
Console.Write(this.inputString);
|
||||
}
|
||||
|
||||
internal void OnBackspace()
|
||||
{
|
||||
bool flag = this.inputString.Length <= 0;
|
||||
if (!flag)
|
||||
{
|
||||
this.inputString = this.inputString.Substring(0, this.inputString.Length - 1);
|
||||
this.RedrawInputLine();
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnEscape()
|
||||
{
|
||||
this.ClearLine();
|
||||
this.inputString = "";
|
||||
this.RedrawInputLine();
|
||||
}
|
||||
|
||||
internal void OnEnter()
|
||||
{
|
||||
this.ClearLine();
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine("> " + this.inputString);
|
||||
string obj = this.inputString;
|
||||
this.inputString = "";
|
||||
bool flag = this.OnInputText != null;
|
||||
if (flag)
|
||||
{
|
||||
this.OnInputText(obj);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
bool flag = !Console.KeyAvailable;
|
||||
if (!flag)
|
||||
{
|
||||
ConsoleKeyInfo consoleKeyInfo = Console.ReadKey();
|
||||
bool flag2 = consoleKeyInfo.Key == ConsoleKey.Enter;
|
||||
if (flag2)
|
||||
{
|
||||
this.OnEnter();
|
||||
}
|
||||
else
|
||||
{
|
||||
bool flag3 = consoleKeyInfo.Key == ConsoleKey.Backspace;
|
||||
if (flag3)
|
||||
{
|
||||
this.OnBackspace();
|
||||
}
|
||||
else
|
||||
{
|
||||
bool flag4 = consoleKeyInfo.Key == ConsoleKey.Escape;
|
||||
if (flag4)
|
||||
{
|
||||
this.OnEscape();
|
||||
}
|
||||
else
|
||||
{
|
||||
bool flag5 = consoleKeyInfo.KeyChar > '\0';
|
||||
if (flag5)
|
||||
{
|
||||
this.inputString += consoleKeyInfo.KeyChar.ToString();
|
||||
this.RedrawInputLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
69
SRMP/Console/ConsoleWindow.cs
Normal file
69
SRMP/Console/ConsoleWindow.cs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
using Microsoft.Win32.SafeHandles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SRMultiplayer
|
||||
{
|
||||
public class ConsoleWindow
|
||||
{
|
||||
private TextWriter oldOutput;
|
||||
|
||||
private const int STD_OUTPUT_HANDLE = -11;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
bool flag = !ConsoleWindow.AttachConsole(uint.MaxValue);
|
||||
if (flag)
|
||||
{
|
||||
ConsoleWindow.AllocConsole();
|
||||
}
|
||||
this.oldOutput = Console.Out;
|
||||
try
|
||||
{
|
||||
IntPtr stdHandle = ConsoleWindow.GetStdHandle(-11);
|
||||
SafeFileHandle handle = new SafeFileHandle(stdHandle, true);
|
||||
FileStream stream = new FileStream(handle, FileAccess.Write);
|
||||
Encoding ascii = Encoding.ASCII;
|
||||
Console.SetOut(new StreamWriter(stream, ascii)
|
||||
{
|
||||
AutoFlush = true
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SRMP.Log("Couldn't redirect output: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
Console.SetOut(this.oldOutput);
|
||||
ConsoleWindow.FreeConsole();
|
||||
}
|
||||
|
||||
public void SetTitle(string strName)
|
||||
{
|
||||
ConsoleWindow.SetConsoleTitle(strName);
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool AttachConsole(uint dwProcessId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool AllocConsole();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool FreeConsole();
|
||||
|
||||
[DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern IntPtr GetStdHandle(int nStdHandle);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool SetConsoleTitle(string lpConsoleTitle);
|
||||
}
|
||||
}
|
||||
249
SRMP/Extensions.cs
Normal file
249
SRMP/Extensions.cs
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
using Lidgren.Network;
|
||||
using SRMultiplayer;
|
||||
using SRMultiplayer.Networking;
|
||||
using SRMultiplayer.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SRMultiplayer
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static void Rebuild(this RefineryUI ui)
|
||||
{
|
||||
foreach(Transform child in ui.inventoryGridPanel.transform)
|
||||
{
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
|
||||
GadgetDirector gadgetDirector = SRSingleton<SceneContext>.Instance.GadgetDirector;
|
||||
PediaDirector pediaDirector = SRSingleton<SceneContext>.Instance.PediaDirector;
|
||||
int num = 0;
|
||||
foreach (Identifiable.Id id in ui.listedItems)
|
||||
{
|
||||
int refineryCount = gadgetDirector.GetRefineryCount(id);
|
||||
Identifiable.Id id2 = id;
|
||||
PediaDirector.Id? pediaId = pediaDirector.GetPediaId(Identifiable.IsPlort(id) ? ui.PlortToSlime(id) : id);
|
||||
if (refineryCount == 0 && pediaId != null && !pediaDirector.IsUnlocked(pediaId.Value))
|
||||
{
|
||||
id2 = Identifiable.Id.NONE;
|
||||
}
|
||||
ui.AddInventory(id2, refineryCount);
|
||||
num++;
|
||||
}
|
||||
for (int j = num; j < 15; j++)
|
||||
{
|
||||
ui.AddEmptyInventory();
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteAmmoSlot(this NetOutgoingMessage om, Ammo.Slot slot)
|
||||
{
|
||||
om.Write(slot != null);
|
||||
if (slot != null)
|
||||
{
|
||||
om.Write((ushort)slot.id);
|
||||
om.Write(slot.count);
|
||||
om.Write(slot.emotions != null);
|
||||
if (slot.emotions != null)
|
||||
{
|
||||
om.Write(slot.emotions.Count);
|
||||
foreach (var emotion in slot.emotions)
|
||||
{
|
||||
om.Write((byte)emotion.Key);
|
||||
om.Write(emotion.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Ammo.Slot ReadAmmoSlot(this NetIncomingMessage im)
|
||||
{
|
||||
if (im.ReadBoolean())
|
||||
{
|
||||
var slot = new Ammo.Slot((Identifiable.Id)im.ReadUInt16(), im.ReadInt32());
|
||||
if (im.ReadBoolean())
|
||||
{
|
||||
int emotionCount = im.ReadInt32();
|
||||
slot.emotions = new SlimeEmotionData();
|
||||
for (int l = 0; l < emotionCount; l++)
|
||||
{
|
||||
slot.emotions.Add((SlimeEmotions.Emotion)im.ReadByte(), im.ReadFloat());
|
||||
}
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void Send(this Packet packet, NetDeliveryMethod method = NetDeliveryMethod.ReliableOrdered, int sequence = 0)
|
||||
{
|
||||
if(!Globals.IsClient)
|
||||
{
|
||||
NetworkServer.Instance.SendToAll(packet, method, sequence);
|
||||
return;
|
||||
}
|
||||
NetworkClient.Instance.Send(packet, method, sequence);
|
||||
}
|
||||
|
||||
public static void Send(this Packet packet, NetworkPlayer player, NetDeliveryMethod method = NetDeliveryMethod.ReliableOrdered, int sequence = 0)
|
||||
{
|
||||
if (!Globals.IsServer)
|
||||
{
|
||||
SRMP.Log("Trying to send packet as server while not server");
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkServer.Instance.Send(player.Connection, packet, method, sequence);
|
||||
}
|
||||
|
||||
public static void SendToAll(this Packet packet, NetDeliveryMethod method = NetDeliveryMethod.ReliableOrdered, int sequence = 0)
|
||||
{
|
||||
if (!Globals.IsServer)
|
||||
{
|
||||
SRMP.Log("Trying to send packet as server while not server");
|
||||
return;
|
||||
}
|
||||
|
||||
List<NetConnection> cons = new List<NetConnection>();
|
||||
foreach (var p in Globals.Players.Values)
|
||||
{
|
||||
if (p.Connection != null)
|
||||
{
|
||||
cons.Add(p.Connection);
|
||||
}
|
||||
}
|
||||
NetworkServer.Instance.SendTo(packet, cons, method, sequence);
|
||||
}
|
||||
|
||||
public static void SendToAllInRegions(this Packet packet, NetworkPlayer player, bool includeSelf, NetDeliveryMethod method = NetDeliveryMethod.ReliableOrdered, int sequence = 0)
|
||||
{
|
||||
if (!Globals.IsServer)
|
||||
{
|
||||
SRMP.Log("Trying to send packet as server while not server");
|
||||
return;
|
||||
}
|
||||
|
||||
List<NetConnection> cons = new List<NetConnection>();
|
||||
foreach(var netRegion in player.Regions)
|
||||
{
|
||||
foreach(var p in netRegion.Players)
|
||||
{
|
||||
if(p.ID != player.ID && p.Connection != null && !cons.Contains(p.Connection))
|
||||
{
|
||||
cons.Add(p.Connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
NetworkServer.Instance.SendTo(packet, cons, method, sequence);
|
||||
}
|
||||
|
||||
public static void SendToAllExcept(this Packet packet, NetworkPlayer player, NetDeliveryMethod method = NetDeliveryMethod.ReliableOrdered, int sequence = 0)
|
||||
{
|
||||
if (!Globals.IsServer)
|
||||
{
|
||||
SRMP.Log("Trying to send packet as server while not server");
|
||||
return;
|
||||
}
|
||||
|
||||
List<NetConnection> cons = new List<NetConnection>();
|
||||
foreach (var p in Globals.Players.Values)
|
||||
{
|
||||
if (p.ID != player.ID && p.Connection != null)
|
||||
{
|
||||
cons.Add(p.Connection);
|
||||
}
|
||||
}
|
||||
NetworkServer.Instance.SendTo(packet, cons, method, sequence);
|
||||
}
|
||||
|
||||
public static T CopyComponent<T>(this T original, GameObject destination) where T : Component
|
||||
{
|
||||
System.Type type = original.GetType();
|
||||
Component copy = destination.AddComponent(type);
|
||||
System.Reflection.FieldInfo[] fields = type.GetFields();
|
||||
foreach (System.Reflection.FieldInfo field in fields)
|
||||
{
|
||||
field.SetValue(copy, field.GetValue(original));
|
||||
}
|
||||
return copy as T;
|
||||
}
|
||||
|
||||
public static T GetOrAddComponent<T>(this GameObject obj) where T : Component
|
||||
{
|
||||
var comp = obj.GetComponent<T>();
|
||||
if(comp == null)
|
||||
{
|
||||
return obj.AddComponent<T>();
|
||||
}
|
||||
return comp;
|
||||
}
|
||||
|
||||
public static T GetInParent<T>(this GameObject obj) where T : Component
|
||||
{
|
||||
var cmp = obj.GetComponent<T>();
|
||||
if (cmp != null)
|
||||
{
|
||||
return cmp;
|
||||
}
|
||||
if (obj.transform.parent != null)
|
||||
{
|
||||
return GetInParent<T>(obj.transform.parent.gameObject);
|
||||
}
|
||||
return default(T);
|
||||
}
|
||||
|
||||
public static string GetGameObjectPath(this Transform transform, bool withID = true)
|
||||
{
|
||||
string path = transform.name + (withID ? transform.GetSiblingIndex().ToString() : "");
|
||||
while (transform.parent != null)
|
||||
{
|
||||
transform = transform.parent;
|
||||
path = transform.name + (withID ? transform.GetSiblingIndex().ToString() : "") + "/" + path;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public static string GetGameObjectPath(this GameObject transform, bool withID = true)
|
||||
{
|
||||
return GetGameObjectPath(transform.transform, withID);
|
||||
}
|
||||
|
||||
public static bool GetBit(this byte b, int bitNumber)
|
||||
{
|
||||
return (b & (1 << bitNumber)) != 0;
|
||||
}
|
||||
|
||||
public static byte SetBit(this byte b, int position, bool value)
|
||||
{
|
||||
if(value)
|
||||
{
|
||||
return (byte)(b | (1 << position));
|
||||
}
|
||||
else
|
||||
{
|
||||
return (byte)(b & ~(1 << position));
|
||||
}
|
||||
}
|
||||
|
||||
public static Transform FindDisabled(this Transform transform, string name)
|
||||
{
|
||||
if(transform.name.Equals(name, StringComparison.CurrentCulture))
|
||||
{
|
||||
return transform;
|
||||
}
|
||||
foreach(Transform child in transform)
|
||||
{
|
||||
var found = FindDisabled(child, name);
|
||||
if(found != null)
|
||||
{
|
||||
return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
78
SRMP/Globals.cs
Normal file
78
SRMP/Globals.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
using SRMultiplayer.Networking;
|
||||
using SRMultiplayer.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SRMultiplayer
|
||||
{
|
||||
public static class Globals
|
||||
{
|
||||
public static int Version;
|
||||
public static UserData UserData;
|
||||
public static GameObject BeatrixModel;
|
||||
public static RuntimeAnimatorController BeatrixController;
|
||||
public static GameObject IngameMultiplayerMenuPrefab;
|
||||
public static GameObject MainMultiplayerMenuPrefab;
|
||||
public static Dictionary<byte, NetworkPlayer> Players = new Dictionary<byte, NetworkPlayer>();
|
||||
public static string Username;
|
||||
public static string ServerCode;
|
||||
public static byte LocalID;
|
||||
public static NetworkPlayer LocalPlayer;
|
||||
public static bool HandlePacket;
|
||||
public static Guid PartyID;
|
||||
public static bool IsClient { get { return NetworkClient.Instance.Status == NetworkClient.ConnectionStatus.Connected; } }
|
||||
public static bool IsServer { get { return NetworkServer.Instance.Status == NetworkServer.ServerStatus.Running; } }
|
||||
public static bool IsMultiplayer { get { return IsClient || IsServer; } }
|
||||
public static bool GameLoaded;
|
||||
public static bool ClientLoaded;
|
||||
public static bool DisableAchievements;
|
||||
public static string CurrentGameName;
|
||||
public static PauseState PauseState;
|
||||
public static Dictionary<string, SECTR_AudioCue> Audios = new Dictionary<string, SECTR_AudioCue>();
|
||||
public static Dictionary<int, NetworkActor> Actors = new Dictionary<int, NetworkActor>();
|
||||
public static Dictionary<int, NetworkRegion> Regions = new Dictionary<int, NetworkRegion>();
|
||||
public static Dictionary<string, NetworkLandplot> LandPlots = new Dictionary<string, NetworkLandplot>();
|
||||
public static Dictionary<string, GameObject> FXPrefabs = new Dictionary<string, GameObject>();
|
||||
public static Dictionary<string, NetworkAccessDoor> AccessDoors = new Dictionary<string, NetworkAccessDoor>();
|
||||
public static Dictionary<string, NetworkGordo> Gordos = new Dictionary<string, NetworkGordo>();
|
||||
public static Dictionary<int, NetworkSpawnResource> SpawnResources = new Dictionary<int, NetworkSpawnResource>();
|
||||
public static Dictionary<string, NetworkPuzzleSlot> PuzzleSlots = new Dictionary<string, NetworkPuzzleSlot>();
|
||||
public static Dictionary<string, NetworkWorldStateMasterSwitch> Switches = new Dictionary<string, NetworkWorldStateMasterSwitch>();
|
||||
public static Dictionary<string, NetworkGadgetSite> GadgetSites = new Dictionary<string, NetworkGadgetSite>();
|
||||
public static Dictionary<int, NetworkDirectedActorSpawner> Spawners = new Dictionary<int, NetworkDirectedActorSpawner>();
|
||||
public static Dictionary<string, NetworkTreasurePod> TreasurePods = new Dictionary<string, NetworkTreasurePod>();
|
||||
public static Dictionary<int, NetworkExchangeAcceptor> ExchangeAcceptors = new Dictionary<int, NetworkExchangeAcceptor>();
|
||||
public static Dictionary<int, NetworkFireColumn> FireColumns = new Dictionary<int, NetworkFireColumn>();
|
||||
public static Dictionary<int, NetworkKookadobaPatchNode> Kookadobas = new Dictionary<int, NetworkKookadobaPatchNode>();
|
||||
public static Dictionary<int, NetworkNutcracker> Nutcrackers = new Dictionary<int, NetworkNutcracker>();
|
||||
public static Dictionary<int, NetworkRaceTrigger> RaceTriggers = new Dictionary<int, NetworkRaceTrigger>();
|
||||
public static List<string> LemonTrees = new List<string>();
|
||||
public static Dictionary<PacketType, long> PacketSize = new Dictionary<PacketType, long>();
|
||||
|
||||
public static List<string> Mods
|
||||
{
|
||||
get
|
||||
{
|
||||
List<string> mods = new List<string>();
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
if (!assembly.GetName().Name.Contains("Unity") && !assembly.GetName().Name.Contains("InControl") && !assembly.GetName().Name.Contains("DOTween") &&
|
||||
!assembly.GetName().Name.Contains("mscorlib") && !assembly.GetName().Name.Contains("System") && !assembly.GetName().Name.Contains("Assembly-CSharp") &&
|
||||
!assembly.GetName().Name.Contains("Logger") && !assembly.GetName().Name.Contains("Mono.") && !assembly.GetName().Name.Contains("Harmony") &&
|
||||
!assembly.GetName().Name.Equals("SRML") && !assembly.GetName().Name.Equals("SRML.Editor") && !assembly.GetName().Name.Equals("Newtonsoft.Json") &&
|
||||
!assembly.GetName().Name.Equals("INIFileParser") && !assembly.GetName().Name.Equals("SRMultiplayer") && !assembly.GetName().Name.Contains("Microsoft.") &&
|
||||
!assembly.GetName().Name.Equals("SRMP") && !assembly.GetName().Name.Equals("XGamingRuntime") && !Globals.UserData.IgnoredMods.Contains(assembly.GetName().Name))
|
||||
{
|
||||
mods.Add(assembly.GetName().Name);
|
||||
}
|
||||
}
|
||||
return mods;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
SRMP/Lidgren.Network/Encryption/NetAESEncryption.cs
Normal file
38
SRMP/Lidgren.Network/Encryption/NetAESEncryption.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public class NetAESEncryption : NetCryptoProviderBase
|
||||
{
|
||||
public NetAESEncryption(NetPeer peer)
|
||||
#if UNITY
|
||||
: base(peer, new RijndaelManaged())
|
||||
#else
|
||||
: base(peer, new AesCryptoServiceProvider())
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
public NetAESEncryption(NetPeer peer, string key)
|
||||
#if UNITY
|
||||
: base(peer, new RijndaelManaged())
|
||||
#else
|
||||
: base(peer, new AesCryptoServiceProvider())
|
||||
#endif
|
||||
{
|
||||
SetKey(key);
|
||||
}
|
||||
|
||||
public NetAESEncryption(NetPeer peer, byte[] data, int offset, int count)
|
||||
#if UNITY
|
||||
: base(peer, new RijndaelManaged())
|
||||
#else
|
||||
: base(peer, new AesCryptoServiceProvider())
|
||||
#endif
|
||||
{
|
||||
SetKey(data, offset, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
89
SRMP/Lidgren.Network/Encryption/NetBlockEncryptionBase.cs
Normal file
89
SRMP/Lidgren.Network/Encryption/NetBlockEncryptionBase.cs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Base for a non-threadsafe encryption class
|
||||
/// </summary>
|
||||
public abstract class NetBlockEncryptionBase : NetEncryption
|
||||
{
|
||||
// temporary space for one block to avoid reallocating every time
|
||||
private byte[] m_tmp;
|
||||
|
||||
/// <summary>
|
||||
/// Block size in bytes for this cipher
|
||||
/// </summary>
|
||||
public abstract int BlockSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// NetBlockEncryptionBase constructor
|
||||
/// </summary>
|
||||
public NetBlockEncryptionBase(NetPeer peer)
|
||||
: base(peer)
|
||||
{
|
||||
m_tmp = new byte[BlockSize];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt am outgoing message with this algorithm; no writing can be done to the message after encryption, or message will be corrupted
|
||||
/// </summary>
|
||||
public override bool Encrypt(NetOutgoingMessage msg)
|
||||
{
|
||||
int payloadBitLength = msg.LengthBits;
|
||||
int numBytes = msg.LengthBytes;
|
||||
int blockSize = BlockSize;
|
||||
int numBlocks = (int)Math.Ceiling((double)numBytes / (double)blockSize);
|
||||
int dstSize = numBlocks * blockSize;
|
||||
|
||||
msg.EnsureBufferSize(dstSize * 8 + (4 * 8)); // add 4 bytes for payload length at end
|
||||
msg.LengthBits = dstSize * 8; // length will automatically adjust +4 bytes when payload length is written
|
||||
|
||||
for(int i=0;i<numBlocks;i++)
|
||||
{
|
||||
EncryptBlock(msg.m_data, (i * blockSize), m_tmp);
|
||||
Buffer.BlockCopy(m_tmp, 0, msg.m_data, (i * blockSize), m_tmp.Length);
|
||||
}
|
||||
|
||||
// add true payload length last
|
||||
msg.Write((UInt32)payloadBitLength);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt an incoming message encrypted with corresponding Encrypt
|
||||
/// </summary>
|
||||
/// <param name="msg">message to decrypt</param>
|
||||
/// <returns>true if successful; false if failed</returns>
|
||||
public override bool Decrypt(NetIncomingMessage msg)
|
||||
{
|
||||
int numEncryptedBytes = msg.LengthBytes - 4; // last 4 bytes is true bit length
|
||||
int blockSize = BlockSize;
|
||||
int numBlocks = numEncryptedBytes / blockSize;
|
||||
if (numBlocks * blockSize != numEncryptedBytes)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < numBlocks; i++)
|
||||
{
|
||||
DecryptBlock(msg.m_data, (i * blockSize), m_tmp);
|
||||
Buffer.BlockCopy(m_tmp, 0, msg.m_data, (i * blockSize), m_tmp.Length);
|
||||
}
|
||||
|
||||
// read 32 bits of true payload length
|
||||
uint realSize = NetBitWriter.ReadUInt32(msg.m_data, 32, (numEncryptedBytes * 8));
|
||||
msg.m_bitLength = (int)realSize;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt a block of bytes
|
||||
/// </summary>
|
||||
protected abstract void EncryptBlock(byte[] source, int sourceOffset, byte[] destination);
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt a block of bytes
|
||||
/// </summary>
|
||||
protected abstract void DecryptBlock(byte[] source, int sourceOffset, byte[] destination);
|
||||
}
|
||||
}
|
||||
77
SRMP/Lidgren.Network/Encryption/NetCryptoProviderBase.cs
Normal file
77
SRMP/Lidgren.Network/Encryption/NetCryptoProviderBase.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public abstract class NetCryptoProviderBase : NetEncryption
|
||||
{
|
||||
protected SymmetricAlgorithm m_algorithm;
|
||||
|
||||
public NetCryptoProviderBase(NetPeer peer, SymmetricAlgorithm algo)
|
||||
: base(peer)
|
||||
{
|
||||
m_algorithm = algo;
|
||||
m_algorithm.GenerateKey();
|
||||
m_algorithm.GenerateIV();
|
||||
}
|
||||
|
||||
public override void SetKey(byte[] data, int offset, int count)
|
||||
{
|
||||
int len = m_algorithm.Key.Length;
|
||||
var key = new byte[len];
|
||||
for (int i = 0; i < len; i++)
|
||||
key[i] = data[offset + (i % count)];
|
||||
m_algorithm.Key = key;
|
||||
|
||||
len = m_algorithm.IV.Length;
|
||||
key = new byte[len];
|
||||
for (int i = 0; i < len; i++)
|
||||
key[len - 1 - i] = data[offset + (i % count)];
|
||||
m_algorithm.IV = key;
|
||||
}
|
||||
|
||||
public override bool Encrypt(NetOutgoingMessage msg)
|
||||
{
|
||||
int unEncLenBits = msg.LengthBits;
|
||||
|
||||
var ms = new MemoryStream();
|
||||
var cs = new CryptoStream(ms, m_algorithm.CreateEncryptor(), CryptoStreamMode.Write);
|
||||
cs.Write(msg.m_data, 0, msg.LengthBytes);
|
||||
cs.Close();
|
||||
|
||||
// get results
|
||||
var arr = ms.ToArray();
|
||||
ms.Close();
|
||||
|
||||
msg.EnsureBufferSize((arr.Length + 4) * 8);
|
||||
msg.LengthBits = 0; // reset write pointer
|
||||
msg.Write((uint)unEncLenBits);
|
||||
msg.Write(arr);
|
||||
msg.LengthBits = (arr.Length + 4) * 8;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Decrypt(NetIncomingMessage msg)
|
||||
{
|
||||
int unEncLenBits = (int)msg.ReadUInt32();
|
||||
|
||||
var ms = new MemoryStream(msg.m_data, 4, msg.LengthBytes - 4);
|
||||
var cs = new CryptoStream(ms, m_algorithm.CreateDecryptor(), CryptoStreamMode.Read);
|
||||
|
||||
var byteLen = NetUtility.BytesToHoldBits(unEncLenBits);
|
||||
var result = m_peer.GetStorage(byteLen);
|
||||
cs.Read(result, 0, byteLen);
|
||||
cs.Close();
|
||||
|
||||
// TODO: recycle existing msg
|
||||
|
||||
msg.m_data = result;
|
||||
msg.m_bitLength = unEncLenBits;
|
||||
msg.m_readPosition = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public abstract class NetCryptoProviderEncryption : NetEncryption
|
||||
{
|
||||
public NetCryptoProviderEncryption(NetPeer peer)
|
||||
: base(peer)
|
||||
{
|
||||
}
|
||||
|
||||
protected abstract CryptoStream GetEncryptStream(MemoryStream ms);
|
||||
|
||||
protected abstract CryptoStream GetDecryptStream(MemoryStream ms);
|
||||
|
||||
public override bool Encrypt(NetOutgoingMessage msg)
|
||||
{
|
||||
int unEncLenBits = msg.LengthBits;
|
||||
|
||||
var ms = new MemoryStream();
|
||||
var cs = GetEncryptStream(ms);
|
||||
cs.Write(msg.m_data, 0, msg.LengthBytes);
|
||||
cs.Close();
|
||||
|
||||
// get results
|
||||
var arr = ms.ToArray();
|
||||
ms.Close();
|
||||
|
||||
msg.EnsureBufferSize((arr.Length + 4) * 8);
|
||||
msg.LengthBits = 0; // reset write pointer
|
||||
msg.Write((uint)unEncLenBits);
|
||||
msg.Write(arr);
|
||||
msg.LengthBits = (arr.Length + 4) * 8;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Decrypt(NetIncomingMessage msg)
|
||||
{
|
||||
int unEncLenBits = (int)msg.ReadUInt32();
|
||||
|
||||
var ms = new MemoryStream(msg.m_data, 4, msg.LengthBytes - 4);
|
||||
var cs = GetDecryptStream(ms);
|
||||
|
||||
var result = m_peer.GetStorage(unEncLenBits);
|
||||
cs.Read(result, 0, NetUtility.BytesToHoldBits(unEncLenBits));
|
||||
cs.Close();
|
||||
|
||||
// TODO: recycle existing msg
|
||||
|
||||
msg.m_data = result;
|
||||
msg.m_bitLength = unEncLenBits;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
SRMP/Lidgren.Network/Encryption/NetDESEncryption.cs
Normal file
26
SRMP/Lidgren.Network/Encryption/NetDESEncryption.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public class NetDESEncryption : NetCryptoProviderBase
|
||||
{
|
||||
public NetDESEncryption(NetPeer peer)
|
||||
: base(peer, new DESCryptoServiceProvider())
|
||||
{
|
||||
}
|
||||
|
||||
public NetDESEncryption(NetPeer peer, string key)
|
||||
: base(peer, new DESCryptoServiceProvider())
|
||||
{
|
||||
SetKey(key);
|
||||
}
|
||||
|
||||
public NetDESEncryption(NetPeer peer, byte[] data, int offset, int count)
|
||||
: base(peer, new DESCryptoServiceProvider())
|
||||
{
|
||||
SetKey(data, offset, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
SRMP/Lidgren.Network/Encryption/NetEncryption.cs
Normal file
45
SRMP/Lidgren.Network/Encryption/NetEncryption.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for an encryption algorithm
|
||||
/// </summary>
|
||||
public abstract class NetEncryption
|
||||
{
|
||||
/// <summary>
|
||||
/// NetPeer
|
||||
/// </summary>
|
||||
protected NetPeer m_peer;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public NetEncryption(NetPeer peer)
|
||||
{
|
||||
if (peer == null)
|
||||
throw new NetException("Peer must not be null");
|
||||
m_peer = peer;
|
||||
}
|
||||
|
||||
public void SetKey(string str)
|
||||
{
|
||||
var bytes = System.Text.Encoding.ASCII.GetBytes(str);
|
||||
SetKey(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
public abstract void SetKey(byte[] data, int offset, int count);
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt an outgoing message in place
|
||||
/// </summary>
|
||||
public abstract bool Encrypt(NetOutgoingMessage msg);
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt an incoming message in place
|
||||
/// </summary>
|
||||
public abstract bool Decrypt(NetIncomingMessage msg);
|
||||
}
|
||||
}
|
||||
26
SRMP/Lidgren.Network/Encryption/NetRC2Encryption.cs
Normal file
26
SRMP/Lidgren.Network/Encryption/NetRC2Encryption.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public class NetRC2Encryption : NetCryptoProviderBase
|
||||
{
|
||||
public NetRC2Encryption(NetPeer peer)
|
||||
: base(peer, new RC2CryptoServiceProvider())
|
||||
{
|
||||
}
|
||||
|
||||
public NetRC2Encryption(NetPeer peer, string key)
|
||||
: base(peer, new RC2CryptoServiceProvider())
|
||||
{
|
||||
SetKey(key);
|
||||
}
|
||||
|
||||
public NetRC2Encryption(NetPeer peer, byte[] data, int offset, int count)
|
||||
: base(peer, new RC2CryptoServiceProvider())
|
||||
{
|
||||
SetKey(data, offset, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
SRMP/Lidgren.Network/Encryption/NetTripleDESEncryption.cs
Normal file
26
SRMP/Lidgren.Network/Encryption/NetTripleDESEncryption.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public class NetTripleDESEncryption : NetCryptoProviderBase
|
||||
{
|
||||
public NetTripleDESEncryption(NetPeer peer)
|
||||
: base(peer, new TripleDESCryptoServiceProvider())
|
||||
{
|
||||
}
|
||||
|
||||
public NetTripleDESEncryption(NetPeer peer, string key)
|
||||
: base(peer, new TripleDESCryptoServiceProvider())
|
||||
{
|
||||
SetKey(key);
|
||||
}
|
||||
|
||||
public NetTripleDESEncryption(NetPeer peer, byte[] data, int offset, int count)
|
||||
: base(peer, new TripleDESCryptoServiceProvider())
|
||||
{
|
||||
SetKey(data, offset, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
66
SRMP/Lidgren.Network/Encryption/NetXorEncryption.cs
Normal file
66
SRMP/Lidgren.Network/Encryption/NetXorEncryption.cs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Example class; not very good encryption
|
||||
/// </summary>
|
||||
public class NetXorEncryption : NetEncryption
|
||||
{
|
||||
private byte[] m_key;
|
||||
|
||||
/// <summary>
|
||||
/// NetXorEncryption constructor
|
||||
/// </summary>
|
||||
public NetXorEncryption(NetPeer peer, byte[] key)
|
||||
: base(peer)
|
||||
{
|
||||
m_key = key;
|
||||
}
|
||||
|
||||
public override void SetKey(byte[] data, int offset, int count)
|
||||
{
|
||||
m_key = new byte[count];
|
||||
Array.Copy(data, offset, m_key, 0, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NetXorEncryption constructor
|
||||
/// </summary>
|
||||
public NetXorEncryption(NetPeer peer, string key)
|
||||
: base(peer)
|
||||
{
|
||||
m_key = Encoding.UTF8.GetBytes(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt an outgoing message
|
||||
/// </summary>
|
||||
public override bool Encrypt(NetOutgoingMessage msg)
|
||||
{
|
||||
int numBytes = msg.LengthBytes;
|
||||
for (int i = 0; i < numBytes; i++)
|
||||
{
|
||||
int offset = i % m_key.Length;
|
||||
msg.m_data[i] = (byte)(msg.m_data[i] ^ m_key[offset]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt an incoming message
|
||||
/// </summary>
|
||||
public override bool Decrypt(NetIncomingMessage msg)
|
||||
{
|
||||
int numBytes = msg.LengthBytes;
|
||||
for (int i = 0; i < numBytes; i++)
|
||||
{
|
||||
int offset = i % m_key.Length;
|
||||
msg.m_data[i] = (byte)(msg.m_data[i] ^ m_key[offset]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
154
SRMP/Lidgren.Network/Encryption/NetXteaEncryption.cs
Normal file
154
SRMP/Lidgren.Network/Encryption/NetXteaEncryption.cs
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Security;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods to encrypt and decrypt data using the XTEA algorithm
|
||||
/// </summary>
|
||||
public sealed class NetXtea : NetBlockEncryptionBase
|
||||
{
|
||||
private const int c_blockSize = 8;
|
||||
private const int c_keySize = 16;
|
||||
private const int c_delta = unchecked((int)0x9E3779B9);
|
||||
|
||||
private readonly int m_numRounds;
|
||||
private readonly uint[] m_sum0;
|
||||
private readonly uint[] m_sum1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the block size for this cipher
|
||||
/// </summary>
|
||||
public override int BlockSize { get { return c_blockSize; } }
|
||||
|
||||
/// <summary>
|
||||
/// 16 byte key
|
||||
/// </summary>
|
||||
public NetXtea(NetPeer peer, byte[] key, int rounds)
|
||||
: base(peer)
|
||||
{
|
||||
if (key.Length < c_keySize)
|
||||
throw new NetException("Key too short!");
|
||||
|
||||
m_numRounds = rounds;
|
||||
m_sum0 = new uint[m_numRounds];
|
||||
m_sum1 = new uint[m_numRounds];
|
||||
uint[] tmp = new uint[8];
|
||||
|
||||
int num2;
|
||||
int index = num2 = 0;
|
||||
while (index < 4)
|
||||
{
|
||||
tmp[index] = BitConverter.ToUInt32(key, num2);
|
||||
index++;
|
||||
num2 += 4;
|
||||
}
|
||||
for (index = num2 = 0; index < 32; index++)
|
||||
{
|
||||
m_sum0[index] = ((uint)num2) + tmp[num2 & 3];
|
||||
num2 += -1640531527;
|
||||
m_sum1[index] = ((uint)num2) + tmp[(num2 >> 11) & 3];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 16 byte key
|
||||
/// </summary>
|
||||
public NetXtea(NetPeer peer, byte[] key)
|
||||
: this(peer, key, 32)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// String to hash for key
|
||||
/// </summary>
|
||||
public NetXtea(NetPeer peer, string key)
|
||||
: this(peer, NetUtility.ComputeSHAHash(Encoding.UTF8.GetBytes(key)), 32)
|
||||
{
|
||||
}
|
||||
|
||||
public override void SetKey(byte[] data, int offset, int length)
|
||||
{
|
||||
var key = NetUtility.ComputeSHAHash(data, offset, length);
|
||||
NetException.Assert(key.Length >= 16);
|
||||
SetKey(key, 0, 16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypts a block of bytes
|
||||
/// </summary>
|
||||
protected override void EncryptBlock(byte[] source, int sourceOffset, byte[] destination)
|
||||
{
|
||||
uint v0 = BytesToUInt(source, sourceOffset);
|
||||
uint v1 = BytesToUInt(source, sourceOffset + 4);
|
||||
|
||||
for (int i = 0; i != m_numRounds; i++)
|
||||
{
|
||||
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ m_sum0[i];
|
||||
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ m_sum1[i];
|
||||
}
|
||||
|
||||
UIntToBytes(v0, destination, 0);
|
||||
UIntToBytes(v1, destination, 0 + 4);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypts a block of bytes
|
||||
/// </summary>
|
||||
protected override void DecryptBlock(byte[] source, int sourceOffset, byte[] destination)
|
||||
{
|
||||
// Pack bytes into integers
|
||||
uint v0 = BytesToUInt(source, sourceOffset);
|
||||
uint v1 = BytesToUInt(source, sourceOffset + 4);
|
||||
|
||||
for (int i = m_numRounds - 1; i >= 0; i--)
|
||||
{
|
||||
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ m_sum1[i];
|
||||
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ m_sum0[i];
|
||||
}
|
||||
|
||||
UIntToBytes(v0, destination, 0);
|
||||
UIntToBytes(v1, destination, 0 + 4);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private static uint BytesToUInt(byte[] bytes, int offset)
|
||||
{
|
||||
uint retval = (uint)(bytes[offset] << 24);
|
||||
retval |= (uint)(bytes[++offset] << 16);
|
||||
retval |= (uint)(bytes[++offset] << 8);
|
||||
return (retval | bytes[++offset]);
|
||||
}
|
||||
|
||||
private static void UIntToBytes(uint value, byte[] destination, int destinationOffset)
|
||||
{
|
||||
destination[destinationOffset++] = (byte)(value >> 24);
|
||||
destination[destinationOffset++] = (byte)(value >> 16);
|
||||
destination[destinationOffset++] = (byte)(value >> 8);
|
||||
destination[destinationOffset++] = (byte)value;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
SRMP/Lidgren.Network/NamespaceDoc.cs
Normal file
14
SRMP/Lidgren.Network/NamespaceDoc.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Lidgren Network Library
|
||||
/// </summary>
|
||||
internal class NamespaceDoc
|
||||
{
|
||||
// <include file='_Namespace.xml' path='Documentation/*' />
|
||||
}
|
||||
}
|
||||
2352
SRMP/Lidgren.Network/NetBigInteger.cs
Normal file
2352
SRMP/Lidgren.Network/NetBigInteger.cs
Normal file
File diff suppressed because it is too large
Load diff
172
SRMP/Lidgren.Network/NetBitVector.cs
Normal file
172
SRMP/Lidgren.Network/NetBitVector.cs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Fixed size vector of booleans
|
||||
/// </summary>
|
||||
public sealed class NetBitVector
|
||||
{
|
||||
private readonly int m_capacity;
|
||||
private readonly int[] m_data;
|
||||
private int m_numBitsSet;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bits/booleans stored in this vector
|
||||
/// </summary>
|
||||
public int Capacity { get { return m_capacity; } }
|
||||
|
||||
/// <summary>
|
||||
/// NetBitVector constructor
|
||||
/// </summary>
|
||||
public NetBitVector(int bitsCapacity)
|
||||
{
|
||||
m_capacity = bitsCapacity;
|
||||
m_data = new int[(bitsCapacity + 31) / 32];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if all bits/booleans are set to zero/false
|
||||
/// </summary>
|
||||
public bool IsEmpty()
|
||||
{
|
||||
return (m_numBitsSet == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of bits/booleans set to one/true
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int Count()
|
||||
{
|
||||
return m_numBitsSet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shift all bits one step down, cycling the first bit to the top
|
||||
/// </summary>
|
||||
public void RotateDown()
|
||||
{
|
||||
int lenMinusOne = m_data.Length - 1;
|
||||
|
||||
int firstBit = m_data[0] & 1;
|
||||
for (int i = 0; i < lenMinusOne; i++)
|
||||
m_data[i] = ((m_data[i] >> 1) & ~(1 << 31)) | m_data[i + 1] << 31;
|
||||
|
||||
int lastIndex = m_capacity - 1 - (32 * lenMinusOne);
|
||||
|
||||
// special handling of last int
|
||||
int cur = m_data[lenMinusOne];
|
||||
cur = cur >> 1;
|
||||
cur |= firstBit << lastIndex;
|
||||
|
||||
m_data[lenMinusOne] = cur;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first (lowest) index set to true
|
||||
/// </summary>
|
||||
public int GetFirstSetIndex()
|
||||
{
|
||||
int idx = 0;
|
||||
|
||||
int data = m_data[0];
|
||||
while (data == 0)
|
||||
{
|
||||
idx++;
|
||||
data = m_data[idx];
|
||||
}
|
||||
|
||||
int a = 0;
|
||||
while (((data >> a) & 1) == 0)
|
||||
a++;
|
||||
|
||||
return (idx * 32) + a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bit/bool at the specified index
|
||||
/// </summary>
|
||||
public bool Get(int bitIndex)
|
||||
{
|
||||
NetException.Assert(bitIndex >= 0 && bitIndex < m_capacity);
|
||||
|
||||
return (m_data[bitIndex / 32] & (1 << (bitIndex % 32))) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets or clears the bit/bool at the specified index
|
||||
/// </summary>
|
||||
public void Set(int bitIndex, bool value)
|
||||
{
|
||||
NetException.Assert(bitIndex >= 0 && bitIndex < m_capacity);
|
||||
|
||||
int idx = bitIndex / 32;
|
||||
if (value)
|
||||
{
|
||||
if ((m_data[idx] & (1 << (bitIndex % 32))) == 0)
|
||||
m_numBitsSet++;
|
||||
m_data[idx] |= (1 << (bitIndex % 32));
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((m_data[idx] & (1 << (bitIndex % 32))) != 0)
|
||||
m_numBitsSet--;
|
||||
m_data[idx] &= (~(1 << (bitIndex % 32)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bit/bool at the specified index
|
||||
/// </summary>
|
||||
[System.Runtime.CompilerServices.IndexerName("Bit")]
|
||||
public bool this[int index]
|
||||
{
|
||||
get { return Get(index); }
|
||||
set { Set(index, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all bits/booleans to zero/false
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
Array.Clear(m_data, 0, m_data.Length);
|
||||
m_numBitsSet = 0;
|
||||
NetException.Assert(this.IsEmpty());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents this object
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder bdr = new StringBuilder(m_capacity + 2);
|
||||
bdr.Append('[');
|
||||
for (int i = 0; i < m_capacity; i++)
|
||||
bdr.Append(Get(m_capacity - i - 1) ? '1' : '0');
|
||||
bdr.Append(']');
|
||||
return bdr.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
513
SRMP/Lidgren.Network/NetBitWriter.cs
Normal file
513
SRMP/Lidgren.Network/NetBitWriter.cs
Normal file
|
|
@ -0,0 +1,513 @@
|
|||
//#define UNSAFE
|
||||
//#define BIGENDIAN
|
||||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for NetBuffer to write/read bits
|
||||
/// </summary>
|
||||
public static class NetBitWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Read 1-8 bits from a buffer into a byte
|
||||
/// </summary>
|
||||
public static byte ReadByte(byte[] fromBuffer, int numberOfBits, int readBitOffset)
|
||||
{
|
||||
NetException.Assert(((numberOfBits > 0) && (numberOfBits < 9)), "Read() can only read between 1 and 8 bits");
|
||||
|
||||
int bytePtr = readBitOffset >> 3;
|
||||
int startReadAtIndex = readBitOffset - (bytePtr * 8); // (readBitOffset % 8);
|
||||
|
||||
if (startReadAtIndex == 0 && numberOfBits == 8)
|
||||
return fromBuffer[bytePtr];
|
||||
|
||||
// mask away unused bits lower than (right of) relevant bits in first byte
|
||||
byte returnValue = (byte)(fromBuffer[bytePtr] >> startReadAtIndex);
|
||||
|
||||
int numberOfBitsInSecondByte = numberOfBits - (8 - startReadAtIndex);
|
||||
|
||||
if (numberOfBitsInSecondByte < 1)
|
||||
{
|
||||
// we don't need to read from the second byte, but we DO need
|
||||
// to mask away unused bits higher than (left of) relevant bits
|
||||
return (byte)(returnValue & (255 >> (8 - numberOfBits)));
|
||||
}
|
||||
|
||||
byte second = fromBuffer[bytePtr + 1];
|
||||
|
||||
// mask away unused bits higher than (left of) relevant bits in second byte
|
||||
second &= (byte)(255 >> (8 - numberOfBitsInSecondByte));
|
||||
|
||||
return (byte)(returnValue | (byte)(second << (numberOfBits - numberOfBitsInSecondByte)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read several bytes from a buffer
|
||||
/// </summary>
|
||||
public static void ReadBytes(byte[] fromBuffer, int numberOfBytes, int readBitOffset, byte[] destination, int destinationByteOffset)
|
||||
{
|
||||
int readPtr = readBitOffset >> 3;
|
||||
int startReadAtIndex = readBitOffset - (readPtr * 8); // (readBitOffset % 8);
|
||||
|
||||
if (startReadAtIndex == 0)
|
||||
{
|
||||
Buffer.BlockCopy(fromBuffer, readPtr, destination, destinationByteOffset, numberOfBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
int secondPartLen = 8 - startReadAtIndex;
|
||||
int secondMask = 255 >> secondPartLen;
|
||||
|
||||
for (int i = 0; i < numberOfBytes; i++)
|
||||
{
|
||||
// mask away unused bits lower than (right of) relevant bits in byte
|
||||
int b = fromBuffer[readPtr] >> startReadAtIndex;
|
||||
|
||||
readPtr++;
|
||||
|
||||
// mask away unused bits higher than (left of) relevant bits in second byte
|
||||
int second = fromBuffer[readPtr] & secondMask;
|
||||
|
||||
destination[destinationByteOffset++] = (byte)(b | (second << secondPartLen));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write 0-8 bits of data to buffer
|
||||
/// </summary>
|
||||
public static void WriteByte(byte source, int numberOfBits, byte[] destination, int destBitOffset)
|
||||
{
|
||||
if (numberOfBits == 0)
|
||||
return;
|
||||
|
||||
NetException.Assert(((numberOfBits >= 0) && (numberOfBits <= 8)), "Must write between 0 and 8 bits!");
|
||||
|
||||
// Mask out all the bits we dont want
|
||||
source = (byte)(source & (0xFF >> (8 - numberOfBits)));
|
||||
|
||||
int p = destBitOffset >> 3;
|
||||
int bitsUsed = destBitOffset & 0x7; // mod 8
|
||||
int bitsFree = 8 - bitsUsed;
|
||||
int bitsLeft = bitsFree - numberOfBits;
|
||||
|
||||
// Fast path, everything fits in the first byte
|
||||
if (bitsLeft >= 0)
|
||||
{
|
||||
int mask = (0xFF >> bitsFree) | (0xFF << (8 - bitsLeft));
|
||||
|
||||
destination[p] = (byte)(
|
||||
// Mask out lower and upper bits
|
||||
(destination[p] & mask) |
|
||||
|
||||
// Insert new bits
|
||||
(source << bitsUsed)
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
destination[p] = (byte)(
|
||||
// Mask out upper bits
|
||||
(destination[p] & (0xFF >> bitsFree)) |
|
||||
|
||||
// Write the lower bits to the upper bits in the first byte
|
||||
(source << bitsUsed)
|
||||
);
|
||||
|
||||
p += 1;
|
||||
|
||||
destination[p] = (byte)(
|
||||
// Mask out lower bits
|
||||
(destination[p] & (0xFF << (numberOfBits - bitsFree))) |
|
||||
|
||||
// Write the upper bits to the lower bits of the second byte
|
||||
(source >> bitsFree)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write several whole bytes
|
||||
/// </summary>
|
||||
public static void WriteBytes(byte[] source, int sourceByteOffset, int numberOfBytes, byte[] destination, int destBitOffset)
|
||||
{
|
||||
int dstBytePtr = destBitOffset >> 3;
|
||||
int firstPartLen = (destBitOffset % 8);
|
||||
|
||||
if (firstPartLen == 0)
|
||||
{
|
||||
Buffer.BlockCopy(source, sourceByteOffset, destination, dstBytePtr, numberOfBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
int lastPartLen = 8 - firstPartLen;
|
||||
|
||||
for (int i = 0; i < numberOfBytes; i++)
|
||||
{
|
||||
byte src = source[sourceByteOffset + i];
|
||||
|
||||
// write last part of this byte
|
||||
destination[dstBytePtr] &= (byte)(255 >> lastPartLen); // clear before writing
|
||||
destination[dstBytePtr] |= (byte)(src << firstPartLen); // write first half
|
||||
|
||||
dstBytePtr++;
|
||||
|
||||
// write first part of next byte
|
||||
destination[dstBytePtr] &= (byte)(255 << firstPartLen); // clear before writing
|
||||
destination[dstBytePtr] |= (byte)(src >> lastPartLen); // write second half
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an unsigned 16 bit integer
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
#if UNSAFE
|
||||
public static unsafe ushort ReadUInt16(byte[] fromBuffer, int numberOfBits, int readBitOffset)
|
||||
{
|
||||
Debug.Assert(((numberOfBits > 0) && (numberOfBits <= 16)), "ReadUInt16() can only read between 1 and 16 bits");
|
||||
|
||||
if (numberOfBits == 16 && ((readBitOffset % 8) == 0))
|
||||
{
|
||||
fixed (byte* ptr = &(fromBuffer[readBitOffset / 8]))
|
||||
{
|
||||
return *(((ushort*)ptr));
|
||||
}
|
||||
}
|
||||
#else
|
||||
public static ushort ReadUInt16(byte[] fromBuffer, int numberOfBits, int readBitOffset)
|
||||
{
|
||||
Debug.Assert(((numberOfBits > 0) && (numberOfBits <= 16)), "ReadUInt16() can only read between 1 and 16 bits");
|
||||
#endif
|
||||
ushort returnValue;
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
returnValue = ReadByte(fromBuffer, numberOfBits, readBitOffset);
|
||||
return returnValue;
|
||||
}
|
||||
returnValue = ReadByte(fromBuffer, 8, readBitOffset);
|
||||
numberOfBits -= 8;
|
||||
readBitOffset += 8;
|
||||
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
returnValue |= (ushort)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 8);
|
||||
}
|
||||
|
||||
#if BIGENDIAN
|
||||
// reorder bytes
|
||||
uint retVal = returnValue;
|
||||
retVal = ((retVal & 0x0000ff00) >> 8) | ((retVal & 0x000000ff) << 8);
|
||||
return (ushort)retVal;
|
||||
#else
|
||||
return returnValue;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of bits into an UInt32
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
#if UNSAFE
|
||||
public static unsafe uint ReadUInt32(byte[] fromBuffer, int numberOfBits, int readBitOffset)
|
||||
{
|
||||
NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 32)), "ReadUInt32() can only read between 1 and 32 bits");
|
||||
|
||||
if (numberOfBits == 32 && ((readBitOffset % 8) == 0))
|
||||
{
|
||||
fixed (byte* ptr = &(fromBuffer[readBitOffset / 8]))
|
||||
{
|
||||
return *(((uint*)ptr));
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
public static uint ReadUInt32(byte[] fromBuffer, int numberOfBits, int readBitOffset)
|
||||
{
|
||||
NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 32)), "ReadUInt32() can only read between 1 and 32 bits");
|
||||
#endif
|
||||
uint returnValue;
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
returnValue = ReadByte(fromBuffer, numberOfBits, readBitOffset);
|
||||
return returnValue;
|
||||
}
|
||||
returnValue = ReadByte(fromBuffer, 8, readBitOffset);
|
||||
numberOfBits -= 8;
|
||||
readBitOffset += 8;
|
||||
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
returnValue |= (uint)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 8);
|
||||
return returnValue;
|
||||
}
|
||||
returnValue |= (uint)(ReadByte(fromBuffer, 8, readBitOffset) << 8);
|
||||
numberOfBits -= 8;
|
||||
readBitOffset += 8;
|
||||
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
uint r = ReadByte(fromBuffer, numberOfBits, readBitOffset);
|
||||
r <<= 16;
|
||||
returnValue |= r;
|
||||
return returnValue;
|
||||
}
|
||||
returnValue |= (uint)(ReadByte(fromBuffer, 8, readBitOffset) << 16);
|
||||
numberOfBits -= 8;
|
||||
readBitOffset += 8;
|
||||
|
||||
returnValue |= (uint)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 24);
|
||||
|
||||
#if BIGENDIAN
|
||||
// reorder bytes
|
||||
return
|
||||
((returnValue & 0xff000000) >> 24) |
|
||||
((returnValue & 0x00ff0000) >> 8) |
|
||||
((returnValue & 0x0000ff00) << 8) |
|
||||
((returnValue & 0x000000ff) << 24);
|
||||
#else
|
||||
return returnValue;
|
||||
#endif
|
||||
}
|
||||
|
||||
//[CLSCompliant(false)]
|
||||
//public static ulong ReadUInt64(byte[] fromBuffer, int numberOfBits, int readBitOffset)
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unsigned 16 bit integer
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public static void WriteUInt16(ushort source, int numberOfBits, byte[] destination, int destinationBitOffset)
|
||||
{
|
||||
if (numberOfBits == 0)
|
||||
return;
|
||||
|
||||
NetException.Assert((numberOfBits >= 0 && numberOfBits <= 16), "numberOfBits must be between 0 and 16");
|
||||
#if BIGENDIAN
|
||||
// reorder bytes
|
||||
uint intSource = source;
|
||||
intSource = ((intSource & 0x0000ff00) >> 8) | ((intSource & 0x000000ff) << 8);
|
||||
source = (ushort)intSource;
|
||||
#endif
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset);
|
||||
return;
|
||||
}
|
||||
|
||||
NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset);
|
||||
|
||||
numberOfBits -= 8;
|
||||
if (numberOfBits > 0)
|
||||
NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset + 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified number of bits into a byte array
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public static int WriteUInt32(uint source, int numberOfBits, byte[] destination, int destinationBitOffset)
|
||||
{
|
||||
#if BIGENDIAN
|
||||
// reorder bytes
|
||||
source = ((source & 0xff000000) >> 24) |
|
||||
((source & 0x00ff0000) >> 8) |
|
||||
((source & 0x0000ff00) << 8) |
|
||||
((source & 0x000000ff) << 24);
|
||||
#endif
|
||||
|
||||
int returnValue = destinationBitOffset + numberOfBits;
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset);
|
||||
return returnValue;
|
||||
}
|
||||
NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset);
|
||||
destinationBitOffset += 8;
|
||||
numberOfBits -= 8;
|
||||
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset);
|
||||
return returnValue;
|
||||
}
|
||||
NetBitWriter.WriteByte((byte)(source >> 8), 8, destination, destinationBitOffset);
|
||||
destinationBitOffset += 8;
|
||||
numberOfBits -= 8;
|
||||
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
NetBitWriter.WriteByte((byte)(source >> 16), numberOfBits, destination, destinationBitOffset);
|
||||
return returnValue;
|
||||
}
|
||||
NetBitWriter.WriteByte((byte)(source >> 16), 8, destination, destinationBitOffset);
|
||||
destinationBitOffset += 8;
|
||||
numberOfBits -= 8;
|
||||
|
||||
NetBitWriter.WriteByte((byte)(source >> 24), numberOfBits, destination, destinationBitOffset);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified number of bits into a byte array
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public static int WriteUInt64(ulong source, int numberOfBits, byte[] destination, int destinationBitOffset)
|
||||
{
|
||||
#if BIGENDIAN
|
||||
source = ((source & 0xff00000000000000L) >> 56) |
|
||||
((source & 0x00ff000000000000L) >> 40) |
|
||||
((source & 0x0000ff0000000000L) >> 24) |
|
||||
((source & 0x000000ff00000000L) >> 8) |
|
||||
((source & 0x00000000ff000000L) << 8) |
|
||||
((source & 0x0000000000ff0000L) << 24) |
|
||||
((source & 0x000000000000ff00L) << 40) |
|
||||
((source & 0x00000000000000ffL) << 56);
|
||||
#endif
|
||||
|
||||
int returnValue = destinationBitOffset + numberOfBits;
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset);
|
||||
return returnValue;
|
||||
}
|
||||
NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset);
|
||||
destinationBitOffset += 8;
|
||||
numberOfBits -= 8;
|
||||
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset);
|
||||
return returnValue;
|
||||
}
|
||||
NetBitWriter.WriteByte((byte)(source >> 8), 8, destination, destinationBitOffset);
|
||||
destinationBitOffset += 8;
|
||||
numberOfBits -= 8;
|
||||
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
NetBitWriter.WriteByte((byte)(source >> 16), numberOfBits, destination, destinationBitOffset);
|
||||
return returnValue;
|
||||
}
|
||||
NetBitWriter.WriteByte((byte)(source >> 16), 8, destination, destinationBitOffset);
|
||||
destinationBitOffset += 8;
|
||||
numberOfBits -= 8;
|
||||
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
NetBitWriter.WriteByte((byte)(source >> 24), numberOfBits, destination, destinationBitOffset);
|
||||
return returnValue;
|
||||
}
|
||||
NetBitWriter.WriteByte((byte)(source >> 24), 8, destination, destinationBitOffset);
|
||||
destinationBitOffset += 8;
|
||||
numberOfBits -= 8;
|
||||
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
NetBitWriter.WriteByte((byte)(source >> 32), numberOfBits, destination, destinationBitOffset);
|
||||
return returnValue;
|
||||
}
|
||||
NetBitWriter.WriteByte((byte)(source >> 32), 8, destination, destinationBitOffset);
|
||||
destinationBitOffset += 8;
|
||||
numberOfBits -= 8;
|
||||
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
NetBitWriter.WriteByte((byte)(source >> 40), numberOfBits, destination, destinationBitOffset);
|
||||
return returnValue;
|
||||
}
|
||||
NetBitWriter.WriteByte((byte)(source >> 40), 8, destination, destinationBitOffset);
|
||||
destinationBitOffset += 8;
|
||||
numberOfBits -= 8;
|
||||
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
NetBitWriter.WriteByte((byte)(source >> 48), numberOfBits, destination, destinationBitOffset);
|
||||
return returnValue;
|
||||
}
|
||||
NetBitWriter.WriteByte((byte)(source >> 48), 8, destination, destinationBitOffset);
|
||||
destinationBitOffset += 8;
|
||||
numberOfBits -= 8;
|
||||
|
||||
if (numberOfBits <= 8)
|
||||
{
|
||||
NetBitWriter.WriteByte((byte)(source >> 56), numberOfBits, destination, destinationBitOffset);
|
||||
return returnValue;
|
||||
}
|
||||
NetBitWriter.WriteByte((byte)(source >> 56), 8, destination, destinationBitOffset);
|
||||
destinationBitOffset += 8;
|
||||
numberOfBits -= 8;
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
//
|
||||
// Variable size
|
||||
//
|
||||
|
||||
/// <summary>
|
||||
/// Write Base128 encoded variable sized unsigned integer
|
||||
/// </summary>
|
||||
/// <returns>number of bytes written</returns>
|
||||
[CLSCompliant(false)]
|
||||
public static int WriteVariableUInt32(byte[] intoBuffer, int offset, uint value)
|
||||
{
|
||||
int retval = 0;
|
||||
uint num1 = (uint)value;
|
||||
while (num1 >= 0x80)
|
||||
{
|
||||
intoBuffer[offset + retval] = (byte)(num1 | 0x80);
|
||||
num1 = num1 >> 7;
|
||||
retval++;
|
||||
}
|
||||
intoBuffer[offset + retval] = (byte)num1;
|
||||
return retval + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a UInt32 written using WriteUnsignedVarInt(); will increment offset!
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public static uint ReadVariableUInt32(byte[] buffer, ref int offset)
|
||||
{
|
||||
int num1 = 0;
|
||||
int num2 = 0;
|
||||
while (true)
|
||||
{
|
||||
NetException.Assert(num2 != 0x23, "Bad 7-bit encoded integer");
|
||||
|
||||
byte num3 = buffer[offset++];
|
||||
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
|
||||
num2 += 7;
|
||||
if ((num3 & 0x80) == 0)
|
||||
return (uint)num1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
312
SRMP/Lidgren.Network/NetBuffer.Peek.cs
Normal file
312
SRMP/Lidgren.Network/NetBuffer.Peek.cs
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetBuffer
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the internal data buffer
|
||||
/// </summary>
|
||||
public byte[] PeekDataBuffer() { return m_data; }
|
||||
|
||||
//
|
||||
// 1 bit
|
||||
//
|
||||
/// <summary>
|
||||
/// Reads a 1-bit Boolean without advancing the read pointer
|
||||
/// </summary>
|
||||
public bool PeekBoolean()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 1, c_readOverflowError);
|
||||
byte retval = NetBitWriter.ReadByte(m_data, 1, m_readPosition);
|
||||
return (retval > 0 ? true : false);
|
||||
}
|
||||
|
||||
//
|
||||
// 8 bit
|
||||
//
|
||||
/// <summary>
|
||||
/// Reads a Byte without advancing the read pointer
|
||||
/// </summary>
|
||||
public byte PeekByte()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError);
|
||||
byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an SByte without advancing the read pointer
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public sbyte PeekSByte()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError);
|
||||
byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition);
|
||||
return (sbyte)retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of bits into a Byte without advancing the read pointer
|
||||
/// </summary>
|
||||
public byte PeekByte(int numberOfBits)
|
||||
{
|
||||
byte retval = NetBitWriter.ReadByte(m_data, numberOfBits, m_readPosition);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of bytes without advancing the read pointer
|
||||
/// </summary>
|
||||
public byte[] PeekBytes(int numberOfBytes)
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError);
|
||||
|
||||
byte[] retval = new byte[numberOfBytes];
|
||||
NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, retval, 0);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of bytes without advancing the read pointer
|
||||
/// </summary>
|
||||
public void PeekBytes(byte[] into, int offset, int numberOfBytes)
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError);
|
||||
NetException.Assert(offset + numberOfBytes <= into.Length);
|
||||
|
||||
NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, into, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// 16 bit
|
||||
//
|
||||
/// <summary>
|
||||
/// Reads an Int16 without advancing the read pointer
|
||||
/// </summary>
|
||||
public Int16 PeekInt16()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError);
|
||||
uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition);
|
||||
return (short)retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a UInt16 without advancing the read pointer
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public UInt16 PeekUInt16()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError);
|
||||
uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition);
|
||||
return (ushort)retval;
|
||||
}
|
||||
|
||||
//
|
||||
// 32 bit
|
||||
//
|
||||
/// <summary>
|
||||
/// Reads an Int32 without advancing the read pointer
|
||||
/// </summary>
|
||||
public Int32 PeekInt32()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
|
||||
uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
|
||||
return (Int32)retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of bits into an Int32 without advancing the read pointer
|
||||
/// </summary>
|
||||
public Int32 PeekInt32(int numberOfBits)
|
||||
{
|
||||
NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "ReadInt() can only read between 1 and 32 bits");
|
||||
NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError);
|
||||
|
||||
uint retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
|
||||
|
||||
if (numberOfBits == 32)
|
||||
return (int)retval;
|
||||
|
||||
int signBit = 1 << (numberOfBits - 1);
|
||||
if ((retval & signBit) == 0)
|
||||
return (int)retval; // positive
|
||||
|
||||
// negative
|
||||
unchecked
|
||||
{
|
||||
uint mask = ((uint)-1) >> (33 - numberOfBits);
|
||||
uint tmp = (retval & mask) + 1;
|
||||
return -((int)tmp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a UInt32 without advancing the read pointer
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public UInt32 PeekUInt32()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
|
||||
uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of bits into a UInt32 without advancing the read pointer
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public UInt32 PeekUInt32(int numberOfBits)
|
||||
{
|
||||
NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "ReadUInt() can only read between 1 and 32 bits");
|
||||
//NetException.Assert(m_bitLength - m_readBitPtr >= numberOfBits, "tried to read past buffer size");
|
||||
|
||||
UInt32 retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
|
||||
return retval;
|
||||
}
|
||||
|
||||
//
|
||||
// 64 bit
|
||||
//
|
||||
/// <summary>
|
||||
/// Reads a UInt64 without advancing the read pointer
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public UInt64 PeekUInt64()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
|
||||
|
||||
ulong low = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
|
||||
ulong high = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition + 32);
|
||||
|
||||
ulong retval = low + (high << 32);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an Int64 without advancing the read pointer
|
||||
/// </summary>
|
||||
public Int64 PeekInt64()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
|
||||
unchecked
|
||||
{
|
||||
ulong retval = PeekUInt64();
|
||||
long longRetval = (long)retval;
|
||||
return longRetval;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of bits into an UInt64 without advancing the read pointer
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public UInt64 PeekUInt64(int numberOfBits)
|
||||
{
|
||||
NetException.Assert((numberOfBits > 0 && numberOfBits <= 64), "ReadUInt() can only read between 1 and 64 bits");
|
||||
NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError);
|
||||
|
||||
ulong retval;
|
||||
if (numberOfBits <= 32)
|
||||
{
|
||||
retval = (ulong)NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
|
||||
retval |= (UInt64)NetBitWriter.ReadUInt32(m_data, numberOfBits - 32, m_readPosition + 32) << 32;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of bits into an Int64 without advancing the read pointer
|
||||
/// </summary>
|
||||
public Int64 PeekInt64(int numberOfBits)
|
||||
{
|
||||
NetException.Assert(((numberOfBits > 0) && (numberOfBits < 65)), "ReadInt64(bits) can only read between 1 and 64 bits");
|
||||
return (long)PeekUInt64(numberOfBits);
|
||||
}
|
||||
|
||||
//
|
||||
// Floating point
|
||||
//
|
||||
/// <summary>
|
||||
/// Reads a 32-bit Single without advancing the read pointer
|
||||
/// </summary>
|
||||
public float PeekFloat()
|
||||
{
|
||||
return PeekSingle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 32-bit Single without advancing the read pointer
|
||||
/// </summary>
|
||||
public float PeekSingle()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
|
||||
|
||||
if ((m_readPosition & 7) == 0) // read directly
|
||||
{
|
||||
float retval = BitConverter.ToSingle(m_data, m_readPosition >> 3);
|
||||
return retval;
|
||||
}
|
||||
|
||||
byte[] bytes = PeekBytes(4);
|
||||
return BitConverter.ToSingle(bytes, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 64-bit Double without advancing the read pointer
|
||||
/// </summary>
|
||||
public double PeekDouble()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
|
||||
|
||||
if ((m_readPosition & 7) == 0) // read directly
|
||||
{
|
||||
// read directly
|
||||
double retval = BitConverter.ToDouble(m_data, m_readPosition >> 3);
|
||||
return retval;
|
||||
}
|
||||
|
||||
byte[] bytes = PeekBytes(8);
|
||||
return BitConverter.ToDouble(bytes, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a string without advancing the read pointer
|
||||
/// </summary>
|
||||
public string PeekString()
|
||||
{
|
||||
int wasReadPosition = m_readPosition;
|
||||
string retval = ReadString();
|
||||
m_readPosition = wasReadPosition;
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
103
SRMP/Lidgren.Network/NetBuffer.Read.Reflection.cs
Normal file
103
SRMP/Lidgren.Network/NetBuffer.Read.Reflection.cs
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetBuffer
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads all public and private declared instance fields of the object in alphabetical order using reflection
|
||||
/// </summary>
|
||||
public void ReadAllFields(object target)
|
||||
{
|
||||
ReadAllFields(target, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all fields with the specified binding of the object in alphabetical order using reflection
|
||||
/// </summary>
|
||||
public void ReadAllFields(object target, BindingFlags flags)
|
||||
{
|
||||
if (target == null)
|
||||
throw new ArgumentNullException("target");
|
||||
|
||||
Type tp = target.GetType();
|
||||
|
||||
FieldInfo[] fields = tp.GetFields(flags);
|
||||
NetUtility.SortMembersList(fields);
|
||||
|
||||
foreach (FieldInfo fi in fields)
|
||||
{
|
||||
object value;
|
||||
|
||||
// find read method
|
||||
MethodInfo readMethod;
|
||||
if (s_readMethods.TryGetValue(fi.FieldType, out readMethod))
|
||||
{
|
||||
// read value
|
||||
value = readMethod.Invoke(this, null);
|
||||
|
||||
// set the value
|
||||
fi.SetValue(target, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all public and private declared instance fields of the object in alphabetical order using reflection
|
||||
/// </summary>
|
||||
public void ReadAllProperties(object target)
|
||||
{
|
||||
ReadAllProperties(target, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads all fields with the specified binding of the object in alphabetical order using reflection
|
||||
/// </summary>
|
||||
public void ReadAllProperties(object target, BindingFlags flags)
|
||||
{
|
||||
if (target == null)
|
||||
throw new ArgumentNullException("target");
|
||||
|
||||
Type tp = target.GetType();
|
||||
|
||||
PropertyInfo[] fields = tp.GetProperties(flags);
|
||||
NetUtility.SortMembersList(fields);
|
||||
foreach (PropertyInfo fi in fields)
|
||||
{
|
||||
object value;
|
||||
|
||||
// find read method
|
||||
MethodInfo readMethod;
|
||||
if (s_readMethods.TryGetValue(fi.PropertyType, out readMethod))
|
||||
{
|
||||
// read value
|
||||
value = readMethod.Invoke(this, null);
|
||||
|
||||
// set the value
|
||||
var setMethod = fi.GetSetMethod();
|
||||
if (setMethod != null)
|
||||
setMethod.Invoke(target, new object[] { value });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
713
SRMP/Lidgren.Network/NetBuffer.Read.cs
Normal file
713
SRMP/Lidgren.Network/NetBuffer.Read.cs
Normal file
|
|
@ -0,0 +1,713 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
|
||||
#if !__NOIPENDPOINT__
|
||||
using NetEndPoint = System.Net.IPEndPoint;
|
||||
#endif
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for NetIncomingMessage and NetOutgoingMessage
|
||||
/// </summary>
|
||||
public partial class NetBuffer
|
||||
{
|
||||
private const string c_readOverflowError = "Trying to read past the buffer size - likely caused by mismatching Write/Reads, different size or order.";
|
||||
private const int c_bufferSize = 64; // Min 8 to hold anything but strings. Increase it if readed strings usally don't fit inside the buffer
|
||||
private static object s_buffer;
|
||||
|
||||
/// <summary>
|
||||
/// Reads a boolean value (stored as a single bit) written using Write(bool)
|
||||
/// </summary>
|
||||
public bool ReadBoolean()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 1, c_readOverflowError);
|
||||
byte retval = NetBitWriter.ReadByte(m_data, 1, m_readPosition);
|
||||
m_readPosition += 1;
|
||||
return (retval > 0 ? true : false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a byte
|
||||
/// </summary>
|
||||
public byte ReadByte()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError);
|
||||
byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition);
|
||||
m_readPosition += 8;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a byte and returns true or false for success
|
||||
/// </summary>
|
||||
public bool ReadByte(out byte result)
|
||||
{
|
||||
if (m_bitLength - m_readPosition < 8)
|
||||
{
|
||||
result = 0;
|
||||
return false;
|
||||
}
|
||||
result = NetBitWriter.ReadByte(m_data, 8, m_readPosition);
|
||||
m_readPosition += 8;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a signed byte
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public sbyte ReadSByte()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError);
|
||||
byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition);
|
||||
m_readPosition += 8;
|
||||
return (sbyte)retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads 1 to 8 bits into a byte
|
||||
/// </summary>
|
||||
public byte ReadByte(int numberOfBits)
|
||||
{
|
||||
NetException.Assert(numberOfBits > 0 && numberOfBits <= 8, "ReadByte(bits) can only read between 1 and 8 bits");
|
||||
byte retval = NetBitWriter.ReadByte(m_data, numberOfBits, m_readPosition);
|
||||
m_readPosition += numberOfBits;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of bytes
|
||||
/// </summary>
|
||||
public byte[] ReadBytes(int numberOfBytes)
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition + 7 >= (numberOfBytes * 8), c_readOverflowError);
|
||||
|
||||
byte[] retval = new byte[numberOfBytes];
|
||||
NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, retval, 0);
|
||||
m_readPosition += (8 * numberOfBytes);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of bytes and returns true for success
|
||||
/// </summary>
|
||||
public bool ReadBytes(int numberOfBytes, out byte[] result)
|
||||
{
|
||||
if (m_bitLength - m_readPosition + 7 < (numberOfBytes * 8))
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
result = new byte[numberOfBytes];
|
||||
NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, result, 0);
|
||||
m_readPosition += (8 * numberOfBytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of bytes into a preallocated array
|
||||
/// </summary>
|
||||
/// <param name="into">The destination array</param>
|
||||
/// <param name="offset">The offset where to start writing in the destination array</param>
|
||||
/// <param name="numberOfBytes">The number of bytes to read</param>
|
||||
public void ReadBytes(byte[] into, int offset, int numberOfBytes)
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition + 7 >= (numberOfBytes * 8), c_readOverflowError);
|
||||
NetException.Assert(offset + numberOfBytes <= into.Length);
|
||||
|
||||
NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, into, offset);
|
||||
m_readPosition += (8 * numberOfBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the specified number of bits into a preallocated array
|
||||
/// </summary>
|
||||
/// <param name="into">The destination array</param>
|
||||
/// <param name="offset">The offset where to start writing in the destination array</param>
|
||||
/// <param name="numberOfBits">The number of bits to read</param>
|
||||
public void ReadBits(byte[] into, int offset, int numberOfBits)
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError);
|
||||
NetException.Assert(offset + NetUtility.BytesToHoldBits(numberOfBits) <= into.Length);
|
||||
|
||||
int numberOfWholeBytes = numberOfBits / 8;
|
||||
int extraBits = numberOfBits - (numberOfWholeBytes * 8);
|
||||
|
||||
NetBitWriter.ReadBytes(m_data, numberOfWholeBytes, m_readPosition, into, offset);
|
||||
m_readPosition += (8 * numberOfWholeBytes);
|
||||
|
||||
if (extraBits > 0)
|
||||
into[offset + numberOfWholeBytes] = ReadByte(extraBits);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 16 bit signed integer written using Write(Int16)
|
||||
/// </summary>
|
||||
public Int16 ReadInt16()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError);
|
||||
uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition);
|
||||
m_readPosition += 16;
|
||||
return (short)retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 16 bit unsigned integer written using Write(UInt16)
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public UInt16 ReadUInt16()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError);
|
||||
uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition);
|
||||
m_readPosition += 16;
|
||||
return (ushort)retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 32 bit signed integer written using Write(Int32)
|
||||
/// </summary>
|
||||
public Int32 ReadInt32()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
|
||||
uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
|
||||
m_readPosition += 32;
|
||||
return (Int32)retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 32 bit signed integer written using Write(Int32)
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public bool ReadInt32(out Int32 result)
|
||||
{
|
||||
if (m_bitLength - m_readPosition < 32)
|
||||
{
|
||||
result = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
result = (Int32)NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
|
||||
m_readPosition += 32;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a signed integer stored in 1 to 32 bits, written using Write(Int32, Int32)
|
||||
/// </summary>
|
||||
public Int32 ReadInt32(int numberOfBits)
|
||||
{
|
||||
NetException.Assert(numberOfBits > 0 && numberOfBits <= 32, "ReadInt32(bits) can only read between 1 and 32 bits");
|
||||
NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError);
|
||||
|
||||
uint retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
|
||||
m_readPosition += numberOfBits;
|
||||
|
||||
if (numberOfBits == 32)
|
||||
return (int)retval;
|
||||
|
||||
int signBit = 1 << (numberOfBits - 1);
|
||||
if ((retval & signBit) == 0)
|
||||
return (int)retval; // positive
|
||||
|
||||
// negative
|
||||
unchecked
|
||||
{
|
||||
uint mask = ((uint)-1) >> (33 - numberOfBits);
|
||||
uint tmp = (retval & mask) + 1;
|
||||
return -((int)tmp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an 32 bit unsigned integer written using Write(UInt32)
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public UInt32 ReadUInt32()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
|
||||
uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
|
||||
m_readPosition += 32;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an 32 bit unsigned integer written using Write(UInt32) and returns true for success
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public bool ReadUInt32(out UInt32 result)
|
||||
{
|
||||
if (m_bitLength - m_readPosition < 32)
|
||||
{
|
||||
result = 0;
|
||||
return false;
|
||||
}
|
||||
result = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
|
||||
m_readPosition += 32;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an unsigned integer stored in 1 to 32 bits, written using Write(UInt32, Int32)
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public UInt32 ReadUInt32(int numberOfBits)
|
||||
{
|
||||
NetException.Assert(numberOfBits > 0 && numberOfBits <= 32, "ReadUInt32(bits) can only read between 1 and 32 bits");
|
||||
//NetException.Assert(m_bitLength - m_readBitPtr >= numberOfBits, "tried to read past buffer size");
|
||||
|
||||
UInt32 retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
|
||||
m_readPosition += numberOfBits;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 64 bit unsigned integer written using Write(UInt64)
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public UInt64 ReadUInt64()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
|
||||
|
||||
ulong low = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
|
||||
m_readPosition += 32;
|
||||
ulong high = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
|
||||
|
||||
ulong retval = low + (high << 32);
|
||||
|
||||
m_readPosition += 32;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 64 bit signed integer written using Write(Int64)
|
||||
/// </summary>
|
||||
public Int64 ReadInt64()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
|
||||
unchecked
|
||||
{
|
||||
ulong retval = ReadUInt64();
|
||||
long longRetval = (long)retval;
|
||||
return longRetval;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an unsigned integer stored in 1 to 64 bits, written using Write(UInt64, Int32)
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public UInt64 ReadUInt64(int numberOfBits)
|
||||
{
|
||||
NetException.Assert(numberOfBits > 0 && numberOfBits <= 64, "ReadUInt64(bits) can only read between 1 and 64 bits");
|
||||
NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError);
|
||||
|
||||
ulong retval;
|
||||
if (numberOfBits <= 32)
|
||||
{
|
||||
retval = (ulong)NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition);
|
||||
retval |= (UInt64)NetBitWriter.ReadUInt32(m_data, numberOfBits - 32, m_readPosition + 32) << 32;
|
||||
}
|
||||
m_readPosition += numberOfBits;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a signed integer stored in 1 to 64 bits, written using Write(Int64, Int32)
|
||||
/// </summary>
|
||||
public Int64 ReadInt64(int numberOfBits)
|
||||
{
|
||||
NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 64)), "ReadInt64(bits) can only read between 1 and 64 bits");
|
||||
return (long)ReadUInt64(numberOfBits);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 32 bit floating point value written using Write(Single)
|
||||
/// </summary>
|
||||
public float ReadFloat()
|
||||
{
|
||||
return ReadSingle();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 32 bit floating point value written using Write(Single)
|
||||
/// </summary>
|
||||
public float ReadSingle()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError);
|
||||
|
||||
if ((m_readPosition & 7) == 0) // read directly
|
||||
{
|
||||
float retval = BitConverter.ToSingle(m_data, m_readPosition >> 3);
|
||||
m_readPosition += 32;
|
||||
return retval;
|
||||
}
|
||||
|
||||
byte[] bytes = (byte[]) Interlocked.Exchange(ref s_buffer, null) ?? new byte[c_bufferSize];
|
||||
ReadBytes(bytes, 0, 4);
|
||||
float res = BitConverter.ToSingle(bytes, 0);
|
||||
s_buffer = bytes;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 32 bit floating point value written using Write(Single)
|
||||
/// </summary>
|
||||
public bool ReadSingle(out float result)
|
||||
{
|
||||
if (m_bitLength - m_readPosition < 32)
|
||||
{
|
||||
result = 0.0f;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((m_readPosition & 7) == 0) // read directly
|
||||
{
|
||||
result = BitConverter.ToSingle(m_data, m_readPosition >> 3);
|
||||
m_readPosition += 32;
|
||||
return true;
|
||||
}
|
||||
|
||||
byte[] bytes = (byte[]) Interlocked.Exchange(ref s_buffer, null) ?? new byte[c_bufferSize];
|
||||
ReadBytes(bytes, 0, 4);
|
||||
result = BitConverter.ToSingle(bytes, 0);
|
||||
s_buffer = bytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 64 bit floating point value written using Write(Double)
|
||||
/// </summary>
|
||||
public double ReadDouble()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError);
|
||||
|
||||
if ((m_readPosition & 7) == 0) // read directly
|
||||
{
|
||||
// read directly
|
||||
double retval = BitConverter.ToDouble(m_data, m_readPosition >> 3);
|
||||
m_readPosition += 64;
|
||||
return retval;
|
||||
}
|
||||
|
||||
byte[] bytes = (byte[]) Interlocked.Exchange(ref s_buffer, null) ?? new byte[c_bufferSize];
|
||||
ReadBytes(bytes, 0, 8);
|
||||
double res = BitConverter.ToDouble(bytes, 0);
|
||||
s_buffer = bytes;
|
||||
return res;
|
||||
}
|
||||
|
||||
//
|
||||
// Variable bit count
|
||||
//
|
||||
|
||||
/// <summary>
|
||||
/// Reads a variable sized UInt32 written using WriteVariableUInt32()
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public uint ReadVariableUInt32()
|
||||
{
|
||||
int num1 = 0;
|
||||
int num2 = 0;
|
||||
while (m_bitLength - m_readPosition >= 8)
|
||||
{
|
||||
byte num3 = this.ReadByte();
|
||||
num1 |= (num3 & 0x7f) << num2;
|
||||
num2 += 7;
|
||||
if ((num3 & 0x80) == 0)
|
||||
return (uint)num1;
|
||||
}
|
||||
|
||||
// ouch; failed to find enough bytes; malformed variable length number?
|
||||
return (uint)num1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a variable sized UInt32 written using WriteVariableUInt32() and returns true for success
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public bool ReadVariableUInt32(out uint result)
|
||||
{
|
||||
int num1 = 0;
|
||||
int num2 = 0;
|
||||
while (m_bitLength - m_readPosition >= 8)
|
||||
{
|
||||
byte num3;
|
||||
if (ReadByte(out num3) == false)
|
||||
{
|
||||
result = 0;
|
||||
return false;
|
||||
}
|
||||
num1 |= (num3 & 0x7f) << num2;
|
||||
num2 += 7;
|
||||
if ((num3 & 0x80) == 0)
|
||||
{
|
||||
result = (uint)num1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
result = (uint)num1;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a variable sized Int32 written using WriteVariableInt32()
|
||||
/// </summary>
|
||||
public int ReadVariableInt32()
|
||||
{
|
||||
uint n = ReadVariableUInt32();
|
||||
return (int)(n >> 1) ^ -(int)(n & 1); // decode zigzag
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a variable sized Int64 written using WriteVariableInt64()
|
||||
/// </summary>
|
||||
public Int64 ReadVariableInt64()
|
||||
{
|
||||
UInt64 n = ReadVariableUInt64();
|
||||
return (Int64)(n >> 1) ^ -(long)(n & 1); // decode zigzag
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a variable sized UInt32 written using WriteVariableInt64()
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public UInt64 ReadVariableUInt64()
|
||||
{
|
||||
UInt64 num1 = 0;
|
||||
int num2 = 0;
|
||||
while (m_bitLength - m_readPosition >= 8)
|
||||
{
|
||||
//if (num2 == 0x23)
|
||||
// throw new FormatException("Bad 7-bit encoded integer");
|
||||
|
||||
byte num3 = this.ReadByte();
|
||||
num1 |= ((UInt64)num3 & 0x7f) << num2;
|
||||
num2 += 7;
|
||||
if ((num3 & 0x80) == 0)
|
||||
return num1;
|
||||
}
|
||||
|
||||
// ouch; failed to find enough bytes; malformed variable length number?
|
||||
return num1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 32 bit floating point value written using WriteSignedSingle()
|
||||
/// </summary>
|
||||
/// <param name="numberOfBits">The number of bits used when writing the value</param>
|
||||
/// <returns>A floating point value larger or equal to -1 and smaller or equal to 1</returns>
|
||||
public float ReadSignedSingle(int numberOfBits)
|
||||
{
|
||||
uint encodedVal = ReadUInt32(numberOfBits);
|
||||
int maxVal = (1 << numberOfBits) - 1;
|
||||
return ((float)(encodedVal + 1) / (float)(maxVal + 1) - 0.5f) * 2.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 32 bit floating point value written using WriteUnitSingle()
|
||||
/// </summary>
|
||||
/// <param name="numberOfBits">The number of bits used when writing the value</param>
|
||||
/// <returns>A floating point value larger or equal to 0 and smaller or equal to 1</returns>
|
||||
public float ReadUnitSingle(int numberOfBits)
|
||||
{
|
||||
uint encodedVal = ReadUInt32(numberOfBits);
|
||||
int maxVal = (1 << numberOfBits) - 1;
|
||||
return (float)(encodedVal + 1) / (float)(maxVal + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 32 bit floating point value written using WriteRangedSingle()
|
||||
/// </summary>
|
||||
/// <param name="min">The minimum value used when writing the value</param>
|
||||
/// <param name="max">The maximum value used when writing the value</param>
|
||||
/// <param name="numberOfBits">The number of bits used when writing the value</param>
|
||||
/// <returns>A floating point value larger or equal to MIN and smaller or equal to MAX</returns>
|
||||
public float ReadRangedSingle(float min, float max, int numberOfBits)
|
||||
{
|
||||
float range = max - min;
|
||||
int maxVal = (1 << numberOfBits) - 1;
|
||||
float encodedVal = (float)ReadUInt32(numberOfBits);
|
||||
float unit = encodedVal / (float)maxVal;
|
||||
return min + (unit * range);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 32 bit integer value written using WriteRangedInteger()
|
||||
/// </summary>
|
||||
/// <param name="min">The minimum value used when writing the value</param>
|
||||
/// <param name="max">The maximum value used when writing the value</param>
|
||||
/// <returns>A signed integer value larger or equal to MIN and smaller or equal to MAX</returns>
|
||||
public int ReadRangedInteger(int min, int max)
|
||||
{
|
||||
uint range = (uint)(max - min);
|
||||
int numBits = NetUtility.BitsToHoldUInt(range);
|
||||
|
||||
uint rvalue = ReadUInt32(numBits);
|
||||
return (int)(min + rvalue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a 64 bit integer value written using WriteRangedInteger() (64 version)
|
||||
/// </summary>
|
||||
/// <param name="min">The minimum value used when writing the value</param>
|
||||
/// <param name="max">The maximum value used when writing the value</param>
|
||||
/// <returns>A signed integer value larger or equal to MIN and smaller or equal to MAX</returns>
|
||||
public long ReadRangedInteger(long min, long max)
|
||||
{
|
||||
ulong range = (ulong)(max - min);
|
||||
int numBits = NetUtility.BitsToHoldUInt64(range);
|
||||
|
||||
ulong rvalue = ReadUInt64(numBits);
|
||||
return min + (long)rvalue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a string written using Write(string)
|
||||
/// </summary>
|
||||
public string ReadString()
|
||||
{
|
||||
int byteLen = (int)ReadVariableUInt32();
|
||||
|
||||
if (byteLen <= 0)
|
||||
return String.Empty;
|
||||
|
||||
if ((ulong)(m_bitLength - m_readPosition) < ((ulong)byteLen * 8))
|
||||
{
|
||||
// not enough data
|
||||
#if DEBUG
|
||||
|
||||
throw new NetException(c_readOverflowError);
|
||||
#else
|
||||
m_readPosition = m_bitLength;
|
||||
return null; // unfortunate; but we need to protect against DDOS
|
||||
#endif
|
||||
}
|
||||
|
||||
if ((m_readPosition & 7) == 0)
|
||||
{
|
||||
// read directly
|
||||
string retval = System.Text.Encoding.UTF8.GetString(m_data, m_readPosition >> 3, byteLen);
|
||||
m_readPosition += (8 * byteLen);
|
||||
return retval;
|
||||
}
|
||||
|
||||
if (byteLen <= c_bufferSize) {
|
||||
byte[] buffer = (byte[]) Interlocked.Exchange(ref s_buffer, null) ?? new byte[c_bufferSize];
|
||||
ReadBytes(buffer, 0, byteLen);
|
||||
string retval = Encoding.UTF8.GetString(buffer, 0, byteLen);
|
||||
s_buffer = buffer;
|
||||
return retval;
|
||||
} else {
|
||||
byte[] bytes = ReadBytes(byteLen);
|
||||
return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a string written using Write(string) and returns true for success
|
||||
/// </summary>
|
||||
public bool ReadString(out string result)
|
||||
{
|
||||
uint byteLen;
|
||||
if (ReadVariableUInt32(out byteLen) == false)
|
||||
{
|
||||
result = String.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (byteLen <= 0)
|
||||
{
|
||||
result = String.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_bitLength - m_readPosition < (byteLen * 8))
|
||||
{
|
||||
result = String.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((m_readPosition & 7) == 0)
|
||||
{
|
||||
// read directly
|
||||
result = System.Text.Encoding.UTF8.GetString(m_data, m_readPosition >> 3, (int)byteLen);
|
||||
m_readPosition += (8 * (int)byteLen);
|
||||
return true;
|
||||
}
|
||||
|
||||
byte[] bytes;
|
||||
if (ReadBytes((int)byteLen, out bytes) == false)
|
||||
{
|
||||
result = String.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
result = System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a value, in local time comparable to NetTime.Now, written using WriteTime() for the connection supplied
|
||||
/// </summary>
|
||||
public double ReadTime(NetConnection connection, bool highPrecision)
|
||||
{
|
||||
double remoteTime = (highPrecision ? ReadDouble() : (double)ReadSingle());
|
||||
|
||||
if (connection == null)
|
||||
throw new NetException("Cannot call ReadTime() on message without a connected sender (ie. unconnected messages)");
|
||||
|
||||
// lets bypass NetConnection.GetLocalTime for speed
|
||||
return remoteTime - connection.m_remoteTimeOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a stored IPv4 endpoint description
|
||||
/// </summary>
|
||||
public NetEndPoint ReadIPEndPoint()
|
||||
{
|
||||
byte len = ReadByte();
|
||||
byte[] addressBytes = ReadBytes(len);
|
||||
int port = (int)ReadUInt16();
|
||||
|
||||
var address = NetUtility.CreateAddressFromBytes(addressBytes);
|
||||
return new NetEndPoint(address, port);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes.
|
||||
/// </summary>
|
||||
public void SkipPadBits()
|
||||
{
|
||||
m_readPosition = ((m_readPosition + 7) >> 3) * 8;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes.
|
||||
/// </summary>
|
||||
public void ReadPadBits()
|
||||
{
|
||||
m_readPosition = ((m_readPosition + 7) >> 3) * 8;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pads data with the specified number of bits.
|
||||
/// </summary>
|
||||
public void SkipPadBits(int numberOfBits)
|
||||
{
|
||||
m_readPosition += numberOfBits;
|
||||
}
|
||||
}
|
||||
}
|
||||
94
SRMP/Lidgren.Network/NetBuffer.Write.Reflection.cs
Normal file
94
SRMP/Lidgren.Network/NetBuffer.Write.Reflection.cs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetBuffer
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes all public and private declared instance fields of the object in alphabetical order using reflection
|
||||
/// </summary>
|
||||
public void WriteAllFields(object ob)
|
||||
{
|
||||
WriteAllFields(ob, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes all fields with specified binding in alphabetical order using reflection
|
||||
/// </summary>
|
||||
public void WriteAllFields(object ob, BindingFlags flags)
|
||||
{
|
||||
if (ob == null)
|
||||
return;
|
||||
Type tp = ob.GetType();
|
||||
|
||||
FieldInfo[] fields = tp.GetFields(flags);
|
||||
NetUtility.SortMembersList(fields);
|
||||
|
||||
foreach (FieldInfo fi in fields)
|
||||
{
|
||||
object value = fi.GetValue(ob);
|
||||
|
||||
// find the appropriate Write method
|
||||
MethodInfo writeMethod;
|
||||
if (s_writeMethods.TryGetValue(fi.FieldType, out writeMethod))
|
||||
writeMethod.Invoke(this, new object[] { value });
|
||||
else
|
||||
throw new NetException("Failed to find write method for type " + fi.FieldType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes all public and private declared instance properties of the object in alphabetical order using reflection
|
||||
/// </summary>
|
||||
public void WriteAllProperties(object ob)
|
||||
{
|
||||
WriteAllProperties(ob, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes all properties with specified binding in alphabetical order using reflection
|
||||
/// </summary>
|
||||
public void WriteAllProperties(object ob, BindingFlags flags)
|
||||
{
|
||||
if (ob == null)
|
||||
return;
|
||||
Type tp = ob.GetType();
|
||||
|
||||
PropertyInfo[] fields = tp.GetProperties(flags);
|
||||
NetUtility.SortMembersList(fields);
|
||||
|
||||
foreach (PropertyInfo fi in fields)
|
||||
{
|
||||
MethodInfo getMethod = fi.GetGetMethod();
|
||||
if (getMethod != null)
|
||||
{
|
||||
object value = getMethod.Invoke(ob, null);
|
||||
|
||||
// find the appropriate Write method
|
||||
MethodInfo writeMethod;
|
||||
if (s_writeMethods.TryGetValue(fi.PropertyType, out writeMethod))
|
||||
writeMethod.Invoke(this, new object[] { value });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
703
SRMP/Lidgren.Network/NetBuffer.Write.cs
Normal file
703
SRMP/Lidgren.Network/NetBuffer.Write.cs
Normal file
|
|
@ -0,0 +1,703 @@
|
|||
//#define UNSAFE
|
||||
//#define BIGENDIAN
|
||||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility struct for writing Singles
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct SingleUIntUnion
|
||||
{
|
||||
/// <summary>
|
||||
/// Value as a 32 bit float
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
public float SingleValue;
|
||||
|
||||
/// <summary>
|
||||
/// Value as an unsigned 32 bit integer
|
||||
/// </summary>
|
||||
[FieldOffset(0)]
|
||||
[CLSCompliant(false)]
|
||||
public uint UIntValue;
|
||||
}
|
||||
|
||||
public partial class NetBuffer
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures the buffer can hold this number of bits
|
||||
/// </summary>
|
||||
public void EnsureBufferSize(int numberOfBits)
|
||||
{
|
||||
int byteLen = ((numberOfBits + 7) >> 3);
|
||||
if (m_data == null)
|
||||
{
|
||||
m_data = new byte[byteLen + c_overAllocateAmount];
|
||||
return;
|
||||
}
|
||||
if (m_data.Length < byteLen)
|
||||
Array.Resize<byte>(ref m_data, byteLen + c_overAllocateAmount);
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the buffer can hold this number of bits
|
||||
/// </summary>
|
||||
internal void InternalEnsureBufferSize(int numberOfBits)
|
||||
{
|
||||
int byteLen = ((numberOfBits + 7) >> 3);
|
||||
if (m_data == null)
|
||||
{
|
||||
m_data = new byte[byteLen];
|
||||
return;
|
||||
}
|
||||
if (m_data.Length < byteLen)
|
||||
Array.Resize<byte>(ref m_data, byteLen);
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a boolean value using 1 bit
|
||||
/// </summary>
|
||||
public void Write(bool value)
|
||||
{
|
||||
EnsureBufferSize(m_bitLength + 1);
|
||||
NetBitWriter.WriteByte((value ? (byte)1 : (byte)0), 1, m_data, m_bitLength);
|
||||
m_bitLength += 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a byte
|
||||
/// </summary>
|
||||
public void Write(byte source)
|
||||
{
|
||||
EnsureBufferSize(m_bitLength + 8);
|
||||
NetBitWriter.WriteByte(source, 8, m_data, m_bitLength);
|
||||
m_bitLength += 8;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a byte at a given offset in the buffer
|
||||
/// </summary>
|
||||
public void WriteAt(Int32 offset, byte source) {
|
||||
int newBitLength = Math.Max(m_bitLength, offset + 8);
|
||||
EnsureBufferSize(newBitLength);
|
||||
NetBitWriter.WriteByte((byte) source, 8, m_data, offset);
|
||||
m_bitLength = newBitLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a signed byte
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public void Write(sbyte source)
|
||||
{
|
||||
EnsureBufferSize(m_bitLength + 8);
|
||||
NetBitWriter.WriteByte((byte)source, 8, m_data, m_bitLength);
|
||||
m_bitLength += 8;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes 1 to 8 bits of a byte
|
||||
/// </summary>
|
||||
public void Write(byte source, int numberOfBits)
|
||||
{
|
||||
NetException.Assert((numberOfBits > 0 && numberOfBits <= 8), "Write(byte, numberOfBits) can only write between 1 and 8 bits");
|
||||
EnsureBufferSize(m_bitLength + numberOfBits);
|
||||
NetBitWriter.WriteByte(source, numberOfBits, m_data, m_bitLength);
|
||||
m_bitLength += numberOfBits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes all bytes in an array
|
||||
/// </summary>
|
||||
public void Write(byte[] source)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException("source");
|
||||
int bits = source.Length * 8;
|
||||
EnsureBufferSize(m_bitLength + bits);
|
||||
NetBitWriter.WriteBytes(source, 0, source.Length, m_data, m_bitLength);
|
||||
m_bitLength += bits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified number of bytes from an array
|
||||
/// </summary>
|
||||
public void Write(byte[] source, int offsetInBytes, int numberOfBytes)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException("source");
|
||||
int bits = numberOfBytes * 8;
|
||||
EnsureBufferSize(m_bitLength + bits);
|
||||
NetBitWriter.WriteBytes(source, offsetInBytes, numberOfBytes, m_data, m_bitLength);
|
||||
m_bitLength += bits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unsigned 16 bit integer
|
||||
/// </summary>
|
||||
/// <param name="source"></param>
|
||||
[CLSCompliant(false)]
|
||||
public void Write(UInt16 source)
|
||||
{
|
||||
EnsureBufferSize(m_bitLength + 16);
|
||||
NetBitWriter.WriteUInt16(source, 16, m_data, m_bitLength);
|
||||
m_bitLength += 16;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 16 bit unsigned integer at a given offset in the buffer
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public void WriteAt(Int32 offset, UInt16 source)
|
||||
{
|
||||
int newBitLength = Math.Max(m_bitLength, offset + 16);
|
||||
EnsureBufferSize(newBitLength);
|
||||
NetBitWriter.WriteUInt16(source, 16, m_data, offset);
|
||||
m_bitLength = newBitLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unsigned integer using 1 to 16 bits
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public void Write(UInt16 source, int numberOfBits)
|
||||
{
|
||||
NetException.Assert((numberOfBits > 0 && numberOfBits <= 16), "Write(ushort, numberOfBits) can only write between 1 and 16 bits");
|
||||
EnsureBufferSize(m_bitLength + numberOfBits);
|
||||
NetBitWriter.WriteUInt16(source, numberOfBits, m_data, m_bitLength);
|
||||
m_bitLength += numberOfBits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a signed 16 bit integer
|
||||
/// </summary>
|
||||
public void Write(Int16 source)
|
||||
{
|
||||
EnsureBufferSize(m_bitLength + 16);
|
||||
NetBitWriter.WriteUInt16((ushort)source, 16, m_data, m_bitLength);
|
||||
m_bitLength += 16;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 16 bit signed integer at a given offset in the buffer
|
||||
/// </summary>
|
||||
public void WriteAt(Int32 offset, Int16 source)
|
||||
{
|
||||
int newBitLength = Math.Max(m_bitLength, offset + 16);
|
||||
EnsureBufferSize(newBitLength);
|
||||
NetBitWriter.WriteUInt16((ushort)source, 16, m_data, offset);
|
||||
m_bitLength = newBitLength;
|
||||
}
|
||||
|
||||
#if UNSAFE
|
||||
/// <summary>
|
||||
/// Writes a 32 bit signed integer
|
||||
/// </summary>
|
||||
public unsafe void Write(Int32 source)
|
||||
{
|
||||
EnsureBufferSize(m_bitLength + 32);
|
||||
|
||||
// can write fast?
|
||||
if (m_bitLength % 8 == 0)
|
||||
{
|
||||
fixed (byte* numRef = &Data[m_bitLength / 8])
|
||||
{
|
||||
*((int*)numRef) = source;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NetBitWriter.WriteUInt32((UInt32)source, 32, Data, m_bitLength);
|
||||
}
|
||||
m_bitLength += 32;
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Writes a 32 bit signed integer
|
||||
/// </summary>
|
||||
public void Write(Int32 source)
|
||||
{
|
||||
EnsureBufferSize(m_bitLength + 32);
|
||||
NetBitWriter.WriteUInt32((UInt32)source, 32, m_data, m_bitLength);
|
||||
m_bitLength += 32;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 32 bit signed integer at a given offset in the buffer
|
||||
/// </summary>
|
||||
public void WriteAt(Int32 offset, Int32 source)
|
||||
{
|
||||
int newBitLength = Math.Max(m_bitLength, offset + 32);
|
||||
EnsureBufferSize(newBitLength);
|
||||
NetBitWriter.WriteUInt32((UInt32)source, 32, m_data, offset);
|
||||
m_bitLength = newBitLength;
|
||||
}
|
||||
|
||||
#if UNSAFE
|
||||
/// <summary>
|
||||
/// Writes a 32 bit unsigned integer
|
||||
/// </summary>
|
||||
public unsafe void Write(UInt32 source)
|
||||
{
|
||||
EnsureBufferSize(m_bitLength + 32);
|
||||
|
||||
// can write fast?
|
||||
if (m_bitLength % 8 == 0)
|
||||
{
|
||||
fixed (byte* numRef = &Data[m_bitLength / 8])
|
||||
{
|
||||
*((uint*)numRef) = source;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NetBitWriter.WriteUInt32(source, 32, Data, m_bitLength);
|
||||
}
|
||||
|
||||
m_bitLength += 32;
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Writes a 32 bit unsigned integer
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public void Write(UInt32 source)
|
||||
{
|
||||
EnsureBufferSize(m_bitLength + 32);
|
||||
NetBitWriter.WriteUInt32(source, 32, m_data, m_bitLength);
|
||||
m_bitLength += 32;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 32 bit unsigned integer at a given offset in the buffer
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public void WriteAt(Int32 offset, UInt32 source)
|
||||
{
|
||||
int newBitLength = Math.Max(m_bitLength, offset + 32);
|
||||
EnsureBufferSize(newBitLength);
|
||||
NetBitWriter.WriteUInt32(source, 32, m_data, offset);
|
||||
m_bitLength = newBitLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 32 bit signed integer
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public void Write(UInt32 source, int numberOfBits)
|
||||
{
|
||||
NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(uint, numberOfBits) can only write between 1 and 32 bits");
|
||||
EnsureBufferSize(m_bitLength + numberOfBits);
|
||||
NetBitWriter.WriteUInt32(source, numberOfBits, m_data, m_bitLength);
|
||||
m_bitLength += numberOfBits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a signed integer using 1 to 32 bits
|
||||
/// </summary>
|
||||
public void Write(Int32 source, int numberOfBits)
|
||||
{
|
||||
NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(int, numberOfBits) can only write between 1 and 32 bits");
|
||||
EnsureBufferSize(m_bitLength + numberOfBits);
|
||||
|
||||
if (numberOfBits != 32)
|
||||
{
|
||||
// make first bit sign
|
||||
int signBit = 1 << (numberOfBits - 1);
|
||||
if (source < 0)
|
||||
source = (-source - 1) | signBit;
|
||||
else
|
||||
source &= (~signBit);
|
||||
}
|
||||
|
||||
NetBitWriter.WriteUInt32((uint)source, numberOfBits, m_data, m_bitLength);
|
||||
|
||||
m_bitLength += numberOfBits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 64 bit unsigned integer
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public void Write(UInt64 source)
|
||||
{
|
||||
EnsureBufferSize(m_bitLength + 64);
|
||||
NetBitWriter.WriteUInt64(source, 64, m_data, m_bitLength);
|
||||
m_bitLength += 64;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 64 bit unsigned integer at a given offset in the buffer
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public void WriteAt(Int32 offset, UInt64 source)
|
||||
{
|
||||
int newBitLength = Math.Max(m_bitLength, offset + 64);
|
||||
EnsureBufferSize(newBitLength);
|
||||
NetBitWriter.WriteUInt64(source, 64, m_data, offset);
|
||||
m_bitLength = newBitLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unsigned integer using 1 to 64 bits
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public void Write(UInt64 source, int numberOfBits)
|
||||
{
|
||||
EnsureBufferSize(m_bitLength + numberOfBits);
|
||||
NetBitWriter.WriteUInt64(source, numberOfBits, m_data, m_bitLength);
|
||||
m_bitLength += numberOfBits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a 64 bit signed integer
|
||||
/// </summary>
|
||||
public void Write(Int64 source)
|
||||
{
|
||||
EnsureBufferSize(m_bitLength + 64);
|
||||
ulong usource = (ulong)source;
|
||||
NetBitWriter.WriteUInt64(usource, 64, m_data, m_bitLength);
|
||||
m_bitLength += 64;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a signed integer using 1 to 64 bits
|
||||
/// </summary>
|
||||
public void Write(Int64 source, int numberOfBits)
|
||||
{
|
||||
EnsureBufferSize(m_bitLength + numberOfBits);
|
||||
ulong usource = (ulong)source;
|
||||
NetBitWriter.WriteUInt64(usource, numberOfBits, m_data, m_bitLength);
|
||||
m_bitLength += numberOfBits;
|
||||
}
|
||||
|
||||
//
|
||||
// Floating point
|
||||
//
|
||||
#if UNSAFE
|
||||
/// <summary>
|
||||
/// Writes a 32 bit floating point value
|
||||
/// </summary>
|
||||
public unsafe void Write(float source)
|
||||
{
|
||||
uint val = *((uint*)&source);
|
||||
#if BIGENDIAN
|
||||
val = NetUtility.SwapByteOrder(val);
|
||||
#endif
|
||||
Write(val);
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Writes a 32 bit floating point value
|
||||
/// </summary>
|
||||
public void Write(float source)
|
||||
{
|
||||
// Use union to avoid BitConverter.GetBytes() which allocates memory on the heap
|
||||
SingleUIntUnion su;
|
||||
su.UIntValue = 0; // must initialize every member of the union to avoid warning
|
||||
su.SingleValue = source;
|
||||
|
||||
#if BIGENDIAN
|
||||
// swap byte order
|
||||
su.UIntValue = NetUtility.SwapByteOrder(su.UIntValue);
|
||||
#endif
|
||||
Write(su.UIntValue);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if UNSAFE
|
||||
/// <summary>
|
||||
/// Writes a 64 bit floating point value
|
||||
/// </summary>
|
||||
public unsafe void Write(double source)
|
||||
{
|
||||
ulong val = *((ulong*)&source);
|
||||
#if BIGENDIAN
|
||||
val = NetUtility.SwapByteOrder(val);
|
||||
#endif
|
||||
Write(val);
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Writes a 64 bit floating point value
|
||||
/// </summary>
|
||||
public void Write(double source)
|
||||
{
|
||||
byte[] val = BitConverter.GetBytes(source);
|
||||
#if BIGENDIAN
|
||||
// 0 1 2 3 4 5 6 7
|
||||
|
||||
// swap byte order
|
||||
byte tmp = val[7];
|
||||
val[7] = val[0];
|
||||
val[0] = tmp;
|
||||
|
||||
tmp = val[6];
|
||||
val[6] = val[1];
|
||||
val[1] = tmp;
|
||||
|
||||
tmp = val[5];
|
||||
val[5] = val[2];
|
||||
val[2] = tmp;
|
||||
|
||||
tmp = val[4];
|
||||
val[4] = val[3];
|
||||
val[3] = tmp;
|
||||
#endif
|
||||
Write(val);
|
||||
}
|
||||
#endif
|
||||
|
||||
//
|
||||
// Variable bits
|
||||
//
|
||||
|
||||
/// <summary>
|
||||
/// Write Base128 encoded variable sized unsigned integer of up to 32 bits
|
||||
/// </summary>
|
||||
/// <returns>number of bytes written</returns>
|
||||
[CLSCompliant(false)]
|
||||
public int WriteVariableUInt32(uint value)
|
||||
{
|
||||
int retval = 1;
|
||||
uint num1 = (uint)value;
|
||||
while (num1 >= 0x80)
|
||||
{
|
||||
this.Write((byte)(num1 | 0x80));
|
||||
num1 = num1 >> 7;
|
||||
retval++;
|
||||
}
|
||||
this.Write((byte)num1);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write Base128 encoded variable sized signed integer of up to 32 bits
|
||||
/// </summary>
|
||||
/// <returns>number of bytes written</returns>
|
||||
public int WriteVariableInt32(int value)
|
||||
{
|
||||
uint zigzag = (uint)(value << 1) ^ (uint)(value >> 31);
|
||||
return WriteVariableUInt32(zigzag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write Base128 encoded variable sized signed integer of up to 64 bits
|
||||
/// </summary>
|
||||
/// <returns>number of bytes written</returns>
|
||||
public int WriteVariableInt64(Int64 value)
|
||||
{
|
||||
ulong zigzag = (ulong)(value << 1) ^ (ulong)(value >> 63);
|
||||
return WriteVariableUInt64(zigzag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write Base128 encoded variable sized unsigned integer of up to 64 bits
|
||||
/// </summary>
|
||||
/// <returns>number of bytes written</returns>
|
||||
[CLSCompliant(false)]
|
||||
public int WriteVariableUInt64(UInt64 value)
|
||||
{
|
||||
int retval = 1;
|
||||
UInt64 num1 = (UInt64)value;
|
||||
while (num1 >= 0x80)
|
||||
{
|
||||
this.Write((byte)(num1 | 0x80));
|
||||
num1 = num1 >> 7;
|
||||
retval++;
|
||||
}
|
||||
this.Write((byte)num1);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compress (lossy) a float in the range -1..1 using numberOfBits bits
|
||||
/// </summary>
|
||||
public void WriteSignedSingle(float value, int numberOfBits)
|
||||
{
|
||||
NetException.Assert(((value >= -1.0) && (value <= 1.0)), " WriteSignedSingle() must be passed a float in the range -1 to 1; val is " + value);
|
||||
|
||||
float unit = (value + 1.0f) * 0.5f;
|
||||
int maxVal = (1 << numberOfBits) - 1;
|
||||
uint writeVal = (uint)(unit * (float)maxVal);
|
||||
|
||||
Write(writeVal, numberOfBits);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compress (lossy) a float in the range 0..1 using numberOfBits bits
|
||||
/// </summary>
|
||||
public void WriteUnitSingle(float value, int numberOfBits)
|
||||
{
|
||||
NetException.Assert(((value >= 0.0) && (value <= 1.0)), " WriteUnitSingle() must be passed a float in the range 0 to 1; val is " + value);
|
||||
|
||||
int maxValue = (1 << numberOfBits) - 1;
|
||||
uint writeVal = (uint)(value * (float)maxValue);
|
||||
|
||||
Write(writeVal, numberOfBits);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compress a float within a specified range using a certain number of bits
|
||||
/// </summary>
|
||||
public void WriteRangedSingle(float value, float min, float max, int numberOfBits)
|
||||
{
|
||||
NetException.Assert(((value >= min) && (value <= max)), " WriteRangedSingle() must be passed a float in the range MIN to MAX; val is " + value);
|
||||
|
||||
float range = max - min;
|
||||
float unit = ((value - min) / range);
|
||||
int maxVal = (1 << numberOfBits) - 1;
|
||||
Write((UInt32)((float)maxVal * unit), numberOfBits);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an integer with the least amount of bits need for the specified range
|
||||
/// Returns number of bits written
|
||||
/// </summary>
|
||||
public int WriteRangedInteger(int min, int max, int value)
|
||||
{
|
||||
NetException.Assert(value >= min && value <= max, "Value not within min/max range!");
|
||||
|
||||
uint range = (uint)(max - min);
|
||||
int numBits = NetUtility.BitsToHoldUInt(range);
|
||||
|
||||
uint rvalue = (uint)(value - min);
|
||||
Write(rvalue, numBits);
|
||||
|
||||
return numBits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an integer with the least amount of bits need for the specified range
|
||||
/// Returns number of bits written
|
||||
/// </summary>
|
||||
public int WriteRangedInteger(long min, long max, long value)
|
||||
{
|
||||
NetException.Assert(value >= min && value <= max, "Value not within min/max range!");
|
||||
|
||||
ulong range = (ulong)(max - min);
|
||||
int numBits = NetUtility.BitsToHoldUInt64(range);
|
||||
|
||||
ulong rvalue = (ulong)(value - min);
|
||||
Write(rvalue, numBits);
|
||||
|
||||
return numBits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write a string
|
||||
/// </summary>
|
||||
public void Write(string source)
|
||||
{
|
||||
if (string.IsNullOrEmpty(source))
|
||||
{
|
||||
WriteVariableUInt32(0);
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(source);
|
||||
EnsureBufferSize(m_bitLength + 8 + (bytes.Length * 8));
|
||||
WriteVariableUInt32((uint)bytes.Length);
|
||||
Write(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an endpoint description
|
||||
/// </summary>
|
||||
public void Write(IPEndPoint endPoint)
|
||||
{
|
||||
byte[] bytes = endPoint.Address.GetAddressBytes();
|
||||
Write((byte)bytes.Length);
|
||||
Write(bytes);
|
||||
Write((ushort)endPoint.Port);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the current local time to a message; readable (and convertable to local time) by the remote host using ReadTime()
|
||||
/// </summary>
|
||||
public void WriteTime(bool highPrecision)
|
||||
{
|
||||
double localTime = NetTime.Now;
|
||||
if (highPrecision)
|
||||
Write(localTime);
|
||||
else
|
||||
Write((float)localTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a local timestamp to a message; readable (and convertable to local time) by the remote host using ReadTime()
|
||||
/// </summary>
|
||||
public void WriteTime(double localTime, bool highPrecision)
|
||||
{
|
||||
if (highPrecision)
|
||||
Write(localTime);
|
||||
else
|
||||
Write((float)localTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes.
|
||||
/// </summary>
|
||||
public void WritePadBits()
|
||||
{
|
||||
m_bitLength = ((m_bitLength + 7) >> 3) * 8;
|
||||
EnsureBufferSize(m_bitLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pads data with the specified number of bits.
|
||||
/// </summary>
|
||||
public void WritePadBits(int numberOfBits)
|
||||
{
|
||||
m_bitLength += numberOfBits;
|
||||
EnsureBufferSize(m_bitLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Append all the bits of message to this message
|
||||
/// </summary>
|
||||
public void Write(NetBuffer buffer)
|
||||
{
|
||||
EnsureBufferSize(m_bitLength + (buffer.LengthBytes * 8));
|
||||
|
||||
Write(buffer.m_data, 0, buffer.LengthBytes);
|
||||
|
||||
// did we write excessive bits?
|
||||
int bitsInLastByte = (buffer.m_bitLength % 8);
|
||||
if (bitsInLastByte != 0)
|
||||
{
|
||||
int excessBits = 8 - bitsInLastByte;
|
||||
m_bitLength -= excessBits;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
98
SRMP/Lidgren.Network/NetBuffer.cs
Normal file
98
SRMP/Lidgren.Network/NetBuffer.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetBuffer
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of bytes to overallocate for each message to avoid resizing
|
||||
/// </summary>
|
||||
protected const int c_overAllocateAmount = 4;
|
||||
|
||||
private static readonly Dictionary<Type, MethodInfo> s_readMethods;
|
||||
private static readonly Dictionary<Type, MethodInfo> s_writeMethods;
|
||||
|
||||
internal byte[] m_data;
|
||||
internal int m_bitLength;
|
||||
internal int m_readPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the internal data buffer
|
||||
/// </summary>
|
||||
public byte[] Data
|
||||
{
|
||||
get { return m_data; }
|
||||
set { m_data = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the length of the used portion of the buffer in bytes
|
||||
/// </summary>
|
||||
public int LengthBytes
|
||||
{
|
||||
get { return ((m_bitLength + 7) >> 3); }
|
||||
set
|
||||
{
|
||||
m_bitLength = value * 8;
|
||||
InternalEnsureBufferSize(m_bitLength);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the length of the used portion of the buffer in bits
|
||||
/// </summary>
|
||||
public int LengthBits
|
||||
{
|
||||
get { return m_bitLength; }
|
||||
set
|
||||
{
|
||||
m_bitLength = value;
|
||||
InternalEnsureBufferSize(m_bitLength);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the read position in the buffer, in bits (not bytes)
|
||||
/// </summary>
|
||||
public long Position
|
||||
{
|
||||
get { return (long)m_readPosition; }
|
||||
set { m_readPosition = (int)value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position in the buffer in bytes; note that the bits of the first returned byte may already have been read - check the Position property to make sure.
|
||||
/// </summary>
|
||||
public int PositionInBytes
|
||||
{
|
||||
get { return (int)(m_readPosition / 8); }
|
||||
}
|
||||
|
||||
static NetBuffer()
|
||||
{
|
||||
s_readMethods = new Dictionary<Type, MethodInfo>();
|
||||
MethodInfo[] methods = typeof(NetIncomingMessage).GetMethods(BindingFlags.Instance | BindingFlags.Public);
|
||||
foreach (MethodInfo mi in methods)
|
||||
{
|
||||
if (mi.GetParameters().Length == 0 && mi.Name.StartsWith("Read", StringComparison.InvariantCulture) && mi.Name.Substring(4) == mi.ReturnType.Name)
|
||||
{
|
||||
s_readMethods[mi.ReturnType] = mi;
|
||||
}
|
||||
}
|
||||
|
||||
s_writeMethods = new Dictionary<Type, MethodInfo>();
|
||||
methods = typeof(NetOutgoingMessage).GetMethods(BindingFlags.Instance | BindingFlags.Public);
|
||||
foreach (MethodInfo mi in methods)
|
||||
{
|
||||
if (mi.Name.Equals("Write", StringComparison.InvariantCulture))
|
||||
{
|
||||
ParameterInfo[] pis = mi.GetParameters();
|
||||
if (pis.Length == 1)
|
||||
s_writeMethods[pis[0].ParameterType] = mi;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
176
SRMP/Lidgren.Network/NetClient.cs
Normal file
176
SRMP/Lidgren.Network/NetClient.cs
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
#if !__NOIPENDPOINT__
|
||||
using NetEndPoint = System.Net.IPEndPoint;
|
||||
#endif
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Specialized version of NetPeer used for a "client" connection. It does not accept any incoming connections and maintains a ServerConnection property
|
||||
/// </summary>
|
||||
public class NetClient : NetPeer
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the connection to the server, if any
|
||||
/// </summary>
|
||||
public NetConnection ServerConnection
|
||||
{
|
||||
get
|
||||
{
|
||||
NetConnection retval = null;
|
||||
if (m_connections.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
retval = m_connections[0];
|
||||
}
|
||||
catch
|
||||
{
|
||||
// preempted!
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the connection status of the server connection (or NetConnectionStatus.Disconnected if no connection)
|
||||
/// </summary>
|
||||
public NetConnectionStatus ConnectionStatus
|
||||
{
|
||||
get
|
||||
{
|
||||
var conn = ServerConnection;
|
||||
if (conn == null)
|
||||
return NetConnectionStatus.Disconnected;
|
||||
return conn.Status;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NetClient constructor
|
||||
/// </summary>
|
||||
/// <param name="config"></param>
|
||||
public NetClient(NetPeerConfiguration config)
|
||||
: base(config)
|
||||
{
|
||||
config.AcceptIncomingConnections = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect to a remote server
|
||||
/// </summary>
|
||||
/// <param name="remoteEndPoint">The remote endpoint to connect to</param>
|
||||
/// <param name="hailMessage">The hail message to pass</param>
|
||||
/// <returns>server connection, or null if already connected</returns>
|
||||
public override NetConnection Connect(NetEndPoint remoteEndPoint, NetOutgoingMessage hailMessage)
|
||||
{
|
||||
lock (m_connections)
|
||||
{
|
||||
if (m_connections.Count > 0)
|
||||
{
|
||||
LogWarning("Connect attempt failed; Already connected");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
lock (m_handshakes)
|
||||
{
|
||||
if (m_handshakes.Count > 0)
|
||||
{
|
||||
LogWarning("Connect attempt failed; Handshake already in progress");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return base.Connect(remoteEndPoint, hailMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect from server
|
||||
/// </summary>
|
||||
/// <param name="byeMessage">reason for disconnect</param>
|
||||
public void Disconnect(string byeMessage)
|
||||
{
|
||||
NetConnection serverConnection = ServerConnection;
|
||||
if (serverConnection == null)
|
||||
{
|
||||
lock (m_handshakes)
|
||||
{
|
||||
if (m_handshakes.Count > 0)
|
||||
{
|
||||
LogVerbose("Aborting connection attempt");
|
||||
foreach(var hs in m_handshakes)
|
||||
hs.Value.Disconnect(byeMessage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LogWarning("Disconnect requested when not connected!");
|
||||
return;
|
||||
}
|
||||
serverConnection.Disconnect(byeMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends message to server
|
||||
/// </summary>
|
||||
public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method)
|
||||
{
|
||||
NetConnection serverConnection = ServerConnection;
|
||||
if (serverConnection == null)
|
||||
{
|
||||
LogWarning("Cannot send message, no server connection!");
|
||||
return NetSendResult.FailedNotConnected;
|
||||
}
|
||||
|
||||
return serverConnection.SendMessage(msg, method, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends message to server
|
||||
/// </summary>
|
||||
public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel)
|
||||
{
|
||||
NetConnection serverConnection = ServerConnection;
|
||||
if (serverConnection == null)
|
||||
{
|
||||
LogWarning("Cannot send message, no server connection!");
|
||||
Recycle(msg);
|
||||
return NetSendResult.FailedNotConnected;
|
||||
}
|
||||
|
||||
return serverConnection.SendMessage(msg, method, sequenceChannel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents this object
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return "[NetClient " + ServerConnection + "]";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
499
SRMP/Lidgren.Network/NetConnection.Handshake.cs
Normal file
499
SRMP/Lidgren.Network/NetConnection.Handshake.cs
Normal file
|
|
@ -0,0 +1,499 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
#if !__NOIPENDPOINT__
|
||||
using NetEndPoint = System.Net.IPEndPoint;
|
||||
#endif
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetConnection
|
||||
{
|
||||
internal bool m_connectRequested;
|
||||
internal bool m_disconnectRequested;
|
||||
internal bool m_disconnectReqSendBye;
|
||||
internal string m_disconnectMessage;
|
||||
internal bool m_connectionInitiator;
|
||||
internal NetIncomingMessage m_remoteHailMessage;
|
||||
internal double m_lastHandshakeSendTime;
|
||||
internal int m_handshakeAttempts;
|
||||
|
||||
/// <summary>
|
||||
/// The message that the remote part specified via Connect() or Approve() - can be null.
|
||||
/// </summary>
|
||||
public NetIncomingMessage RemoteHailMessage { get { return m_remoteHailMessage; } }
|
||||
|
||||
// heartbeat called when connection still is in m_handshakes of NetPeer
|
||||
internal void UnconnectedHeartbeat(double now)
|
||||
{
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
if (m_disconnectRequested)
|
||||
ExecuteDisconnect(m_disconnectMessage, true);
|
||||
|
||||
if (m_connectRequested)
|
||||
{
|
||||
switch (m_status)
|
||||
{
|
||||
case NetConnectionStatus.Connected:
|
||||
case NetConnectionStatus.RespondedConnect:
|
||||
// reconnect
|
||||
ExecuteDisconnect("Reconnecting", true);
|
||||
break;
|
||||
|
||||
case NetConnectionStatus.InitiatedConnect:
|
||||
// send another connect attempt
|
||||
SendConnect(now);
|
||||
break;
|
||||
|
||||
case NetConnectionStatus.Disconnected:
|
||||
m_peer.ThrowOrLog("This connection is Disconnected; spent. A new one should have been created");
|
||||
break;
|
||||
|
||||
case NetConnectionStatus.Disconnecting:
|
||||
// let disconnect finish first
|
||||
break;
|
||||
|
||||
case NetConnectionStatus.None:
|
||||
default:
|
||||
SendConnect(now);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (now - m_lastHandshakeSendTime > m_peerConfiguration.m_resendHandshakeInterval)
|
||||
{
|
||||
if (m_handshakeAttempts >= m_peerConfiguration.m_maximumHandshakeAttempts)
|
||||
{
|
||||
// failed to connect
|
||||
ExecuteDisconnect("Failed to establish connection - no response from remote host", true);
|
||||
return;
|
||||
}
|
||||
|
||||
// resend handshake
|
||||
switch (m_status)
|
||||
{
|
||||
case NetConnectionStatus.InitiatedConnect:
|
||||
SendConnect(now);
|
||||
break;
|
||||
case NetConnectionStatus.RespondedConnect:
|
||||
SendConnectResponse(now, true);
|
||||
break;
|
||||
case NetConnectionStatus.RespondedAwaitingApproval:
|
||||
// awaiting approval
|
||||
m_lastHandshakeSendTime = now; // postpone handshake resend
|
||||
break;
|
||||
case NetConnectionStatus.None:
|
||||
case NetConnectionStatus.ReceivedInitiation:
|
||||
default:
|
||||
m_peer.LogWarning("Time to resend handshake, but status is " + m_status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void ExecuteDisconnect(string reason, bool sendByeMessage)
|
||||
{
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
// clear send queues
|
||||
for (int i = 0; i < m_sendChannels.Length; i++)
|
||||
{
|
||||
NetSenderChannelBase channel = m_sendChannels[i];
|
||||
if (channel != null)
|
||||
channel.Reset();
|
||||
}
|
||||
|
||||
if (sendByeMessage)
|
||||
SendDisconnect(reason, true);
|
||||
|
||||
if (m_status == NetConnectionStatus.ReceivedInitiation)
|
||||
{
|
||||
// nothing much has happened yet; no need to send disconnected status message
|
||||
m_status = NetConnectionStatus.Disconnected;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetStatus(NetConnectionStatus.Disconnected, reason);
|
||||
}
|
||||
|
||||
// in case we're still in handshake
|
||||
lock (m_peer.m_handshakes)
|
||||
m_peer.m_handshakes.Remove(m_remoteEndPoint);
|
||||
|
||||
m_disconnectRequested = false;
|
||||
m_connectRequested = false;
|
||||
m_handshakeAttempts = 0;
|
||||
}
|
||||
|
||||
internal void SendConnect(double now)
|
||||
{
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
int preAllocate = 13 + m_peerConfiguration.AppIdentifier.Length;
|
||||
preAllocate += (m_localHailMessage == null ? 0 : m_localHailMessage.LengthBytes);
|
||||
|
||||
NetOutgoingMessage om = m_peer.CreateMessage(preAllocate);
|
||||
om.m_messageType = NetMessageType.Connect;
|
||||
om.Write(m_peerConfiguration.AppIdentifier);
|
||||
om.Write(m_peer.m_uniqueIdentifier);
|
||||
om.Write((float)now);
|
||||
|
||||
WriteLocalHail(om);
|
||||
|
||||
m_peer.SendLibrary(om, m_remoteEndPoint);
|
||||
|
||||
m_connectRequested = false;
|
||||
m_lastHandshakeSendTime = now;
|
||||
m_handshakeAttempts++;
|
||||
|
||||
if (m_handshakeAttempts > 1)
|
||||
m_peer.LogDebug("Resending Connect...");
|
||||
SetStatus(NetConnectionStatus.InitiatedConnect, "Locally requested connect");
|
||||
}
|
||||
|
||||
internal void SendConnectResponse(double now, bool onLibraryThread)
|
||||
{
|
||||
if (onLibraryThread)
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
NetOutgoingMessage om = m_peer.CreateMessage(m_peerConfiguration.AppIdentifier.Length + 13 + (m_localHailMessage == null ? 0 : m_localHailMessage.LengthBytes));
|
||||
om.m_messageType = NetMessageType.ConnectResponse;
|
||||
om.Write(m_peerConfiguration.AppIdentifier);
|
||||
om.Write(m_peer.m_uniqueIdentifier);
|
||||
om.Write((float)now);
|
||||
Interlocked.Increment(ref om.m_recyclingCount);
|
||||
WriteLocalHail(om);
|
||||
|
||||
if (onLibraryThread)
|
||||
m_peer.SendLibrary(om, m_remoteEndPoint);
|
||||
else
|
||||
m_peer.m_unsentUnconnectedMessages.Enqueue(new NetTuple<NetEndPoint, NetOutgoingMessage>(m_remoteEndPoint, om));
|
||||
|
||||
m_lastHandshakeSendTime = now;
|
||||
m_handshakeAttempts++;
|
||||
|
||||
if (m_handshakeAttempts > 1)
|
||||
m_peer.LogDebug("Resending ConnectResponse...");
|
||||
|
||||
SetStatus(NetConnectionStatus.RespondedConnect, "Remotely requested connect");
|
||||
}
|
||||
|
||||
internal void SendDisconnect(string reason, bool onLibraryThread)
|
||||
{
|
||||
if (onLibraryThread)
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
NetOutgoingMessage om = m_peer.CreateMessage(reason);
|
||||
om.m_messageType = NetMessageType.Disconnect;
|
||||
Interlocked.Increment(ref om.m_recyclingCount);
|
||||
if (onLibraryThread)
|
||||
m_peer.SendLibrary(om, m_remoteEndPoint);
|
||||
else
|
||||
m_peer.m_unsentUnconnectedMessages.Enqueue(new NetTuple<NetEndPoint, NetOutgoingMessage>(m_remoteEndPoint, om));
|
||||
}
|
||||
|
||||
private void WriteLocalHail(NetOutgoingMessage om)
|
||||
{
|
||||
if (m_localHailMessage != null)
|
||||
{
|
||||
byte[] hi = m_localHailMessage.Data;
|
||||
if (hi != null && hi.Length >= m_localHailMessage.LengthBytes)
|
||||
{
|
||||
if (om.LengthBytes + m_localHailMessage.LengthBytes > m_peerConfiguration.m_maximumTransmissionUnit - 10)
|
||||
m_peer.ThrowOrLog("Hail message too large; can maximally be " + (m_peerConfiguration.m_maximumTransmissionUnit - 10 - om.LengthBytes));
|
||||
om.Write(m_localHailMessage.Data, 0, m_localHailMessage.LengthBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void SendConnectionEstablished()
|
||||
{
|
||||
NetOutgoingMessage om = m_peer.CreateMessage(4);
|
||||
om.m_messageType = NetMessageType.ConnectionEstablished;
|
||||
om.Write((float)NetTime.Now);
|
||||
m_peer.SendLibrary(om, m_remoteEndPoint);
|
||||
|
||||
m_handshakeAttempts = 0;
|
||||
|
||||
InitializePing();
|
||||
if (m_status != NetConnectionStatus.Connected)
|
||||
SetStatus(NetConnectionStatus.Connected, "Connected to " + NetUtility.ToHexString(m_remoteUniqueIdentifier));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Approves this connection; sending a connection response to the remote host
|
||||
/// </summary>
|
||||
public void Approve()
|
||||
{
|
||||
if (m_status != NetConnectionStatus.RespondedAwaitingApproval)
|
||||
{
|
||||
m_peer.LogWarning("Approve() called in wrong status; expected RespondedAwaitingApproval; got " + m_status);
|
||||
return;
|
||||
}
|
||||
|
||||
m_localHailMessage = null;
|
||||
m_handshakeAttempts = 0;
|
||||
SendConnectResponse(NetTime.Now, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Approves this connection; sending a connection response to the remote host
|
||||
/// </summary>
|
||||
/// <param name="localHail">The local hail message that will be set as RemoteHailMessage on the remote host</param>
|
||||
public void Approve(NetOutgoingMessage localHail)
|
||||
{
|
||||
if (m_status != NetConnectionStatus.RespondedAwaitingApproval)
|
||||
{
|
||||
m_peer.LogWarning("Approve() called in wrong status; expected RespondedAwaitingApproval; got " + m_status);
|
||||
return;
|
||||
}
|
||||
|
||||
m_localHailMessage = localHail;
|
||||
m_handshakeAttempts = 0;
|
||||
SendConnectResponse(NetTime.Now, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Denies this connection; disconnecting it
|
||||
/// </summary>
|
||||
public void Deny()
|
||||
{
|
||||
Deny(string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Denies this connection; disconnecting it
|
||||
/// </summary>
|
||||
/// <param name="reason">The stated reason for the disconnect, readable as a string in the StatusChanged message on the remote host</param>
|
||||
public void Deny(string reason)
|
||||
{
|
||||
// send disconnect; remove from handshakes
|
||||
SendDisconnect(reason, false);
|
||||
|
||||
// remove from handshakes
|
||||
lock (m_peer.m_handshakes)
|
||||
m_peer.m_handshakes.Remove(m_remoteEndPoint);
|
||||
}
|
||||
|
||||
internal void ReceivedHandshake(double now, NetMessageType tp, int ptr, int payloadLength)
|
||||
{
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
byte[] hail;
|
||||
switch (tp)
|
||||
{
|
||||
case NetMessageType.Connect:
|
||||
if (m_status == NetConnectionStatus.ReceivedInitiation)
|
||||
{
|
||||
// Whee! Server full has already been checked
|
||||
bool ok = ValidateHandshakeData(ptr, payloadLength, out hail);
|
||||
if (ok)
|
||||
{
|
||||
if (hail != null)
|
||||
{
|
||||
m_remoteHailMessage = m_peer.CreateIncomingMessage(NetIncomingMessageType.Data, hail);
|
||||
m_remoteHailMessage.LengthBits = (hail.Length * 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_remoteHailMessage = null;
|
||||
}
|
||||
|
||||
if (m_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.ConnectionApproval))
|
||||
{
|
||||
// ok, let's not add connection just yet
|
||||
NetIncomingMessage appMsg = m_peer.CreateIncomingMessage(NetIncomingMessageType.ConnectionApproval, (m_remoteHailMessage == null ? 0 : m_remoteHailMessage.LengthBytes));
|
||||
appMsg.m_receiveTime = now;
|
||||
appMsg.m_senderConnection = this;
|
||||
appMsg.m_senderEndPoint = this.m_remoteEndPoint;
|
||||
if (m_remoteHailMessage != null)
|
||||
appMsg.Write(m_remoteHailMessage.m_data, 0, m_remoteHailMessage.LengthBytes);
|
||||
SetStatus(NetConnectionStatus.RespondedAwaitingApproval, "Awaiting approval");
|
||||
m_peer.ReleaseMessage(appMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
SendConnectResponse((float)now, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (m_status == NetConnectionStatus.RespondedAwaitingApproval)
|
||||
{
|
||||
m_peer.LogWarning("Ignoring multiple Connect() most likely due to a delayed Approval");
|
||||
return;
|
||||
}
|
||||
if (m_status == NetConnectionStatus.RespondedConnect)
|
||||
{
|
||||
// our ConnectResponse must have been lost
|
||||
SendConnectResponse((float)now, true);
|
||||
return;
|
||||
}
|
||||
m_peer.LogDebug("Unhandled Connect: " + tp + ", status is " + m_status + " length: " + payloadLength);
|
||||
break;
|
||||
case NetMessageType.ConnectResponse:
|
||||
HandleConnectResponse(now, tp, ptr, payloadLength);
|
||||
break;
|
||||
|
||||
case NetMessageType.ConnectionEstablished:
|
||||
switch (m_status)
|
||||
{
|
||||
case NetConnectionStatus.Connected:
|
||||
// ok...
|
||||
break;
|
||||
case NetConnectionStatus.Disconnected:
|
||||
case NetConnectionStatus.Disconnecting:
|
||||
case NetConnectionStatus.None:
|
||||
// too bad, almost made it
|
||||
break;
|
||||
case NetConnectionStatus.ReceivedInitiation:
|
||||
// uh, a little premature... ignore
|
||||
break;
|
||||
case NetConnectionStatus.InitiatedConnect:
|
||||
// weird, should have been RespondedConnect...
|
||||
break;
|
||||
case NetConnectionStatus.RespondedConnect:
|
||||
// awesome
|
||||
|
||||
NetIncomingMessage msg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
|
||||
InitializeRemoteTimeOffset(msg.ReadSingle());
|
||||
|
||||
m_peer.AcceptConnection(this);
|
||||
InitializePing();
|
||||
SetStatus(NetConnectionStatus.Connected, "Connected to " + NetUtility.ToHexString(m_remoteUniqueIdentifier));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case NetMessageType.Disconnect:
|
||||
// ouch
|
||||
string reason = "Ouch";
|
||||
try
|
||||
{
|
||||
NetIncomingMessage inc = m_peer.SetupReadHelperMessage(ptr, payloadLength);
|
||||
reason = inc.ReadString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
ExecuteDisconnect(reason, false);
|
||||
break;
|
||||
|
||||
case NetMessageType.Discovery:
|
||||
m_peer.HandleIncomingDiscoveryRequest(now, m_remoteEndPoint, ptr, payloadLength);
|
||||
return;
|
||||
|
||||
case NetMessageType.DiscoveryResponse:
|
||||
m_peer.HandleIncomingDiscoveryResponse(now, m_remoteEndPoint, ptr, payloadLength);
|
||||
return;
|
||||
|
||||
case NetMessageType.Ping:
|
||||
// silently ignore
|
||||
return;
|
||||
|
||||
default:
|
||||
m_peer.LogDebug("Unhandled type during handshake: " + tp + " length: " + payloadLength);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleConnectResponse(double now, NetMessageType tp, int ptr, int payloadLength)
|
||||
{
|
||||
byte[] hail;
|
||||
switch (m_status)
|
||||
{
|
||||
case NetConnectionStatus.InitiatedConnect:
|
||||
// awesome
|
||||
bool ok = ValidateHandshakeData(ptr, payloadLength, out hail);
|
||||
if (ok)
|
||||
{
|
||||
if (hail != null)
|
||||
{
|
||||
m_remoteHailMessage = m_peer.CreateIncomingMessage(NetIncomingMessageType.Data, hail);
|
||||
m_remoteHailMessage.LengthBits = (hail.Length * 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_remoteHailMessage = null;
|
||||
}
|
||||
|
||||
m_peer.AcceptConnection(this);
|
||||
SendConnectionEstablished();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case NetConnectionStatus.RespondedConnect:
|
||||
// hello, wtf?
|
||||
break;
|
||||
case NetConnectionStatus.Disconnecting:
|
||||
case NetConnectionStatus.Disconnected:
|
||||
case NetConnectionStatus.ReceivedInitiation:
|
||||
case NetConnectionStatus.None:
|
||||
// wtf? anyway, bye!
|
||||
break;
|
||||
case NetConnectionStatus.Connected:
|
||||
// my ConnectionEstablished must have been lost, send another one
|
||||
SendConnectionEstablished();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateHandshakeData(int ptr, int payloadLength, out byte[] hail)
|
||||
{
|
||||
hail = null;
|
||||
|
||||
// create temporary incoming message
|
||||
NetIncomingMessage msg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
|
||||
try
|
||||
{
|
||||
string remoteAppIdentifier = msg.ReadString();
|
||||
long remoteUniqueIdentifier = msg.ReadInt64();
|
||||
InitializeRemoteTimeOffset(msg.ReadSingle());
|
||||
|
||||
int remainingBytes = payloadLength - (msg.PositionInBytes - ptr);
|
||||
if (remainingBytes > 0)
|
||||
hail = msg.ReadBytes(remainingBytes);
|
||||
|
||||
if (remoteAppIdentifier != m_peer.m_configuration.AppIdentifier)
|
||||
{
|
||||
ExecuteDisconnect("Wrong application identifier!", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_remoteUniqueIdentifier = remoteUniqueIdentifier;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
// whatever; we failed
|
||||
ExecuteDisconnect("Handshake data validation failed", true);
|
||||
m_peer.LogWarning("ReadRemoteHandshakeData failed: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect from the remote peer
|
||||
/// </summary>
|
||||
/// <param name="byeMessage">the message to send with the disconnect message</param>
|
||||
public void Disconnect(string byeMessage)
|
||||
{
|
||||
// user or library thread
|
||||
if (m_status == NetConnectionStatus.None || m_status == NetConnectionStatus.Disconnected)
|
||||
return;
|
||||
|
||||
m_peer.LogVerbose("Disconnect requested for " + this);
|
||||
m_disconnectMessage = byeMessage;
|
||||
|
||||
if (m_status != NetConnectionStatus.Disconnected && m_status != NetConnectionStatus.None)
|
||||
SetStatus(NetConnectionStatus.Disconnecting, byeMessage);
|
||||
|
||||
m_handshakeAttempts = 0;
|
||||
m_disconnectRequested = true;
|
||||
m_disconnectReqSendBye = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
141
SRMP/Lidgren.Network/NetConnection.Latency.cs
Normal file
141
SRMP/Lidgren.Network/NetConnection.Latency.cs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetConnection
|
||||
{
|
||||
private double m_sentPingTime;
|
||||
private int m_sentPingNumber;
|
||||
private double m_averageRoundtripTime;
|
||||
private double m_timeoutDeadline = double.MaxValue;
|
||||
|
||||
// local time value + m_remoteTimeOffset = remote time value
|
||||
internal double m_remoteTimeOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current average roundtrip time in seconds
|
||||
/// </summary>
|
||||
public float AverageRoundtripTime { get { return (float)m_averageRoundtripTime; } }
|
||||
|
||||
/// <summary>
|
||||
/// Time offset between this peer and the remote peer
|
||||
/// </summary>
|
||||
public float RemoteTimeOffset { get { return (float)m_remoteTimeOffset; } }
|
||||
|
||||
// this might happen more than once
|
||||
internal void InitializeRemoteTimeOffset(float remoteSendTime)
|
||||
{
|
||||
m_remoteTimeOffset = (remoteSendTime + (m_averageRoundtripTime / 2.0)) - NetTime.Now;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets local time value comparable to NetTime.Now from a remote value
|
||||
/// </summary>
|
||||
public double GetLocalTime(double remoteTimestamp)
|
||||
{
|
||||
return remoteTimestamp - m_remoteTimeOffset;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remote time value for a local time value produced by NetTime.Now
|
||||
/// </summary>
|
||||
public double GetRemoteTime(double localTimestamp)
|
||||
{
|
||||
return localTimestamp + m_remoteTimeOffset;
|
||||
}
|
||||
|
||||
internal void InitializePing()
|
||||
{
|
||||
m_timeoutDeadline = NetTime.Now + (m_peerConfiguration.m_connectionTimeout * 2.0); // initially allow a little more time
|
||||
SendPing();
|
||||
}
|
||||
|
||||
internal void SendPing()
|
||||
{
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
m_sentPingNumber++;
|
||||
|
||||
m_sentPingTime = NetTime.Now;
|
||||
NetOutgoingMessage om = m_peer.CreateMessage(1);
|
||||
om.Write((byte)m_sentPingNumber); // truncating to 0-255
|
||||
om.m_messageType = NetMessageType.Ping;
|
||||
|
||||
int len = om.Encode(m_peer.m_sendBuffer, 0, 0);
|
||||
bool connectionReset;
|
||||
m_peer.SendPacket(len, m_remoteEndPoint, 1, out connectionReset);
|
||||
|
||||
m_statistics.PacketSent(len, 1);
|
||||
m_peer.Recycle(om);
|
||||
}
|
||||
|
||||
internal void SendPong(int pingNumber)
|
||||
{
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
NetOutgoingMessage om = m_peer.CreateMessage(5);
|
||||
om.Write((byte)pingNumber);
|
||||
om.Write((float)NetTime.Now); // we should update this value to reflect the exact point in time the packet is SENT
|
||||
om.m_messageType = NetMessageType.Pong;
|
||||
|
||||
int len = om.Encode(m_peer.m_sendBuffer, 0, 0);
|
||||
bool connectionReset;
|
||||
|
||||
m_peer.SendPacket(len, m_remoteEndPoint, 1, out connectionReset);
|
||||
|
||||
m_statistics.PacketSent(len, 1);
|
||||
m_peer.Recycle(om);
|
||||
}
|
||||
|
||||
internal void ReceivedPong(double now, int pongNumber, float remoteSendTime)
|
||||
{
|
||||
if ((byte)pongNumber != (byte)m_sentPingNumber)
|
||||
{
|
||||
m_peer.LogVerbose("Ping/Pong mismatch; dropped message?");
|
||||
return;
|
||||
}
|
||||
|
||||
m_timeoutDeadline = now + m_peerConfiguration.m_connectionTimeout;
|
||||
|
||||
double rtt = now - m_sentPingTime;
|
||||
NetException.Assert(rtt >= 0);
|
||||
|
||||
double diff = (remoteSendTime + (rtt / 2.0)) - now;
|
||||
|
||||
if (m_averageRoundtripTime < 0)
|
||||
{
|
||||
m_remoteTimeOffset = diff;
|
||||
m_averageRoundtripTime = rtt;
|
||||
m_peer.LogDebug("Initiated average roundtrip time to " + NetTime.ToReadable(m_averageRoundtripTime) + " Remote time is: " + (now + diff));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_averageRoundtripTime = (m_averageRoundtripTime * 0.7) + (rtt * 0.3);
|
||||
|
||||
m_remoteTimeOffset = ((m_remoteTimeOffset * (double)(m_sentPingNumber - 1)) + diff) / (double)m_sentPingNumber;
|
||||
m_peer.LogVerbose("Updated average roundtrip time to " + NetTime.ToReadable(m_averageRoundtripTime) + ", remote time to " + (now + m_remoteTimeOffset) + " (ie. diff " + m_remoteTimeOffset + ")");
|
||||
}
|
||||
|
||||
// update resend delay for all channels
|
||||
double resendDelay = GetResendDelay();
|
||||
foreach (var chan in m_sendChannels)
|
||||
{
|
||||
var rchan = chan as NetReliableSenderChannel;
|
||||
if (rchan != null)
|
||||
rchan.m_resendDelay = resendDelay;
|
||||
}
|
||||
|
||||
// m_peer.LogVerbose("Timeout deadline pushed to " + m_timeoutDeadline);
|
||||
|
||||
// notify the application that average rtt changed
|
||||
if (m_peer.m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.ConnectionLatencyUpdated))
|
||||
{
|
||||
NetIncomingMessage update = m_peer.CreateIncomingMessage(NetIncomingMessageType.ConnectionLatencyUpdated, 4);
|
||||
update.m_senderConnection = this;
|
||||
update.m_senderEndPoint = this.m_remoteEndPoint;
|
||||
update.Write((float)rtt);
|
||||
m_peer.ReleaseMessage(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
182
SRMP/Lidgren.Network/NetConnection.MTU.cs
Normal file
182
SRMP/Lidgren.Network/NetConnection.MTU.cs
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetConnection
|
||||
{
|
||||
private enum ExpandMTUStatus
|
||||
{
|
||||
None,
|
||||
InProgress,
|
||||
Finished
|
||||
}
|
||||
|
||||
private const int c_protocolMaxMTU = (int)((((float)ushort.MaxValue / 8.0f) - 1.0f));
|
||||
|
||||
private ExpandMTUStatus m_expandMTUStatus;
|
||||
|
||||
private int m_largestSuccessfulMTU;
|
||||
private int m_smallestFailedMTU;
|
||||
|
||||
private int m_lastSentMTUAttemptSize;
|
||||
private double m_lastSentMTUAttemptTime;
|
||||
private int m_mtuAttemptFails;
|
||||
|
||||
internal int m_currentMTU;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current MTU in bytes. If PeerConfiguration.AutoExpandMTU is false, this will be PeerConfiguration.MaximumTransmissionUnit.
|
||||
/// </summary>
|
||||
public int CurrentMTU { get { return m_currentMTU; } }
|
||||
|
||||
internal void InitExpandMTU(double now)
|
||||
{
|
||||
m_lastSentMTUAttemptTime = now + m_peerConfiguration.m_expandMTUFrequency + 1.5f + m_averageRoundtripTime; // wait a tiny bit before starting to expand mtu
|
||||
m_largestSuccessfulMTU = 512;
|
||||
m_smallestFailedMTU = -1;
|
||||
m_currentMTU = m_peerConfiguration.MaximumTransmissionUnit;
|
||||
}
|
||||
|
||||
private void MTUExpansionHeartbeat(double now)
|
||||
{
|
||||
if (m_expandMTUStatus == ExpandMTUStatus.Finished)
|
||||
return;
|
||||
|
||||
if (m_expandMTUStatus == ExpandMTUStatus.None)
|
||||
{
|
||||
if (m_peerConfiguration.m_autoExpandMTU == false)
|
||||
{
|
||||
FinalizeMTU(m_currentMTU);
|
||||
return;
|
||||
}
|
||||
|
||||
// begin expansion
|
||||
ExpandMTU(now);
|
||||
return;
|
||||
}
|
||||
|
||||
if (now > m_lastSentMTUAttemptTime + m_peerConfiguration.ExpandMTUFrequency)
|
||||
{
|
||||
m_mtuAttemptFails++;
|
||||
if (m_mtuAttemptFails == 3)
|
||||
{
|
||||
FinalizeMTU(m_currentMTU);
|
||||
return;
|
||||
}
|
||||
|
||||
// timed out; ie. failed
|
||||
m_smallestFailedMTU = m_lastSentMTUAttemptSize;
|
||||
ExpandMTU(now);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExpandMTU(double now)
|
||||
{
|
||||
int tryMTU;
|
||||
|
||||
// we've nevered encountered failure
|
||||
if (m_smallestFailedMTU == -1)
|
||||
{
|
||||
// we've never encountered failure; expand by 25% each time
|
||||
tryMTU = (int)((float)m_currentMTU * 1.25f);
|
||||
//m_peer.LogDebug("Trying MTU " + tryMTU);
|
||||
}
|
||||
else
|
||||
{
|
||||
// we HAVE encountered failure; so try in between
|
||||
tryMTU = (int)(((float)m_smallestFailedMTU + (float)m_largestSuccessfulMTU) / 2.0f);
|
||||
//m_peer.LogDebug("Trying MTU " + m_smallestFailedMTU + " <-> " + m_largestSuccessfulMTU + " = " + tryMTU);
|
||||
}
|
||||
|
||||
if (tryMTU > c_protocolMaxMTU)
|
||||
tryMTU = c_protocolMaxMTU;
|
||||
|
||||
if (tryMTU == m_largestSuccessfulMTU)
|
||||
{
|
||||
//m_peer.LogDebug("Found optimal MTU - exiting");
|
||||
FinalizeMTU(m_largestSuccessfulMTU);
|
||||
return;
|
||||
}
|
||||
|
||||
SendExpandMTU(now, tryMTU);
|
||||
}
|
||||
|
||||
private void SendExpandMTU(double now, int size)
|
||||
{
|
||||
NetOutgoingMessage om = m_peer.CreateMessage(size);
|
||||
byte[] tmp = new byte[size];
|
||||
om.Write(tmp);
|
||||
om.m_messageType = NetMessageType.ExpandMTURequest;
|
||||
int len = om.Encode(m_peer.m_sendBuffer, 0, 0);
|
||||
|
||||
bool ok = m_peer.SendMTUPacket(len, m_remoteEndPoint);
|
||||
if (ok == false)
|
||||
{
|
||||
//m_peer.LogDebug("Send MTU failed for size " + size);
|
||||
|
||||
// failure
|
||||
if (m_smallestFailedMTU == -1 || size < m_smallestFailedMTU)
|
||||
{
|
||||
m_smallestFailedMTU = size;
|
||||
m_mtuAttemptFails++;
|
||||
if (m_mtuAttemptFails >= m_peerConfiguration.ExpandMTUFailAttempts)
|
||||
{
|
||||
FinalizeMTU(m_largestSuccessfulMTU);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ExpandMTU(now);
|
||||
return;
|
||||
}
|
||||
|
||||
m_lastSentMTUAttemptSize = size;
|
||||
m_lastSentMTUAttemptTime = now;
|
||||
|
||||
m_statistics.PacketSent(len, 1);
|
||||
m_peer.Recycle(om);
|
||||
}
|
||||
|
||||
private void FinalizeMTU(int size)
|
||||
{
|
||||
if (m_expandMTUStatus == ExpandMTUStatus.Finished)
|
||||
return;
|
||||
m_expandMTUStatus = ExpandMTUStatus.Finished;
|
||||
m_currentMTU = size;
|
||||
if (m_currentMTU != m_peerConfiguration.m_maximumTransmissionUnit)
|
||||
m_peer.LogDebug("Expanded Maximum Transmission Unit to: " + m_currentMTU + " bytes");
|
||||
return;
|
||||
}
|
||||
|
||||
private void SendMTUSuccess(int size)
|
||||
{
|
||||
NetOutgoingMessage om = m_peer.CreateMessage(4);
|
||||
om.Write(size);
|
||||
om.m_messageType = NetMessageType.ExpandMTUSuccess;
|
||||
int len = om.Encode(m_peer.m_sendBuffer, 0, 0);
|
||||
bool connectionReset;
|
||||
m_peer.SendPacket(len, m_remoteEndPoint, 1, out connectionReset);
|
||||
m_peer.Recycle(om);
|
||||
|
||||
//m_peer.LogDebug("Received MTU expand request for " + size + " bytes");
|
||||
|
||||
m_statistics.PacketSent(len, 1);
|
||||
}
|
||||
|
||||
private void HandleExpandMTUSuccess(double now, int size)
|
||||
{
|
||||
if (size > m_largestSuccessfulMTU)
|
||||
m_largestSuccessfulMTU = size;
|
||||
|
||||
if (size < m_currentMTU)
|
||||
{
|
||||
//m_peer.LogDebug("Received low MTU expand success (size " + size + "); current mtu is " + m_currentMTU);
|
||||
return;
|
||||
}
|
||||
|
||||
//m_peer.LogDebug("Expanding MTU to " + size);
|
||||
m_currentMTU = size;
|
||||
|
||||
ExpandMTU(now);
|
||||
}
|
||||
}
|
||||
}
|
||||
584
SRMP/Lidgren.Network/NetConnection.cs
Normal file
584
SRMP/Lidgren.Network/NetConnection.cs
Normal file
|
|
@ -0,0 +1,584 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
|
||||
#if !__NOIPENDPOINT__
|
||||
using NetEndPoint = System.Net.IPEndPoint;
|
||||
#endif
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a connection to a remote peer
|
||||
/// </summary>
|
||||
[DebuggerDisplay("RemoteUniqueIdentifier={RemoteUniqueIdentifier} RemoteEndPoint={m_remoteEndPoint}")]
|
||||
public partial class NetConnection
|
||||
{
|
||||
private const int m_infrequentEventsSkipFrames = 8; // number of heartbeats to skip checking for infrequent events (ping, timeout etc)
|
||||
private const int m_messageCoalesceFrames = 3; // number of heartbeats to wait for more incoming messages before sending packet
|
||||
|
||||
internal NetPeer m_peer;
|
||||
internal NetPeerConfiguration m_peerConfiguration;
|
||||
internal NetConnectionStatus m_status; // actual status
|
||||
internal NetConnectionStatus m_outputtedStatus; // status that has been sent as StatusChanged message
|
||||
internal NetConnectionStatus m_visibleStatus; // status visible by querying the Status property
|
||||
internal NetEndPoint m_remoteEndPoint;
|
||||
internal NetSenderChannelBase[] m_sendChannels;
|
||||
internal NetReceiverChannelBase[] m_receiveChannels;
|
||||
internal NetOutgoingMessage m_localHailMessage;
|
||||
internal long m_remoteUniqueIdentifier;
|
||||
internal NetQueue<NetTuple<NetMessageType, int>> m_queuedOutgoingAcks;
|
||||
internal NetQueue<NetTuple<NetMessageType, int>> m_queuedIncomingAcks;
|
||||
private int m_sendBufferWritePtr;
|
||||
private int m_sendBufferNumMessages;
|
||||
private object m_tag;
|
||||
internal NetConnectionStatistics m_statistics;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the application defined object containing data about the connection
|
||||
/// </summary>
|
||||
public object Tag
|
||||
{
|
||||
get { return m_tag; }
|
||||
set { m_tag = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the peer which holds this connection
|
||||
/// </summary>
|
||||
public NetPeer Peer { get { return m_peer; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current status of the connection (synced to the last status message read)
|
||||
/// </summary>
|
||||
public NetConnectionStatus Status { get { return m_visibleStatus; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets various statistics for this connection
|
||||
/// </summary>
|
||||
public NetConnectionStatistics Statistics { get { return m_statistics; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remote endpoint for the connection
|
||||
/// </summary>
|
||||
public NetEndPoint RemoteEndPoint { get { return m_remoteEndPoint; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of the remote NetPeer for this connection
|
||||
/// </summary>
|
||||
public long RemoteUniqueIdentifier { get { return m_remoteUniqueIdentifier; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local hail message that was sent as part of the handshake
|
||||
/// </summary>
|
||||
public NetOutgoingMessage LocalHailMessage { get { return m_localHailMessage; } }
|
||||
|
||||
// gets the time before automatically resending an unacked message
|
||||
internal double GetResendDelay()
|
||||
{
|
||||
double avgRtt = m_averageRoundtripTime;
|
||||
if (avgRtt <= 0)
|
||||
avgRtt = 0.1; // "default" resend is based on 100 ms roundtrip time
|
||||
return 0.025 + (avgRtt * 2.1); // 25 ms + double rtt
|
||||
}
|
||||
|
||||
internal NetConnection(NetPeer peer, NetEndPoint remoteEndPoint)
|
||||
{
|
||||
m_peer = peer;
|
||||
m_peerConfiguration = m_peer.Configuration;
|
||||
m_status = NetConnectionStatus.None;
|
||||
m_outputtedStatus = NetConnectionStatus.None;
|
||||
m_visibleStatus = NetConnectionStatus.None;
|
||||
m_remoteEndPoint = remoteEndPoint;
|
||||
m_sendChannels = new NetSenderChannelBase[NetConstants.NumTotalChannels];
|
||||
m_receiveChannels = new NetReceiverChannelBase[NetConstants.NumTotalChannels];
|
||||
m_queuedOutgoingAcks = new NetQueue<NetTuple<NetMessageType, int>>(4);
|
||||
m_queuedIncomingAcks = new NetQueue<NetTuple<NetMessageType, int>>(4);
|
||||
m_statistics = new NetConnectionStatistics(this);
|
||||
m_averageRoundtripTime = -1.0f;
|
||||
m_currentMTU = m_peerConfiguration.MaximumTransmissionUnit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change the internal endpoint to this new one. Used when, during handshake, a switch in port is detected (due to NAT)
|
||||
/// </summary>
|
||||
internal void MutateEndPoint(NetEndPoint endPoint)
|
||||
{
|
||||
m_remoteEndPoint = endPoint;
|
||||
}
|
||||
|
||||
internal void ResetTimeout(double now)
|
||||
{
|
||||
m_timeoutDeadline = now + m_peerConfiguration.m_connectionTimeout;
|
||||
}
|
||||
|
||||
internal void SetStatus(NetConnectionStatus status, string reason)
|
||||
{
|
||||
// user or library thread
|
||||
|
||||
m_status = status;
|
||||
if (reason == null)
|
||||
reason = string.Empty;
|
||||
|
||||
if (m_status == NetConnectionStatus.Connected)
|
||||
{
|
||||
m_timeoutDeadline = NetTime.Now + m_peerConfiguration.m_connectionTimeout;
|
||||
m_peer.LogVerbose("Timeout deadline initialized to " + m_timeoutDeadline);
|
||||
}
|
||||
|
||||
if (m_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.StatusChanged))
|
||||
{
|
||||
if (m_outputtedStatus != status)
|
||||
{
|
||||
NetIncomingMessage info = m_peer.CreateIncomingMessage(NetIncomingMessageType.StatusChanged, 4 + reason.Length + (reason.Length > 126 ? 2 : 1));
|
||||
info.m_senderConnection = this;
|
||||
info.m_senderEndPoint = m_remoteEndPoint;
|
||||
info.Write((byte)m_status);
|
||||
info.Write(reason);
|
||||
m_peer.ReleaseMessage(info);
|
||||
m_outputtedStatus = status;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// app dont want those messages, update visible status immediately
|
||||
m_outputtedStatus = m_status;
|
||||
m_visibleStatus = m_status;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Heartbeat(double now, uint frameCounter)
|
||||
{
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
NetException.Assert(m_status != NetConnectionStatus.InitiatedConnect && m_status != NetConnectionStatus.RespondedConnect);
|
||||
|
||||
if ((frameCounter % m_infrequentEventsSkipFrames) == 0)
|
||||
{
|
||||
if (now > m_timeoutDeadline)
|
||||
{
|
||||
//
|
||||
// connection timed out
|
||||
//
|
||||
m_peer.LogVerbose("Connection timed out at " + now + " deadline was " + m_timeoutDeadline);
|
||||
ExecuteDisconnect("Connection timed out", true);
|
||||
return;
|
||||
}
|
||||
|
||||
// send ping?
|
||||
if (m_status == NetConnectionStatus.Connected)
|
||||
{
|
||||
if (now > m_sentPingTime + m_peer.m_configuration.m_pingInterval)
|
||||
SendPing();
|
||||
|
||||
// handle expand mtu
|
||||
MTUExpansionHeartbeat(now);
|
||||
}
|
||||
|
||||
if (m_disconnectRequested)
|
||||
{
|
||||
ExecuteDisconnect(m_disconnectMessage, m_disconnectReqSendBye);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool connectionReset; // TODO: handle connection reset
|
||||
|
||||
//
|
||||
// Note: at this point m_sendBufferWritePtr and m_sendBufferNumMessages may be non-null; resends may already be queued up
|
||||
//
|
||||
|
||||
byte[] sendBuffer = m_peer.m_sendBuffer;
|
||||
int mtu = m_currentMTU;
|
||||
|
||||
if ((frameCounter % m_messageCoalesceFrames) == 0) // coalesce a few frames
|
||||
{
|
||||
//
|
||||
// send ack messages
|
||||
//
|
||||
while (m_queuedOutgoingAcks.Count > 0)
|
||||
{
|
||||
int acks = (mtu - (m_sendBufferWritePtr + 5)) / 3; // 3 bytes per actual ack
|
||||
if (acks > m_queuedOutgoingAcks.Count)
|
||||
acks = m_queuedOutgoingAcks.Count;
|
||||
|
||||
NetException.Assert(acks > 0);
|
||||
|
||||
m_sendBufferNumMessages++;
|
||||
|
||||
// write acks header
|
||||
sendBuffer[m_sendBufferWritePtr++] = (byte)NetMessageType.Acknowledge;
|
||||
sendBuffer[m_sendBufferWritePtr++] = 0; // no sequence number
|
||||
sendBuffer[m_sendBufferWritePtr++] = 0; // no sequence number
|
||||
int len = (acks * 3) * 8; // bits
|
||||
sendBuffer[m_sendBufferWritePtr++] = (byte)len;
|
||||
sendBuffer[m_sendBufferWritePtr++] = (byte)(len >> 8);
|
||||
|
||||
// write acks
|
||||
for (int i = 0; i < acks; i++)
|
||||
{
|
||||
NetTuple<NetMessageType, int> tuple;
|
||||
m_queuedOutgoingAcks.TryDequeue(out tuple);
|
||||
|
||||
//m_peer.LogVerbose("Sending ack for " + tuple.Item1 + "#" + tuple.Item2);
|
||||
|
||||
sendBuffer[m_sendBufferWritePtr++] = (byte)tuple.Item1;
|
||||
sendBuffer[m_sendBufferWritePtr++] = (byte)tuple.Item2;
|
||||
sendBuffer[m_sendBufferWritePtr++] = (byte)(tuple.Item2 >> 8);
|
||||
}
|
||||
|
||||
if (m_queuedOutgoingAcks.Count > 0)
|
||||
{
|
||||
// send packet and go for another round of acks
|
||||
NetException.Assert(m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0);
|
||||
m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connectionReset);
|
||||
m_statistics.PacketSent(m_sendBufferWritePtr, 1);
|
||||
m_sendBufferWritePtr = 0;
|
||||
m_sendBufferNumMessages = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Parse incoming acks (may trigger resends)
|
||||
//
|
||||
NetTuple<NetMessageType, int> incAck;
|
||||
while (m_queuedIncomingAcks.TryDequeue(out incAck))
|
||||
{
|
||||
//m_peer.LogVerbose("Received ack for " + acktp + "#" + seqNr);
|
||||
NetSenderChannelBase chan = m_sendChannels[(int)incAck.Item1 - 1];
|
||||
|
||||
// If we haven't sent a message on this channel there is no reason to ack it
|
||||
if (chan == null)
|
||||
continue;
|
||||
|
||||
chan.ReceiveAcknowledge(now, incAck.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// send queued messages
|
||||
//
|
||||
if (m_peer.m_executeFlushSendQueue)
|
||||
{
|
||||
for (int i = m_sendChannels.Length - 1; i >= 0; i--) // Reverse order so reliable messages are sent first
|
||||
{
|
||||
var channel = m_sendChannels[i];
|
||||
NetException.Assert(m_sendBufferWritePtr < 1 || m_sendBufferNumMessages > 0);
|
||||
if (channel != null)
|
||||
{
|
||||
channel.SendQueuedMessages(now);
|
||||
if (channel.NeedToSendMessages())
|
||||
m_peer.m_needFlushSendQueue = true; // failed to send all queued sends; likely a full window - need to try again
|
||||
}
|
||||
NetException.Assert(m_sendBufferWritePtr < 1 || m_sendBufferNumMessages > 0);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Put on wire data has been written to send buffer but not yet sent
|
||||
//
|
||||
if (m_sendBufferWritePtr > 0)
|
||||
{
|
||||
m_peer.VerifyNetworkThread();
|
||||
NetException.Assert(m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0);
|
||||
m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connectionReset);
|
||||
m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages);
|
||||
m_sendBufferWritePtr = 0;
|
||||
m_sendBufferNumMessages = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Queue an item for immediate sending on the wire
|
||||
// This method is called from the ISenderChannels
|
||||
internal void QueueSendMessage(NetOutgoingMessage om, int seqNr)
|
||||
{
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
int sz = om.GetEncodedSize();
|
||||
//if (sz > m_currentMTU)
|
||||
// m_peer.LogWarning("Message larger than MTU! Fragmentation must have failed!");
|
||||
|
||||
bool connReset; // TODO: handle connection reset
|
||||
|
||||
// can fit this message together with previously written to buffer?
|
||||
if (m_sendBufferWritePtr + sz > m_currentMTU)
|
||||
{
|
||||
if (m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0)
|
||||
{
|
||||
// previous message in buffer; send these first
|
||||
m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connReset);
|
||||
m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages);
|
||||
m_sendBufferWritePtr = 0;
|
||||
m_sendBufferNumMessages = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// encode it into buffer regardless if it (now) fits within MTU or not
|
||||
m_sendBufferWritePtr = om.Encode(m_peer.m_sendBuffer, m_sendBufferWritePtr, seqNr);
|
||||
m_sendBufferNumMessages++;
|
||||
|
||||
if (m_sendBufferWritePtr > m_currentMTU)
|
||||
{
|
||||
// send immediately; we're already over MTU
|
||||
m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connReset);
|
||||
m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages);
|
||||
m_sendBufferWritePtr = 0;
|
||||
m_sendBufferNumMessages = 0;
|
||||
}
|
||||
|
||||
if (m_sendBufferWritePtr > 0)
|
||||
m_peer.m_needFlushSendQueue = true; // flush in heartbeat
|
||||
|
||||
Interlocked.Decrement(ref om.m_recyclingCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to this remote connection
|
||||
/// </summary>
|
||||
/// <param name="msg">The message to send</param>
|
||||
/// <param name="method">How to deliver the message</param>
|
||||
/// <param name="sequenceChannel">Sequence channel within the delivery method</param>
|
||||
public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel)
|
||||
{
|
||||
return m_peer.SendMessage(msg, this, method, sequenceChannel);
|
||||
}
|
||||
|
||||
// called by SendMessage() and NetPeer.SendMessage; ie. may be user thread
|
||||
internal NetSendResult EnqueueMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel)
|
||||
{
|
||||
if (m_status != NetConnectionStatus.Connected)
|
||||
return NetSendResult.FailedNotConnected;
|
||||
|
||||
NetMessageType tp = (NetMessageType)((int)method + sequenceChannel);
|
||||
msg.m_messageType = tp;
|
||||
|
||||
// TODO: do we need to make this more thread safe?
|
||||
int channelSlot = (int)method - 1 + sequenceChannel;
|
||||
NetSenderChannelBase chan = m_sendChannels[channelSlot];
|
||||
if (chan == null)
|
||||
chan = CreateSenderChannel(tp);
|
||||
|
||||
if ((method != NetDeliveryMethod.Unreliable && method != NetDeliveryMethod.UnreliableSequenced) && msg.GetEncodedSize() > m_currentMTU)
|
||||
m_peer.ThrowOrLog("Reliable message too large! Fragmentation failure?");
|
||||
|
||||
var retval = chan.Enqueue(msg);
|
||||
//if (retval == NetSendResult.Sent && m_peerConfiguration.m_autoFlushSendQueue == false)
|
||||
// retval = NetSendResult.Queued; // queued since we're not autoflushing
|
||||
return retval;
|
||||
}
|
||||
|
||||
// may be on user thread
|
||||
private NetSenderChannelBase CreateSenderChannel(NetMessageType tp)
|
||||
{
|
||||
NetSenderChannelBase chan;
|
||||
lock (m_sendChannels)
|
||||
{
|
||||
NetDeliveryMethod method = NetUtility.GetDeliveryMethod(tp);
|
||||
int sequenceChannel = (int)tp - (int)method;
|
||||
|
||||
int channelSlot = (int)method - 1 + sequenceChannel;
|
||||
if (m_sendChannels[channelSlot] != null)
|
||||
{
|
||||
// we were pre-empted by another call to this method
|
||||
chan = m_sendChannels[channelSlot];
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case NetDeliveryMethod.Unreliable:
|
||||
case NetDeliveryMethod.UnreliableSequenced:
|
||||
chan = new NetUnreliableSenderChannel(this, NetUtility.GetWindowSize(method), method);
|
||||
break;
|
||||
case NetDeliveryMethod.ReliableOrdered:
|
||||
chan = new NetReliableSenderChannel(this, NetUtility.GetWindowSize(method));
|
||||
break;
|
||||
case NetDeliveryMethod.ReliableSequenced:
|
||||
case NetDeliveryMethod.ReliableUnordered:
|
||||
default:
|
||||
chan = new NetReliableSenderChannel(this, NetUtility.GetWindowSize(method));
|
||||
break;
|
||||
}
|
||||
m_sendChannels[channelSlot] = chan;
|
||||
}
|
||||
}
|
||||
|
||||
return chan;
|
||||
}
|
||||
|
||||
// received a library message while Connected
|
||||
internal void ReceivedLibraryMessage(NetMessageType tp, int ptr, int payloadLength)
|
||||
{
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
double now = NetTime.Now;
|
||||
|
||||
switch (tp)
|
||||
{
|
||||
case NetMessageType.Connect:
|
||||
m_peer.LogDebug("Received handshake message (" + tp + ") despite connection being in place");
|
||||
break;
|
||||
|
||||
case NetMessageType.ConnectResponse:
|
||||
// handshake message must have been lost
|
||||
HandleConnectResponse(now, tp, ptr, payloadLength);
|
||||
break;
|
||||
|
||||
case NetMessageType.ConnectionEstablished:
|
||||
// do nothing, all's well
|
||||
break;
|
||||
|
||||
case NetMessageType.LibraryError:
|
||||
m_peer.ThrowOrLog("LibraryError received by ReceivedLibraryMessage; this usually indicates a malformed message");
|
||||
break;
|
||||
|
||||
case NetMessageType.Disconnect:
|
||||
NetIncomingMessage msg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
|
||||
|
||||
m_disconnectRequested = true;
|
||||
m_disconnectMessage = msg.ReadString();
|
||||
m_disconnectReqSendBye = false;
|
||||
//ExecuteDisconnect(msg.ReadString(), false);
|
||||
break;
|
||||
case NetMessageType.Acknowledge:
|
||||
for (int i = 0; i < payloadLength; i+=3)
|
||||
{
|
||||
NetMessageType acktp = (NetMessageType)m_peer.m_receiveBuffer[ptr++]; // netmessagetype
|
||||
int seqNr = m_peer.m_receiveBuffer[ptr++];
|
||||
seqNr |= (m_peer.m_receiveBuffer[ptr++] << 8);
|
||||
|
||||
// need to enqueue this and handle it in the netconnection heartbeat; so be able to send resends together with normal sends
|
||||
m_queuedIncomingAcks.Enqueue(new NetTuple<NetMessageType, int>(acktp, seqNr));
|
||||
}
|
||||
break;
|
||||
case NetMessageType.Ping:
|
||||
int pingNr = m_peer.m_receiveBuffer[ptr++];
|
||||
SendPong(pingNr);
|
||||
break;
|
||||
case NetMessageType.Pong:
|
||||
NetIncomingMessage pmsg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
|
||||
int pongNr = pmsg.ReadByte();
|
||||
float remoteSendTime = pmsg.ReadSingle();
|
||||
ReceivedPong(now, pongNr, remoteSendTime);
|
||||
break;
|
||||
case NetMessageType.ExpandMTURequest:
|
||||
SendMTUSuccess(payloadLength);
|
||||
break;
|
||||
case NetMessageType.ExpandMTUSuccess:
|
||||
if (m_peer.Configuration.AutoExpandMTU == false)
|
||||
{
|
||||
m_peer.LogDebug("Received ExpandMTURequest altho AutoExpandMTU is turned off!");
|
||||
break;
|
||||
}
|
||||
NetIncomingMessage emsg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
|
||||
int size = emsg.ReadInt32();
|
||||
HandleExpandMTUSuccess(now, size);
|
||||
break;
|
||||
case NetMessageType.NatIntroduction:
|
||||
// Unusual situation where server is actually already known, but got a nat introduction - oh well, lets handle it as usual
|
||||
m_peer.HandleNatIntroduction(ptr);
|
||||
break;
|
||||
default:
|
||||
m_peer.LogWarning("Connection received unhandled library message: " + tp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal void ReceivedMessage(NetIncomingMessage msg)
|
||||
{
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
NetMessageType tp = msg.m_receivedMessageType;
|
||||
|
||||
int channelSlot = (int)tp - 1;
|
||||
NetReceiverChannelBase chan = m_receiveChannels[channelSlot];
|
||||
if (chan == null)
|
||||
chan = CreateReceiverChannel(tp);
|
||||
|
||||
chan.ReceiveMessage(msg);
|
||||
}
|
||||
|
||||
private NetReceiverChannelBase CreateReceiverChannel(NetMessageType tp)
|
||||
{
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
// create receiver channel
|
||||
NetReceiverChannelBase chan;
|
||||
NetDeliveryMethod method = NetUtility.GetDeliveryMethod(tp);
|
||||
switch (method)
|
||||
{
|
||||
case NetDeliveryMethod.Unreliable:
|
||||
chan = new NetUnreliableUnorderedReceiver(this);
|
||||
break;
|
||||
case NetDeliveryMethod.ReliableOrdered:
|
||||
chan = new NetReliableOrderedReceiver(this, NetConstants.ReliableOrderedWindowSize);
|
||||
break;
|
||||
case NetDeliveryMethod.UnreliableSequenced:
|
||||
chan = new NetUnreliableSequencedReceiver(this);
|
||||
break;
|
||||
case NetDeliveryMethod.ReliableUnordered:
|
||||
chan = new NetReliableUnorderedReceiver(this, NetConstants.ReliableOrderedWindowSize);
|
||||
break;
|
||||
case NetDeliveryMethod.ReliableSequenced:
|
||||
chan = new NetReliableSequencedReceiver(this, NetConstants.ReliableSequencedWindowSize);
|
||||
break;
|
||||
default:
|
||||
throw new NetException("Unhandled NetDeliveryMethod!");
|
||||
}
|
||||
|
||||
int channelSlot = (int)tp - 1;
|
||||
NetException.Assert(m_receiveChannels[channelSlot] == null);
|
||||
m_receiveChannels[channelSlot] = chan;
|
||||
|
||||
return chan;
|
||||
}
|
||||
|
||||
internal void QueueAck(NetMessageType tp, int sequenceNumber)
|
||||
{
|
||||
m_queuedOutgoingAcks.Enqueue(new NetTuple<NetMessageType, int>(tp, sequenceNumber));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Zero windowSize indicates that the channel is not yet instantiated (used)
|
||||
/// Negative freeWindowSlots means this amount of messages are currently queued but delayed due to closed window
|
||||
/// </summary>
|
||||
public void GetSendQueueInfo(NetDeliveryMethod method, int sequenceChannel, out int windowSize, out int freeWindowSlots)
|
||||
{
|
||||
int channelSlot = (int)method - 1 + sequenceChannel;
|
||||
var chan = m_sendChannels[channelSlot];
|
||||
if (chan == null)
|
||||
{
|
||||
windowSize = NetUtility.GetWindowSize(method);
|
||||
freeWindowSlots = windowSize;
|
||||
return;
|
||||
}
|
||||
|
||||
windowSize = chan.WindowSize;
|
||||
freeWindowSlots = chan.GetFreeWindowSlots();
|
||||
return;
|
||||
}
|
||||
|
||||
public bool CanSendImmediately(NetDeliveryMethod method, int sequenceChannel)
|
||||
{
|
||||
int channelSlot = (int)method - 1 + sequenceChannel;
|
||||
var chan = m_sendChannels[channelSlot];
|
||||
if (chan == null)
|
||||
return true;
|
||||
return chan.GetFreeWindowSlots() > 0;
|
||||
}
|
||||
|
||||
internal void Shutdown(string reason)
|
||||
{
|
||||
ExecuteDisconnect(reason, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents this object
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return "[NetConnection to " + m_remoteEndPoint + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
213
SRMP/Lidgren.Network/NetConnectionStatistics.cs
Normal file
213
SRMP/Lidgren.Network/NetConnectionStatistics.cs
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
// Uncomment the line below to get statistics in RELEASE builds
|
||||
//#define USE_RELEASE_STATISTICS
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal enum MessageResendReason
|
||||
{
|
||||
Delay,
|
||||
HoleInSequence
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Statistics for a NetConnection instance
|
||||
/// </summary>
|
||||
public sealed class NetConnectionStatistics
|
||||
{
|
||||
private readonly NetConnection m_connection;
|
||||
|
||||
internal long m_sentPackets;
|
||||
internal long m_receivedPackets;
|
||||
|
||||
internal long m_sentMessages;
|
||||
internal long m_receivedMessages;
|
||||
internal long m_droppedMessages;
|
||||
internal long m_receivedFragments;
|
||||
|
||||
internal long m_sentBytes;
|
||||
internal long m_receivedBytes;
|
||||
|
||||
internal long m_resentMessagesDueToDelay;
|
||||
internal long m_resentMessagesDueToHole;
|
||||
|
||||
internal NetConnectionStatistics(NetConnection conn)
|
||||
{
|
||||
m_connection = conn;
|
||||
Reset();
|
||||
}
|
||||
|
||||
internal void Reset()
|
||||
{
|
||||
m_sentPackets = 0;
|
||||
m_receivedPackets = 0;
|
||||
m_sentMessages = 0;
|
||||
m_receivedMessages = 0;
|
||||
m_receivedFragments = 0;
|
||||
m_sentBytes = 0;
|
||||
m_receivedBytes = 0;
|
||||
m_resentMessagesDueToDelay = 0;
|
||||
m_resentMessagesDueToHole = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of sent packets for this connection
|
||||
/// </summary>
|
||||
public long SentPackets { get { return m_sentPackets; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of received packets for this connection
|
||||
/// </summary>
|
||||
public long ReceivedPackets { get { return m_receivedPackets; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of sent bytes for this connection
|
||||
/// </summary>
|
||||
public long SentBytes { get { return m_sentBytes; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of received bytes for this connection
|
||||
/// </summary>
|
||||
public long ReceivedBytes { get { return m_receivedBytes; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of sent messages for this connection
|
||||
/// </summary>
|
||||
public long SentMessages { get { return m_sentMessages; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of received messages for this connection
|
||||
/// </summary>
|
||||
public long ReceivedMessages { get { return m_receivedMessages; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of resent reliable messages for this connection
|
||||
/// </summary>
|
||||
public long ResentMessages { get { return m_resentMessagesDueToHole + m_resentMessagesDueToDelay; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of dropped messages for this connection
|
||||
/// </summary>
|
||||
public long DroppedMessages { get { return m_droppedMessages; } }
|
||||
|
||||
// public double LastSendRespondedTo { get { return m_connection.m_lastSendRespondedTo; } }
|
||||
|
||||
#if !USE_RELEASE_STATISTICS
|
||||
[Conditional("DEBUG")]
|
||||
#endif
|
||||
internal void PacketSent(int numBytes, int numMessages)
|
||||
{
|
||||
NetException.Assert(numBytes > 0 && numMessages > 0);
|
||||
m_sentPackets++;
|
||||
m_sentBytes += numBytes;
|
||||
m_sentMessages += numMessages;
|
||||
}
|
||||
|
||||
#if !USE_RELEASE_STATISTICS
|
||||
[Conditional("DEBUG")]
|
||||
#endif
|
||||
internal void PacketReceived(int numBytes, int numMessages, int numFragments)
|
||||
{
|
||||
NetException.Assert(numBytes > 0 && numMessages > 0);
|
||||
m_receivedPackets++;
|
||||
m_receivedBytes += numBytes;
|
||||
m_receivedMessages += numMessages;
|
||||
m_receivedFragments += numFragments;
|
||||
}
|
||||
|
||||
#if !USE_RELEASE_STATISTICS
|
||||
[Conditional("DEBUG")]
|
||||
#endif
|
||||
internal void MessageResent(MessageResendReason reason)
|
||||
{
|
||||
if (reason == MessageResendReason.Delay)
|
||||
m_resentMessagesDueToDelay++;
|
||||
else
|
||||
m_resentMessagesDueToHole++;
|
||||
}
|
||||
|
||||
#if !USE_RELEASE_STATISTICS
|
||||
[Conditional("DEBUG")]
|
||||
#endif
|
||||
internal void MessageDropped()
|
||||
{
|
||||
m_droppedMessages++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents this object
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder bdr = new StringBuilder();
|
||||
//bdr.AppendLine("Average roundtrip time: " + NetTime.ToReadable(m_connection.m_averageRoundtripTime));
|
||||
bdr.AppendLine("Current MTU: " + m_connection.m_currentMTU);
|
||||
bdr.AppendLine("Sent " + m_sentBytes + " bytes in " + m_sentMessages + " messages in " + m_sentPackets + " packets");
|
||||
bdr.AppendLine("Received " + m_receivedBytes + " bytes in " + m_receivedMessages + " messages (of which " + m_receivedFragments + " fragments) in " + m_receivedPackets + " packets");
|
||||
bdr.AppendLine("Dropped " + m_droppedMessages + " messages (dupes/late/early)");
|
||||
|
||||
if (m_resentMessagesDueToDelay > 0)
|
||||
bdr.AppendLine("Resent messages (delay): " + m_resentMessagesDueToDelay);
|
||||
if (m_resentMessagesDueToHole > 0)
|
||||
bdr.AppendLine("Resent messages (holes): " + m_resentMessagesDueToHole);
|
||||
|
||||
int numUnsent = 0;
|
||||
int numStored = 0;
|
||||
foreach (NetSenderChannelBase sendChan in m_connection.m_sendChannels)
|
||||
{
|
||||
if (sendChan == null)
|
||||
continue;
|
||||
numUnsent += sendChan.QueuedSendsCount;
|
||||
|
||||
var relSendChan = sendChan as NetReliableSenderChannel;
|
||||
if (relSendChan != null)
|
||||
{
|
||||
for (int i = 0; i < relSendChan.m_storedMessages.Length; i++)
|
||||
if (relSendChan.m_storedMessages[i].Message != null)
|
||||
numStored++;
|
||||
}
|
||||
}
|
||||
|
||||
int numWithheld = 0;
|
||||
foreach (NetReceiverChannelBase recChan in m_connection.m_receiveChannels)
|
||||
{
|
||||
var relRecChan = recChan as NetReliableOrderedReceiver;
|
||||
if (relRecChan != null)
|
||||
{
|
||||
for (int i = 0; i < relRecChan.m_withheldMessages.Length; i++)
|
||||
if (relRecChan.m_withheldMessages[i] != null)
|
||||
numWithheld++;
|
||||
}
|
||||
}
|
||||
|
||||
bdr.AppendLine("Unsent messages: " + numUnsent);
|
||||
bdr.AppendLine("Stored messages: " + numStored);
|
||||
bdr.AppendLine("Withheld messages: " + numWithheld);
|
||||
|
||||
return bdr.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
68
SRMP/Lidgren.Network/NetConnectionStatus.cs
Normal file
68
SRMP/Lidgren.Network/NetConnectionStatus.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Status for a NetConnection instance
|
||||
/// </summary>
|
||||
public enum NetConnectionStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// No connection, or attempt, in place
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Connect has been sent; waiting for ConnectResponse
|
||||
/// </summary>
|
||||
InitiatedConnect,
|
||||
|
||||
/// <summary>
|
||||
/// Connect was received, but ConnectResponse hasn't been sent yet
|
||||
/// </summary>
|
||||
ReceivedInitiation,
|
||||
|
||||
/// <summary>
|
||||
/// Connect was received and ApprovalMessage released to the application; awaiting Approve() or Deny()
|
||||
/// </summary>
|
||||
RespondedAwaitingApproval, // We got Connect, released ApprovalMessage
|
||||
|
||||
/// <summary>
|
||||
/// Connect was received and ConnectResponse has been sent; waiting for ConnectionEstablished
|
||||
/// </summary>
|
||||
RespondedConnect, // we got Connect, sent ConnectResponse
|
||||
|
||||
/// <summary>
|
||||
/// Connected
|
||||
/// </summary>
|
||||
Connected, // we received ConnectResponse (if initiator) or ConnectionEstablished (if passive)
|
||||
|
||||
/// <summary>
|
||||
/// In the process of disconnecting
|
||||
/// </summary>
|
||||
Disconnecting,
|
||||
|
||||
/// <summary>
|
||||
/// Disconnected
|
||||
/// </summary>
|
||||
Disconnected
|
||||
}
|
||||
}
|
||||
57
SRMP/Lidgren.Network/NetConstants.cs
Normal file
57
SRMP/Lidgren.Network/NetConstants.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// All the constants used when compiling the library
|
||||
/// </summary>
|
||||
internal static class NetConstants
|
||||
{
|
||||
internal const int NumTotalChannels = 99;
|
||||
|
||||
internal const int NetChannelsPerDeliveryMethod = 32;
|
||||
|
||||
internal const int NumSequenceNumbers = 1024;
|
||||
|
||||
internal const int HeaderByteSize = 5;
|
||||
|
||||
internal const int UnreliableWindowSize = 128;
|
||||
internal const int ReliableOrderedWindowSize = 64;
|
||||
internal const int ReliableSequencedWindowSize = 64;
|
||||
internal const int DefaultWindowSize = 64;
|
||||
|
||||
internal const int MaxFragmentationGroups = ushort.MaxValue - 1;
|
||||
|
||||
internal const int UnfragmentedMessageHeaderSize = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Number of channels which needs a sequence number to work
|
||||
/// </summary>
|
||||
internal const int NumSequencedChannels = ((int)NetMessageType.UserReliableOrdered1 + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserSequenced1;
|
||||
|
||||
/// <summary>
|
||||
/// Number of reliable channels
|
||||
/// </summary>
|
||||
internal const int NumReliableChannels = ((int)NetMessageType.UserReliableOrdered1 + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserReliableUnordered;
|
||||
|
||||
internal const string ConnResetMessage = "Connection was reset by remote host";
|
||||
}
|
||||
}
|
||||
46
SRMP/Lidgren.Network/NetDeliveryMethod.cs
Normal file
46
SRMP/Lidgren.Network/NetDeliveryMethod.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// How the library deals with resends and handling of late messages
|
||||
/// </summary>
|
||||
public enum NetDeliveryMethod : byte
|
||||
{
|
||||
//
|
||||
// Actually a publicly visible subset of NetMessageType
|
||||
//
|
||||
|
||||
/// <summary>
|
||||
/// Indicates an error
|
||||
/// </summary>
|
||||
Unknown = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Unreliable, unordered delivery
|
||||
/// </summary>
|
||||
Unreliable = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Unreliable delivery, but automatically dropping late messages
|
||||
/// </summary>
|
||||
UnreliableSequenced = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Reliable delivery, but unordered
|
||||
/// </summary>
|
||||
ReliableUnordered = 34,
|
||||
|
||||
/// <summary>
|
||||
/// Reliable delivery, except for late messages which are dropped
|
||||
/// </summary>
|
||||
ReliableSequenced = 35,
|
||||
|
||||
/// <summary>
|
||||
/// Reliable, ordered delivery
|
||||
/// </summary>
|
||||
ReliableOrdered = 67,
|
||||
}
|
||||
}
|
||||
74
SRMP/Lidgren.Network/NetException.cs
Normal file
74
SRMP/Lidgren.Network/NetException.cs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown in the Lidgren Network Library
|
||||
/// </summary>
|
||||
public sealed class NetException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// NetException constructor
|
||||
/// </summary>
|
||||
public NetException()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NetException constructor
|
||||
/// </summary>
|
||||
public NetException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NetException constructor
|
||||
/// </summary>
|
||||
public NetException(string message, Exception inner)
|
||||
: base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception, in DEBUG only, if first parameter is false
|
||||
/// </summary>
|
||||
[Conditional("DEBUG")]
|
||||
public static void Assert(bool isOk, string message)
|
||||
{
|
||||
if (!isOk)
|
||||
throw new NetException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an exception, in DEBUG only, if first parameter is false
|
||||
/// </summary>
|
||||
[Conditional("DEBUG")]
|
||||
public static void Assert(bool isOk)
|
||||
{
|
||||
if (!isOk)
|
||||
throw new NetException();
|
||||
}
|
||||
}
|
||||
}
|
||||
175
SRMP/Lidgren.Network/NetFragmentationHelper.cs
Normal file
175
SRMP/Lidgren.Network/NetFragmentationHelper.cs
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal static class NetFragmentationHelper
|
||||
{
|
||||
internal static int WriteHeader(
|
||||
byte[] destination,
|
||||
int ptr,
|
||||
int group,
|
||||
int totalBits,
|
||||
int chunkByteSize,
|
||||
int chunkNumber)
|
||||
{
|
||||
uint num1 = (uint)group;
|
||||
while (num1 >= 0x80)
|
||||
{
|
||||
destination[ptr++] = (byte)(num1 | 0x80);
|
||||
num1 = num1 >> 7;
|
||||
}
|
||||
destination[ptr++] = (byte)num1;
|
||||
|
||||
// write variable length fragment total bits
|
||||
uint num2 = (uint)totalBits;
|
||||
while (num2 >= 0x80)
|
||||
{
|
||||
destination[ptr++] = (byte)(num2 | 0x80);
|
||||
num2 = num2 >> 7;
|
||||
}
|
||||
destination[ptr++] = (byte)num2;
|
||||
|
||||
// write variable length fragment chunk size
|
||||
uint num3 = (uint)chunkByteSize;
|
||||
while (num3 >= 0x80)
|
||||
{
|
||||
destination[ptr++] = (byte)(num3 | 0x80);
|
||||
num3 = num3 >> 7;
|
||||
}
|
||||
destination[ptr++] = (byte)num3;
|
||||
|
||||
// write variable length fragment chunk number
|
||||
uint num4 = (uint)chunkNumber;
|
||||
while (num4 >= 0x80)
|
||||
{
|
||||
destination[ptr++] = (byte)(num4 | 0x80);
|
||||
num4 = num4 >> 7;
|
||||
}
|
||||
destination[ptr++] = (byte)num4;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
internal static int ReadHeader(byte[] buffer, int ptr, out int group, out int totalBits, out int chunkByteSize, out int chunkNumber)
|
||||
{
|
||||
int num1 = 0;
|
||||
int num2 = 0;
|
||||
while (true)
|
||||
{
|
||||
byte num3 = buffer[ptr++];
|
||||
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
|
||||
num2 += 7;
|
||||
if ((num3 & 0x80) == 0)
|
||||
{
|
||||
group = num1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
num1 = 0;
|
||||
num2 = 0;
|
||||
while (true)
|
||||
{
|
||||
byte num3 = buffer[ptr++];
|
||||
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
|
||||
num2 += 7;
|
||||
if ((num3 & 0x80) == 0)
|
||||
{
|
||||
totalBits = num1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
num1 = 0;
|
||||
num2 = 0;
|
||||
while (true)
|
||||
{
|
||||
byte num3 = buffer[ptr++];
|
||||
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
|
||||
num2 += 7;
|
||||
if ((num3 & 0x80) == 0)
|
||||
{
|
||||
chunkByteSize = num1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
num1 = 0;
|
||||
num2 = 0;
|
||||
while (true)
|
||||
{
|
||||
byte num3 = buffer[ptr++];
|
||||
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
|
||||
num2 += 7;
|
||||
if ((num3 & 0x80) == 0)
|
||||
{
|
||||
chunkNumber = num1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
internal static int GetFragmentationHeaderSize(int groupId, int totalBytes, int chunkByteSize, int numChunks)
|
||||
{
|
||||
int len = 4;
|
||||
|
||||
// write variable length fragment group id
|
||||
uint num1 = (uint)groupId;
|
||||
while (num1 >= 0x80)
|
||||
{
|
||||
len++;
|
||||
num1 = num1 >> 7;
|
||||
}
|
||||
|
||||
// write variable length fragment total bits
|
||||
uint num2 = (uint)(totalBytes * 8);
|
||||
while (num2 >= 0x80)
|
||||
{
|
||||
len++;
|
||||
num2 = num2 >> 7;
|
||||
}
|
||||
|
||||
// write variable length fragment chunk byte size
|
||||
uint num3 = (uint)chunkByteSize;
|
||||
while (num3 >= 0x80)
|
||||
{
|
||||
len++;
|
||||
num3 = num3 >> 7;
|
||||
}
|
||||
|
||||
// write variable length fragment chunk number
|
||||
uint num4 = (uint)numChunks;
|
||||
while (num4 >= 0x80)
|
||||
{
|
||||
len++;
|
||||
num4 = num4 >> 7;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
internal static int GetBestChunkSize(int group, int totalBytes, int mtu)
|
||||
{
|
||||
int tryChunkSize = mtu - NetConstants.HeaderByteSize - 4; // naive approximation
|
||||
int est = GetFragmentationHeaderSize(group, totalBytes, tryChunkSize, totalBytes / tryChunkSize);
|
||||
tryChunkSize = mtu - NetConstants.HeaderByteSize - est; // slightly less naive approximation
|
||||
|
||||
int headerSize = 0;
|
||||
do
|
||||
{
|
||||
tryChunkSize--; // keep reducing chunk size until it fits within MTU including header
|
||||
|
||||
int numChunks = totalBytes / tryChunkSize;
|
||||
if (numChunks * tryChunkSize < totalBytes)
|
||||
numChunks++;
|
||||
|
||||
headerSize = GetFragmentationHeaderSize(group, totalBytes, tryChunkSize, numChunks); // 4+ bytes
|
||||
|
||||
} while (tryChunkSize + headerSize + NetConstants.HeaderByteSize + 1 >= mtu);
|
||||
|
||||
return tryChunkSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
SRMP/Lidgren.Network/NetFragmentationInfo.cs
Normal file
12
SRMP/Lidgren.Network/NetFragmentationInfo.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public sealed class NetFragmentationInfo
|
||||
{
|
||||
public int TotalFragmentCount;
|
||||
public bool[] Received;
|
||||
public int TotalReceived;
|
||||
public int FragmentSize;
|
||||
}
|
||||
}
|
||||
119
SRMP/Lidgren.Network/NetIncomingMessage.cs
Normal file
119
SRMP/Lidgren.Network/NetIncomingMessage.cs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Diagnostics;
|
||||
|
||||
#if !__NOIPENDPOINT__
|
||||
using NetEndPoint = System.Net.IPEndPoint;
|
||||
#endif
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Incoming message either sent from a remote peer or generated within the library
|
||||
/// </summary>
|
||||
[DebuggerDisplay("Type={MessageType} LengthBits={LengthBits}")]
|
||||
public sealed class NetIncomingMessage : NetBuffer
|
||||
{
|
||||
internal NetIncomingMessageType m_incomingMessageType;
|
||||
internal NetEndPoint m_senderEndPoint;
|
||||
internal NetConnection m_senderConnection;
|
||||
internal int m_sequenceNumber;
|
||||
internal NetMessageType m_receivedMessageType;
|
||||
internal bool m_isFragment;
|
||||
internal double m_receiveTime;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of this incoming message
|
||||
/// </summary>
|
||||
public NetIncomingMessageType MessageType { get { return m_incomingMessageType; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delivery method this message was sent with (if user data)
|
||||
/// </summary>
|
||||
public NetDeliveryMethod DeliveryMethod { get { return NetUtility.GetDeliveryMethod(m_receivedMessageType); } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sequence channel this message was sent with (if user data)
|
||||
/// </summary>
|
||||
public int SequenceChannel { get { return (int)m_receivedMessageType - (int)NetUtility.GetDeliveryMethod(m_receivedMessageType); } }
|
||||
|
||||
/// <summary>
|
||||
/// endpoint of sender, if any
|
||||
/// </summary>
|
||||
public NetEndPoint SenderEndPoint { get { return m_senderEndPoint; } }
|
||||
|
||||
/// <summary>
|
||||
/// NetConnection of sender, if any
|
||||
/// </summary>
|
||||
public NetConnection SenderConnection { get { return m_senderConnection; } }
|
||||
|
||||
/// <summary>
|
||||
/// What local time the message was received from the network
|
||||
/// </summary>
|
||||
public double ReceiveTime { get { return m_receiveTime; } }
|
||||
|
||||
internal NetIncomingMessage()
|
||||
{
|
||||
}
|
||||
|
||||
internal NetIncomingMessage(NetIncomingMessageType tp)
|
||||
{
|
||||
m_incomingMessageType = tp;
|
||||
}
|
||||
|
||||
internal void Reset()
|
||||
{
|
||||
m_incomingMessageType = NetIncomingMessageType.Error;
|
||||
m_readPosition = 0;
|
||||
m_receivedMessageType = NetMessageType.LibraryError;
|
||||
m_senderConnection = null;
|
||||
m_bitLength = 0;
|
||||
m_isFragment = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt a message
|
||||
/// </summary>
|
||||
/// <param name="encryption">The encryption algorithm used to encrypt the message</param>
|
||||
/// <returns>true on success</returns>
|
||||
public bool Decrypt(NetEncryption encryption)
|
||||
{
|
||||
return encryption.Decrypt(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a value, in local time comparable to NetTime.Now, written using WriteTime()
|
||||
/// Must have a connected sender
|
||||
/// </summary>
|
||||
public double ReadTime(bool highPrecision)
|
||||
{
|
||||
return ReadTime(m_senderConnection, highPrecision);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents this object
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return "[NetIncomingMessage #" + m_sequenceNumber + " " + this.LengthBytes + " bytes]";
|
||||
}
|
||||
}
|
||||
}
|
||||
105
SRMP/Lidgren.Network/NetIncomingMessageType.cs
Normal file
105
SRMP/Lidgren.Network/NetIncomingMessageType.cs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of a NetIncomingMessage
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
|
||||
public enum NetIncomingMessageType
|
||||
{
|
||||
//
|
||||
// library note: values are power-of-two, but they are not flags - it's a convenience for NetPeerConfiguration.DisabledMessageTypes
|
||||
//
|
||||
|
||||
/// <summary>
|
||||
/// Error; this value should never appear
|
||||
/// </summary>
|
||||
Error = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Status for a connection changed
|
||||
/// </summary>
|
||||
StatusChanged = 1 << 0, // Data (string)
|
||||
|
||||
/// <summary>
|
||||
/// Data sent using SendUnconnectedMessage
|
||||
/// </summary>
|
||||
UnconnectedData = 1 << 1, // Data Based on data received
|
||||
|
||||
/// <summary>
|
||||
/// Connection approval is needed
|
||||
/// </summary>
|
||||
ConnectionApproval = 1 << 2, // Data
|
||||
|
||||
/// <summary>
|
||||
/// Application data
|
||||
/// </summary>
|
||||
Data = 1 << 3, // Data Based on data received
|
||||
|
||||
/// <summary>
|
||||
/// Receipt of delivery
|
||||
/// </summary>
|
||||
Receipt = 1 << 4, // Data
|
||||
|
||||
/// <summary>
|
||||
/// Discovery request for a response
|
||||
/// </summary>
|
||||
DiscoveryRequest = 1 << 5, // (no data)
|
||||
|
||||
/// <summary>
|
||||
/// Discovery response to a request
|
||||
/// </summary>
|
||||
DiscoveryResponse = 1 << 6, // Data
|
||||
|
||||
/// <summary>
|
||||
/// Verbose debug message
|
||||
/// </summary>
|
||||
VerboseDebugMessage = 1 << 7, // Data (string)
|
||||
|
||||
/// <summary>
|
||||
/// Debug message
|
||||
/// </summary>
|
||||
DebugMessage = 1 << 8, // Data (string)
|
||||
|
||||
/// <summary>
|
||||
/// Warning message
|
||||
/// </summary>
|
||||
WarningMessage = 1 << 9, // Data (string)
|
||||
|
||||
/// <summary>
|
||||
/// Error message
|
||||
/// </summary>
|
||||
ErrorMessage = 1 << 10, // Data (string)
|
||||
|
||||
/// <summary>
|
||||
/// NAT introduction was successful
|
||||
/// </summary>
|
||||
NatIntroductionSuccess = 1 << 11, // Data (as passed to master server)
|
||||
|
||||
/// <summary>
|
||||
/// A roundtrip was measured and NetConnection.AverageRoundtripTime was updated
|
||||
/// </summary>
|
||||
ConnectionLatencyUpdated = 1 << 12, // Seconds as a Single
|
||||
}
|
||||
}
|
||||
177
SRMP/Lidgren.Network/NetMessageType.cs
Normal file
177
SRMP/Lidgren.Network/NetMessageType.cs
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal enum NetMessageType : byte
|
||||
{
|
||||
Unconnected = 0,
|
||||
|
||||
UserUnreliable = 1,
|
||||
|
||||
UserSequenced1 = 2,
|
||||
UserSequenced2 = 3,
|
||||
UserSequenced3 = 4,
|
||||
UserSequenced4 = 5,
|
||||
UserSequenced5 = 6,
|
||||
UserSequenced6 = 7,
|
||||
UserSequenced7 = 8,
|
||||
UserSequenced8 = 9,
|
||||
UserSequenced9 = 10,
|
||||
UserSequenced10 = 11,
|
||||
UserSequenced11 = 12,
|
||||
UserSequenced12 = 13,
|
||||
UserSequenced13 = 14,
|
||||
UserSequenced14 = 15,
|
||||
UserSequenced15 = 16,
|
||||
UserSequenced16 = 17,
|
||||
UserSequenced17 = 18,
|
||||
UserSequenced18 = 19,
|
||||
UserSequenced19 = 20,
|
||||
UserSequenced20 = 21,
|
||||
UserSequenced21 = 22,
|
||||
UserSequenced22 = 23,
|
||||
UserSequenced23 = 24,
|
||||
UserSequenced24 = 25,
|
||||
UserSequenced25 = 26,
|
||||
UserSequenced26 = 27,
|
||||
UserSequenced27 = 28,
|
||||
UserSequenced28 = 29,
|
||||
UserSequenced29 = 30,
|
||||
UserSequenced30 = 31,
|
||||
UserSequenced31 = 32,
|
||||
UserSequenced32 = 33,
|
||||
|
||||
UserReliableUnordered = 34,
|
||||
|
||||
UserReliableSequenced1 = 35,
|
||||
UserReliableSequenced2 = 36,
|
||||
UserReliableSequenced3 = 37,
|
||||
UserReliableSequenced4 = 38,
|
||||
UserReliableSequenced5 = 39,
|
||||
UserReliableSequenced6 = 40,
|
||||
UserReliableSequenced7 = 41,
|
||||
UserReliableSequenced8 = 42,
|
||||
UserReliableSequenced9 = 43,
|
||||
UserReliableSequenced10 = 44,
|
||||
UserReliableSequenced11 = 45,
|
||||
UserReliableSequenced12 = 46,
|
||||
UserReliableSequenced13 = 47,
|
||||
UserReliableSequenced14 = 48,
|
||||
UserReliableSequenced15 = 49,
|
||||
UserReliableSequenced16 = 50,
|
||||
UserReliableSequenced17 = 51,
|
||||
UserReliableSequenced18 = 52,
|
||||
UserReliableSequenced19 = 53,
|
||||
UserReliableSequenced20 = 54,
|
||||
UserReliableSequenced21 = 55,
|
||||
UserReliableSequenced22 = 56,
|
||||
UserReliableSequenced23 = 57,
|
||||
UserReliableSequenced24 = 58,
|
||||
UserReliableSequenced25 = 59,
|
||||
UserReliableSequenced26 = 60,
|
||||
UserReliableSequenced27 = 61,
|
||||
UserReliableSequenced28 = 62,
|
||||
UserReliableSequenced29 = 63,
|
||||
UserReliableSequenced30 = 64,
|
||||
UserReliableSequenced31 = 65,
|
||||
UserReliableSequenced32 = 66,
|
||||
|
||||
UserReliableOrdered1 = 67,
|
||||
UserReliableOrdered2 = 68,
|
||||
UserReliableOrdered3 = 69,
|
||||
UserReliableOrdered4 = 70,
|
||||
UserReliableOrdered5 = 71,
|
||||
UserReliableOrdered6 = 72,
|
||||
UserReliableOrdered7 = 73,
|
||||
UserReliableOrdered8 = 74,
|
||||
UserReliableOrdered9 = 75,
|
||||
UserReliableOrdered10 = 76,
|
||||
UserReliableOrdered11 = 77,
|
||||
UserReliableOrdered12 = 78,
|
||||
UserReliableOrdered13 = 79,
|
||||
UserReliableOrdered14 = 80,
|
||||
UserReliableOrdered15 = 81,
|
||||
UserReliableOrdered16 = 82,
|
||||
UserReliableOrdered17 = 83,
|
||||
UserReliableOrdered18 = 84,
|
||||
UserReliableOrdered19 = 85,
|
||||
UserReliableOrdered20 = 86,
|
||||
UserReliableOrdered21 = 87,
|
||||
UserReliableOrdered22 = 88,
|
||||
UserReliableOrdered23 = 89,
|
||||
UserReliableOrdered24 = 90,
|
||||
UserReliableOrdered25 = 91,
|
||||
UserReliableOrdered26 = 92,
|
||||
UserReliableOrdered27 = 93,
|
||||
UserReliableOrdered28 = 94,
|
||||
UserReliableOrdered29 = 95,
|
||||
UserReliableOrdered30 = 96,
|
||||
UserReliableOrdered31 = 97,
|
||||
UserReliableOrdered32 = 98,
|
||||
|
||||
Unused1 = 99,
|
||||
Unused2 = 100,
|
||||
Unused3 = 101,
|
||||
Unused4 = 102,
|
||||
Unused5 = 103,
|
||||
Unused6 = 104,
|
||||
Unused7 = 105,
|
||||
Unused8 = 106,
|
||||
Unused9 = 107,
|
||||
Unused10 = 108,
|
||||
Unused11 = 109,
|
||||
Unused12 = 110,
|
||||
Unused13 = 111,
|
||||
Unused14 = 112,
|
||||
Unused15 = 113,
|
||||
Unused16 = 114,
|
||||
Unused17 = 115,
|
||||
Unused18 = 116,
|
||||
Unused19 = 117,
|
||||
Unused20 = 118,
|
||||
Unused21 = 119,
|
||||
Unused22 = 120,
|
||||
Unused23 = 121,
|
||||
Unused24 = 122,
|
||||
Unused25 = 123,
|
||||
Unused26 = 124,
|
||||
Unused27 = 125,
|
||||
Unused28 = 126,
|
||||
Unused29 = 127,
|
||||
|
||||
LibraryError = 128,
|
||||
Ping = 129, // used for RTT calculation
|
||||
Pong = 130, // used for RTT calculation
|
||||
Connect = 131,
|
||||
ConnectResponse = 132,
|
||||
ConnectionEstablished = 133,
|
||||
Acknowledge = 134,
|
||||
Disconnect = 135,
|
||||
Discovery = 136,
|
||||
DiscoveryResponse = 137,
|
||||
NatPunchMessage = 138, // send between peers
|
||||
NatIntroduction = 139, // send to master server
|
||||
NatIntroductionConfirmRequest = 142,
|
||||
NatIntroductionConfirmed = 143,
|
||||
ExpandMTURequest = 140,
|
||||
ExpandMTUSuccess = 141,
|
||||
}
|
||||
}
|
||||
162
SRMP/Lidgren.Network/NetNatIntroduction.cs
Normal file
162
SRMP/Lidgren.Network/NetNatIntroduction.cs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
|
||||
#if !__NOIPENDPOINT__
|
||||
using NetEndPoint = System.Net.IPEndPoint;
|
||||
#endif
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetPeer {
|
||||
private const byte HostByte = 1;
|
||||
private const byte ClientByte = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Send NetIntroduction to hostExternal and clientExternal; introducing client to host
|
||||
/// </summary>
|
||||
public void Introduce(
|
||||
NetEndPoint hostInternal,
|
||||
NetEndPoint hostExternal,
|
||||
NetEndPoint clientInternal,
|
||||
NetEndPoint clientExternal,
|
||||
string token)
|
||||
{
|
||||
// send message to client
|
||||
NetOutgoingMessage um = CreateMessage(10 + token.Length + 1);
|
||||
um.m_messageType = NetMessageType.NatIntroduction;
|
||||
um.Write((byte)0);
|
||||
um.Write(hostInternal);
|
||||
um.Write(hostExternal);
|
||||
um.Write(token);
|
||||
Interlocked.Increment(ref um.m_recyclingCount);
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<NetEndPoint, NetOutgoingMessage>(clientExternal, um));
|
||||
|
||||
// send message to host
|
||||
um = CreateMessage(10 + token.Length + 1);
|
||||
um.m_messageType = NetMessageType.NatIntroduction;
|
||||
um.Write((byte)1);
|
||||
um.Write(clientInternal);
|
||||
um.Write(clientExternal);
|
||||
um.Write(token);
|
||||
Interlocked.Increment(ref um.m_recyclingCount);
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<NetEndPoint, NetOutgoingMessage>(hostExternal, um));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when host/client receives a NatIntroduction message from a master server
|
||||
/// </summary>
|
||||
internal void HandleNatIntroduction(int ptr)
|
||||
{
|
||||
VerifyNetworkThread();
|
||||
|
||||
// read intro
|
||||
NetIncomingMessage tmp = SetupReadHelperMessage(ptr, 1000); // never mind length
|
||||
|
||||
byte hostByte = tmp.ReadByte();
|
||||
NetEndPoint remoteInternal = tmp.ReadIPEndPoint();
|
||||
NetEndPoint remoteExternal = tmp.ReadIPEndPoint();
|
||||
string token = tmp.ReadString();
|
||||
bool isHost = (hostByte != 0);
|
||||
|
||||
LogDebug("NAT introduction received; we are designated " + (isHost ? "host" : "client"));
|
||||
|
||||
NetOutgoingMessage punch;
|
||||
|
||||
if (!isHost && m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess) == false)
|
||||
return; // no need to punch - we're not listening for nat intros!
|
||||
|
||||
// send internal punch
|
||||
punch = CreateMessage(1);
|
||||
punch.m_messageType = NetMessageType.NatPunchMessage;
|
||||
punch.Write(hostByte);
|
||||
punch.Write(token);
|
||||
Interlocked.Increment(ref punch.m_recyclingCount);
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<NetEndPoint, NetOutgoingMessage>(remoteInternal, punch));
|
||||
LogDebug("NAT punch sent to " + remoteInternal);
|
||||
|
||||
// send external punch
|
||||
punch = CreateMessage(1);
|
||||
punch.m_messageType = NetMessageType.NatPunchMessage;
|
||||
punch.Write(hostByte);
|
||||
punch.Write(token);
|
||||
Interlocked.Increment(ref punch.m_recyclingCount);
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<NetEndPoint, NetOutgoingMessage>(remoteExternal, punch));
|
||||
LogDebug("NAT punch sent to " + remoteExternal);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when receiving a NatPunchMessage from a remote endpoint
|
||||
/// </summary>
|
||||
private void HandleNatPunch(int ptr, NetEndPoint senderEndPoint)
|
||||
{
|
||||
NetIncomingMessage tmp = SetupReadHelperMessage(ptr, 1000); // never mind length
|
||||
|
||||
var isFromClient = tmp.ReadByte() == ClientByte;
|
||||
string token = tmp.ReadString();
|
||||
if (isFromClient)
|
||||
{
|
||||
LogDebug("NAT punch received from " + senderEndPoint + " we're host, so we send a NatIntroductionConfirmed message - token is " + token);
|
||||
|
||||
var confirmResponse = CreateMessage(1);
|
||||
confirmResponse.m_messageType = NetMessageType.NatIntroductionConfirmed;
|
||||
confirmResponse.Write(HostByte);
|
||||
confirmResponse.Write(token);
|
||||
Interlocked.Increment(ref confirmResponse.m_recyclingCount);
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<NetEndPoint, NetOutgoingMessage>(senderEndPoint, confirmResponse));
|
||||
}
|
||||
else
|
||||
{
|
||||
LogDebug("NAT punch received from " + senderEndPoint + " we're client, so we send a NatIntroductionConfirmRequest - token is " + token);
|
||||
|
||||
var confirmRequest = CreateMessage(1);
|
||||
confirmRequest.m_messageType = NetMessageType.NatIntroductionConfirmRequest;
|
||||
confirmRequest.Write(ClientByte);
|
||||
confirmRequest.Write(token);
|
||||
Interlocked.Increment(ref confirmRequest.m_recyclingCount);
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<NetEndPoint, NetOutgoingMessage>(senderEndPoint, confirmRequest));
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleNatPunchConfirmRequest(int ptr, NetEndPoint senderEndPoint)
|
||||
{
|
||||
NetIncomingMessage tmp = SetupReadHelperMessage(ptr, 1000); // never mind length
|
||||
var isFromClient = tmp.ReadByte() == ClientByte;
|
||||
string token = tmp.ReadString();
|
||||
|
||||
LogDebug("Received NAT punch confirmation from " + senderEndPoint + " sending NatIntroductionConfirmed - token is " + token);
|
||||
|
||||
var confirmResponse = CreateMessage(1);
|
||||
confirmResponse.m_messageType = NetMessageType.NatIntroductionConfirmed;
|
||||
confirmResponse.Write(isFromClient ? HostByte : ClientByte);
|
||||
confirmResponse.Write(token);
|
||||
Interlocked.Increment(ref confirmResponse.m_recyclingCount);
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<NetEndPoint, NetOutgoingMessage>(senderEndPoint, confirmResponse));
|
||||
}
|
||||
|
||||
private void HandleNatPunchConfirmed(int ptr, NetEndPoint senderEndPoint)
|
||||
{
|
||||
NetIncomingMessage tmp = SetupReadHelperMessage(ptr, 1000); // never mind length
|
||||
var isFromClient = tmp.ReadByte() == ClientByte;
|
||||
if (isFromClient)
|
||||
{
|
||||
LogDebug("NAT punch confirmation received from " + senderEndPoint + " we're host, so we ignore this");
|
||||
return;
|
||||
}
|
||||
|
||||
string token = tmp.ReadString();
|
||||
|
||||
LogDebug("NAT punch confirmation received from " + senderEndPoint + " we're client so we go ahead and succeed the introduction");
|
||||
|
||||
//
|
||||
// Release punch success to client; enabling him to Connect() to msg.Sender if token is ok
|
||||
//
|
||||
NetIncomingMessage punchSuccess = CreateIncomingMessage(NetIncomingMessageType.NatIntroductionSuccess, 10);
|
||||
punchSuccess.m_senderEndPoint = senderEndPoint;
|
||||
punchSuccess.Write(token);
|
||||
ReleaseMessage(punchSuccess);
|
||||
}
|
||||
}
|
||||
}
|
||||
142
SRMP/Lidgren.Network/NetOutgoingMessage.cs
Normal file
142
SRMP/Lidgren.Network/NetOutgoingMessage.cs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Outgoing message used to send data to remote peer(s)
|
||||
/// </summary>
|
||||
[DebuggerDisplay("LengthBits={LengthBits}")]
|
||||
public sealed class NetOutgoingMessage : NetBuffer
|
||||
{
|
||||
internal NetMessageType m_messageType;
|
||||
internal bool m_isSent;
|
||||
|
||||
// Recycling count is:
|
||||
// * incremented for each recipient on send
|
||||
// * incremented, when reliable, in SenderChannel.ExecuteSend()
|
||||
// * decremented (both reliable and unreliable) in NetConnection.QueueSendMessage()
|
||||
// * decremented, when reliable, in SenderChannel.DestoreMessage()
|
||||
// ... when it reaches zero it can be recycled
|
||||
internal int m_recyclingCount;
|
||||
|
||||
internal int m_fragmentGroup; // which group of fragments ths belongs to
|
||||
internal int m_fragmentGroupTotalBits; // total number of bits in this group
|
||||
internal int m_fragmentChunkByteSize; // size, in bytes, of every chunk but the last one
|
||||
internal int m_fragmentChunkNumber; // which number chunk this is, starting with 0
|
||||
|
||||
internal NetOutgoingMessage()
|
||||
{
|
||||
}
|
||||
|
||||
internal void Reset()
|
||||
{
|
||||
m_messageType = NetMessageType.LibraryError;
|
||||
m_bitLength = 0;
|
||||
m_isSent = false;
|
||||
NetException.Assert(m_recyclingCount == 0);
|
||||
m_fragmentGroup = 0;
|
||||
}
|
||||
|
||||
internal int Encode(byte[] intoBuffer, int ptr, int sequenceNumber)
|
||||
{
|
||||
// 8 bits - NetMessageType
|
||||
// 1 bit - Fragment?
|
||||
// 15 bits - Sequence number
|
||||
// 16 bits - Payload length in bits
|
||||
|
||||
intoBuffer[ptr++] = (byte)m_messageType;
|
||||
|
||||
byte low = (byte)((sequenceNumber << 1) | (m_fragmentGroup == 0 ? 0 : 1));
|
||||
intoBuffer[ptr++] = low;
|
||||
intoBuffer[ptr++] = (byte)(sequenceNumber >> 7);
|
||||
|
||||
if (m_fragmentGroup == 0)
|
||||
{
|
||||
intoBuffer[ptr++] = (byte)m_bitLength;
|
||||
intoBuffer[ptr++] = (byte)(m_bitLength >> 8);
|
||||
|
||||
int byteLen = NetUtility.BytesToHoldBits(m_bitLength);
|
||||
if (byteLen > 0)
|
||||
{
|
||||
Buffer.BlockCopy(m_data, 0, intoBuffer, ptr, byteLen);
|
||||
ptr += byteLen;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int wasPtr = ptr;
|
||||
intoBuffer[ptr++] = (byte)m_bitLength;
|
||||
intoBuffer[ptr++] = (byte)(m_bitLength >> 8);
|
||||
|
||||
//
|
||||
// write fragmentation header
|
||||
//
|
||||
ptr = NetFragmentationHelper.WriteHeader(intoBuffer, ptr, m_fragmentGroup, m_fragmentGroupTotalBits, m_fragmentChunkByteSize, m_fragmentChunkNumber);
|
||||
int hdrLen = ptr - wasPtr - 2;
|
||||
|
||||
// update length
|
||||
int realBitLength = m_bitLength + (hdrLen * 8);
|
||||
intoBuffer[wasPtr] = (byte)realBitLength;
|
||||
intoBuffer[wasPtr + 1] = (byte)(realBitLength >> 8);
|
||||
|
||||
int byteLen = NetUtility.BytesToHoldBits(m_bitLength);
|
||||
if (byteLen > 0)
|
||||
{
|
||||
Buffer.BlockCopy(m_data, (int)(m_fragmentChunkNumber * m_fragmentChunkByteSize), intoBuffer, ptr, byteLen);
|
||||
ptr += byteLen;
|
||||
}
|
||||
}
|
||||
|
||||
NetException.Assert(ptr > 0);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
internal int GetEncodedSize()
|
||||
{
|
||||
int retval = NetConstants.UnfragmentedMessageHeaderSize; // regular headers
|
||||
if (m_fragmentGroup != 0)
|
||||
retval += NetFragmentationHelper.GetFragmentationHeaderSize(m_fragmentGroup, m_fragmentGroupTotalBits / 8, m_fragmentChunkByteSize, m_fragmentChunkNumber);
|
||||
retval += this.LengthBytes;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt this message using the provided algorithm; no more writing can be done before sending it or the message will be corrupt!
|
||||
/// </summary>
|
||||
public bool Encrypt(NetEncryption encryption)
|
||||
{
|
||||
return encryption.Encrypt(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents this object
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
if (m_isSent)
|
||||
return "[NetOutgoingMessage " + m_messageType + " " + this.LengthBytes + " bytes]";
|
||||
|
||||
return "[NetOutgoingMessage " + this.LengthBytes + " bytes]";
|
||||
}
|
||||
}
|
||||
}
|
||||
69
SRMP/Lidgren.Network/NetPeer.Discovery.cs
Normal file
69
SRMP/Lidgren.Network/NetPeer.Discovery.cs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
|
||||
#if !__NOIPENDPOINT__
|
||||
using NetEndPoint = System.Net.IPEndPoint;
|
||||
#endif
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetPeer
|
||||
{
|
||||
/// <summary>
|
||||
/// Emit a discovery signal to all hosts on your subnet
|
||||
/// </summary>
|
||||
public void DiscoverLocalPeers(int serverPort)
|
||||
{
|
||||
NetOutgoingMessage um = CreateMessage(0);
|
||||
um.m_messageType = NetMessageType.Discovery;
|
||||
Interlocked.Increment(ref um.m_recyclingCount);
|
||||
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<NetEndPoint, NetOutgoingMessage>(new NetEndPoint(NetUtility.GetBroadcastAddress(), serverPort), um));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit a discovery signal to a single known host
|
||||
/// </summary>
|
||||
public bool DiscoverKnownPeer(string host, int serverPort)
|
||||
{
|
||||
var address = NetUtility.Resolve(host);
|
||||
if (address == null)
|
||||
return false;
|
||||
DiscoverKnownPeer(new NetEndPoint(address, serverPort));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit a discovery signal to a single known host
|
||||
/// </summary>
|
||||
public void DiscoverKnownPeer(NetEndPoint endPoint)
|
||||
{
|
||||
NetOutgoingMessage om = CreateMessage(0);
|
||||
om.m_messageType = NetMessageType.Discovery;
|
||||
om.m_recyclingCount = 1;
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<NetEndPoint, NetOutgoingMessage>(endPoint, om));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a discovery response message
|
||||
/// </summary>
|
||||
public void SendDiscoveryResponse(NetOutgoingMessage msg, NetEndPoint recipient)
|
||||
{
|
||||
if (recipient == null)
|
||||
throw new ArgumentNullException("recipient");
|
||||
|
||||
if (msg == null)
|
||||
msg = CreateMessage(0);
|
||||
else if (msg.m_isSent)
|
||||
throw new NetException("Message has already been sent!");
|
||||
|
||||
if (msg.LengthBytes >= m_configuration.MaximumTransmissionUnit)
|
||||
throw new NetException("Cannot send discovery message larger than MTU (currently " + m_configuration.MaximumTransmissionUnit + " bytes)");
|
||||
|
||||
msg.m_messageType = NetMessageType.DiscoveryResponse;
|
||||
Interlocked.Increment(ref msg.m_recyclingCount);
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<NetEndPoint, NetOutgoingMessage>(recipient, msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
169
SRMP/Lidgren.Network/NetPeer.Fragmentation.cs
Normal file
169
SRMP/Lidgren.Network/NetPeer.Fragmentation.cs
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal class ReceivedFragmentGroup
|
||||
{
|
||||
//public float LastReceived;
|
||||
public byte[] Data;
|
||||
public NetBitVector ReceivedChunks;
|
||||
}
|
||||
|
||||
public partial class NetPeer
|
||||
{
|
||||
private int m_lastUsedFragmentGroup;
|
||||
|
||||
private Dictionary<NetConnection, Dictionary<int, ReceivedFragmentGroup>> m_receivedFragmentGroups;
|
||||
|
||||
// on user thread
|
||||
private NetSendResult SendFragmentedMessage(NetOutgoingMessage msg, IList<NetConnection> recipients, NetDeliveryMethod method, int sequenceChannel)
|
||||
{
|
||||
// Note: this group id is PER SENDING/NetPeer; ie. same id is sent to all recipients;
|
||||
// this should be ok however; as long as recipients differentiate between same id but different sender
|
||||
int group = Interlocked.Increment(ref m_lastUsedFragmentGroup);
|
||||
if (group >= NetConstants.MaxFragmentationGroups)
|
||||
{
|
||||
// @TODO: not thread safe; but in practice probably not an issue
|
||||
m_lastUsedFragmentGroup = 1;
|
||||
group = 1;
|
||||
}
|
||||
msg.m_fragmentGroup = group;
|
||||
|
||||
// do not send msg; but set fragmentgroup in case user tries to recycle it immediately
|
||||
|
||||
// create fragmentation specifics
|
||||
int totalBytes = msg.LengthBytes;
|
||||
|
||||
// determine minimum mtu for all recipients
|
||||
int mtu = GetMTU(recipients);
|
||||
int bytesPerChunk = NetFragmentationHelper.GetBestChunkSize(group, totalBytes, mtu);
|
||||
|
||||
int numChunks = totalBytes / bytesPerChunk;
|
||||
if (numChunks * bytesPerChunk < totalBytes)
|
||||
numChunks++;
|
||||
|
||||
NetSendResult retval = NetSendResult.Sent;
|
||||
|
||||
int bitsPerChunk = bytesPerChunk * 8;
|
||||
int bitsLeft = msg.LengthBits;
|
||||
for (int i = 0; i < numChunks; i++)
|
||||
{
|
||||
NetOutgoingMessage chunk = CreateMessage(0);
|
||||
|
||||
chunk.m_bitLength = (bitsLeft > bitsPerChunk ? bitsPerChunk : bitsLeft);
|
||||
chunk.m_data = msg.m_data;
|
||||
chunk.m_fragmentGroup = group;
|
||||
chunk.m_fragmentGroupTotalBits = totalBytes * 8;
|
||||
chunk.m_fragmentChunkByteSize = bytesPerChunk;
|
||||
chunk.m_fragmentChunkNumber = i;
|
||||
|
||||
NetException.Assert(chunk.m_bitLength != 0);
|
||||
NetException.Assert(chunk.GetEncodedSize() < mtu);
|
||||
|
||||
Interlocked.Add(ref chunk.m_recyclingCount, recipients.Count);
|
||||
|
||||
foreach (NetConnection recipient in recipients)
|
||||
{
|
||||
var res = recipient.EnqueueMessage(chunk, method, sequenceChannel);
|
||||
if (res == NetSendResult.Dropped)
|
||||
Interlocked.Decrement(ref chunk.m_recyclingCount);
|
||||
if ((int)res > (int)retval)
|
||||
retval = res; // return "worst" result
|
||||
}
|
||||
|
||||
bitsLeft -= bitsPerChunk;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
private void HandleReleasedFragment(NetIncomingMessage im)
|
||||
{
|
||||
VerifyNetworkThread();
|
||||
|
||||
//
|
||||
// read fragmentation header and combine fragments
|
||||
//
|
||||
int group;
|
||||
int totalBits;
|
||||
int chunkByteSize;
|
||||
int chunkNumber;
|
||||
int ptr = NetFragmentationHelper.ReadHeader(
|
||||
im.m_data, 0,
|
||||
out group,
|
||||
out totalBits,
|
||||
out chunkByteSize,
|
||||
out chunkNumber
|
||||
);
|
||||
|
||||
NetException.Assert(im.LengthBytes > ptr);
|
||||
|
||||
NetException.Assert(group > 0);
|
||||
NetException.Assert(totalBits > 0);
|
||||
NetException.Assert(chunkByteSize > 0);
|
||||
|
||||
int totalBytes = NetUtility.BytesToHoldBits((int)totalBits);
|
||||
int totalNumChunks = totalBytes / chunkByteSize;
|
||||
if (totalNumChunks * chunkByteSize < totalBytes)
|
||||
totalNumChunks++;
|
||||
|
||||
NetException.Assert(chunkNumber < totalNumChunks);
|
||||
|
||||
if (chunkNumber >= totalNumChunks)
|
||||
{
|
||||
LogWarning("Index out of bounds for chunk " + chunkNumber + " (total chunks " + totalNumChunks + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<int, ReceivedFragmentGroup> groups;
|
||||
if (!m_receivedFragmentGroups.TryGetValue(im.SenderConnection, out groups))
|
||||
{
|
||||
groups = new Dictionary<int, ReceivedFragmentGroup>();
|
||||
m_receivedFragmentGroups[im.SenderConnection] = groups;
|
||||
}
|
||||
|
||||
ReceivedFragmentGroup info;
|
||||
if (!groups.TryGetValue(group, out info))
|
||||
{
|
||||
info = new ReceivedFragmentGroup();
|
||||
info.Data = new byte[totalBytes];
|
||||
info.ReceivedChunks = new NetBitVector(totalNumChunks);
|
||||
groups[group] = info;
|
||||
}
|
||||
|
||||
info.ReceivedChunks[chunkNumber] = true;
|
||||
//info.LastReceived = (float)NetTime.Now;
|
||||
|
||||
// copy to data
|
||||
int offset = (chunkNumber * chunkByteSize);
|
||||
Buffer.BlockCopy(im.m_data, ptr, info.Data, offset, im.LengthBytes - ptr);
|
||||
|
||||
int cnt = info.ReceivedChunks.Count();
|
||||
//LogVerbose("Found fragment #" + chunkNumber + " in group " + group + " offset " + offset + " of total bits " + totalBits + " (total chunks done " + cnt + ")");
|
||||
|
||||
LogVerbose("Received fragment " + chunkNumber + " of " + totalNumChunks + " (" + cnt + " chunks received)");
|
||||
|
||||
if (info.ReceivedChunks.Count() == totalNumChunks)
|
||||
{
|
||||
// Done! Transform this incoming message
|
||||
im.m_data = info.Data;
|
||||
im.m_bitLength = (int)totalBits;
|
||||
im.m_isFragment = false;
|
||||
|
||||
LogVerbose("Fragment group #" + group + " fully received in " + totalNumChunks + " chunks (" + totalBits + " bits)");
|
||||
groups.Remove(group);
|
||||
|
||||
ReleaseMessage(im);
|
||||
}
|
||||
else
|
||||
{
|
||||
// data has been copied; recycle this incoming message
|
||||
Recycle(im);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
761
SRMP/Lidgren.Network/NetPeer.Internal.cs
Normal file
761
SRMP/Lidgren.Network/NetPeer.Internal.cs
Normal file
|
|
@ -0,0 +1,761 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Net.Sockets;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if !__NOIPENDPOINT__
|
||||
using NetEndPoint = System.Net.IPEndPoint;
|
||||
#endif
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetPeer
|
||||
{
|
||||
private NetPeerStatus m_status;
|
||||
private Thread m_networkThread;
|
||||
private Socket m_socket;
|
||||
internal byte[] m_sendBuffer;
|
||||
internal byte[] m_receiveBuffer;
|
||||
internal NetIncomingMessage m_readHelperMessage;
|
||||
private EndPoint m_senderRemote;
|
||||
private object m_initializeLock = new object();
|
||||
private uint m_frameCounter;
|
||||
private double m_lastHeartbeat;
|
||||
private double m_lastSocketBind = float.MinValue;
|
||||
private NetUPnP m_upnp;
|
||||
internal bool m_needFlushSendQueue;
|
||||
|
||||
internal readonly NetPeerConfiguration m_configuration;
|
||||
private readonly NetQueue<NetIncomingMessage> m_releasedIncomingMessages;
|
||||
internal readonly NetQueue<NetTuple<NetEndPoint, NetOutgoingMessage>> m_unsentUnconnectedMessages;
|
||||
|
||||
internal Dictionary<NetEndPoint, NetConnection> m_handshakes;
|
||||
|
||||
internal readonly NetPeerStatistics m_statistics;
|
||||
internal long m_uniqueIdentifier;
|
||||
internal bool m_executeFlushSendQueue;
|
||||
|
||||
private AutoResetEvent m_messageReceivedEvent;
|
||||
private List<NetTuple<SynchronizationContext, SendOrPostCallback>> m_receiveCallbacks;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the socket, if Start() has been called
|
||||
/// </summary>
|
||||
public Socket Socket { get { return m_socket; } }
|
||||
|
||||
/// <summary>
|
||||
/// Call this to register a callback for when a new message arrives
|
||||
/// </summary>
|
||||
public void RegisterReceivedCallback(SendOrPostCallback callback, SynchronizationContext syncContext = null)
|
||||
{
|
||||
if (syncContext == null)
|
||||
syncContext = SynchronizationContext.Current;
|
||||
if (syncContext == null)
|
||||
throw new NetException("Need a SynchronizationContext to register callback on correct thread!");
|
||||
if (m_receiveCallbacks == null)
|
||||
m_receiveCallbacks = new List<NetTuple<SynchronizationContext, SendOrPostCallback>>();
|
||||
m_receiveCallbacks.Add(new NetTuple<SynchronizationContext, SendOrPostCallback>(syncContext, callback));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this to unregister a callback, but remember to do it in the same synchronization context!
|
||||
/// </summary>
|
||||
public void UnregisterReceivedCallback(SendOrPostCallback callback)
|
||||
{
|
||||
if (m_receiveCallbacks == null)
|
||||
return;
|
||||
|
||||
// remove all callbacks regardless of sync context
|
||||
m_receiveCallbacks.RemoveAll(tuple => tuple.Item2.Equals(callback));
|
||||
|
||||
if (m_receiveCallbacks.Count < 1)
|
||||
m_receiveCallbacks = null;
|
||||
}
|
||||
|
||||
internal void ReleaseMessage(NetIncomingMessage msg)
|
||||
{
|
||||
NetException.Assert(msg.m_incomingMessageType != NetIncomingMessageType.Error);
|
||||
|
||||
if (msg.m_isFragment)
|
||||
{
|
||||
HandleReleasedFragment(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
m_releasedIncomingMessages.Enqueue(msg);
|
||||
|
||||
if (m_messageReceivedEvent != null)
|
||||
m_messageReceivedEvent.Set();
|
||||
|
||||
if (m_receiveCallbacks != null)
|
||||
{
|
||||
foreach (var tuple in m_receiveCallbacks)
|
||||
{
|
||||
try
|
||||
{
|
||||
tuple.Item1.Post(tuple.Item2, this);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogWarning("Receive callback exception:" + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BindSocket(bool reBind)
|
||||
{
|
||||
double now = NetTime.Now;
|
||||
if (now - m_lastSocketBind < 1.0)
|
||||
{
|
||||
LogDebug("Suppressed socket rebind; last bound " + (now - m_lastSocketBind) + " seconds ago");
|
||||
return; // only allow rebind once every second
|
||||
}
|
||||
m_lastSocketBind = now;
|
||||
|
||||
using (var mutex = new Mutex(false, "Global\\lidgrenSocketBind"))
|
||||
{
|
||||
try
|
||||
{
|
||||
mutex.WaitOne();
|
||||
|
||||
if (m_socket == null)
|
||||
m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
|
||||
if (reBind)
|
||||
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, (int)1);
|
||||
|
||||
m_socket.ReceiveBufferSize = m_configuration.ReceiveBufferSize;
|
||||
m_socket.SendBufferSize = m_configuration.SendBufferSize;
|
||||
m_socket.Blocking = false;
|
||||
|
||||
var ep = (EndPoint)new NetEndPoint(m_configuration.LocalAddress, reBind ? m_listenPort : m_configuration.Port);
|
||||
m_socket.Bind(ep);
|
||||
|
||||
try
|
||||
{
|
||||
const uint IOC_IN = 0x80000000;
|
||||
const uint IOC_VENDOR = 0x18000000;
|
||||
uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;
|
||||
m_socket.IOControl((int)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore; SIO_UDP_CONNRESET not supported on this platform
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
mutex.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
|
||||
var boundEp = m_socket.LocalEndPoint as NetEndPoint;
|
||||
LogDebug("Socket bound to " + boundEp + ": " + m_socket.IsBound);
|
||||
m_listenPort = boundEp.Port;
|
||||
}
|
||||
|
||||
private void InitializeNetwork()
|
||||
{
|
||||
lock (m_initializeLock)
|
||||
{
|
||||
m_configuration.Lock();
|
||||
|
||||
if (m_status == NetPeerStatus.Running)
|
||||
return;
|
||||
|
||||
if (m_configuration.m_enableUPnP)
|
||||
m_upnp = new NetUPnP(this);
|
||||
|
||||
InitializePools();
|
||||
|
||||
m_releasedIncomingMessages.Clear();
|
||||
m_unsentUnconnectedMessages.Clear();
|
||||
m_handshakes.Clear();
|
||||
|
||||
// bind to socket
|
||||
BindSocket(false);
|
||||
|
||||
m_receiveBuffer = new byte[m_configuration.ReceiveBufferSize];
|
||||
m_sendBuffer = new byte[m_configuration.SendBufferSize];
|
||||
m_readHelperMessage = new NetIncomingMessage(NetIncomingMessageType.Error);
|
||||
m_readHelperMessage.m_data = m_receiveBuffer;
|
||||
|
||||
byte[] macBytes = NetUtility.GetMacAddressBytes();
|
||||
|
||||
var boundEp = m_socket.LocalEndPoint as NetEndPoint;
|
||||
byte[] epBytes = BitConverter.GetBytes(boundEp.GetHashCode());
|
||||
byte[] combined = new byte[epBytes.Length + macBytes.Length];
|
||||
Array.Copy(epBytes, 0, combined, 0, epBytes.Length);
|
||||
Array.Copy(macBytes, 0, combined, epBytes.Length, macBytes.Length);
|
||||
m_uniqueIdentifier = BitConverter.ToInt64(NetUtility.ComputeSHAHash(combined), 0);
|
||||
|
||||
m_status = NetPeerStatus.Running;
|
||||
}
|
||||
}
|
||||
|
||||
private void NetworkLoop()
|
||||
{
|
||||
VerifyNetworkThread();
|
||||
|
||||
LogDebug("Network thread started");
|
||||
|
||||
//
|
||||
// Network loop
|
||||
//
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
Heartbeat();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogWarning(ex.ToString());
|
||||
}
|
||||
} while (m_status == NetPeerStatus.Running);
|
||||
|
||||
//
|
||||
// perform shutdown
|
||||
//
|
||||
ExecutePeerShutdown();
|
||||
}
|
||||
|
||||
private void ExecutePeerShutdown()
|
||||
{
|
||||
VerifyNetworkThread();
|
||||
|
||||
LogDebug("Shutting down...");
|
||||
|
||||
// disconnect and make one final heartbeat
|
||||
var list = new List<NetConnection>(m_handshakes.Count + m_connections.Count);
|
||||
lock (m_connections)
|
||||
{
|
||||
foreach (var conn in m_connections)
|
||||
if (conn != null)
|
||||
list.Add(conn);
|
||||
}
|
||||
|
||||
lock (m_handshakes)
|
||||
{
|
||||
foreach (var hs in m_handshakes.Values)
|
||||
if (hs != null && list.Contains(hs) == false)
|
||||
list.Add(hs);
|
||||
}
|
||||
|
||||
// shut down connections
|
||||
foreach (NetConnection conn in list)
|
||||
conn.Shutdown(m_shutdownReason);
|
||||
|
||||
FlushDelayedPackets();
|
||||
|
||||
// one final heartbeat, will send stuff and do disconnect
|
||||
Heartbeat();
|
||||
|
||||
NetUtility.Sleep(10);
|
||||
|
||||
lock (m_initializeLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (m_socket != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_socket.Shutdown(SocketShutdown.Receive);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
LogDebug("Socket.Shutdown exception: " + ex.ToString());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
m_socket.Close(2); // 2 seconds timeout
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogDebug("Socket.Close exception: " + ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_socket = null;
|
||||
m_status = NetPeerStatus.NotRunning;
|
||||
LogDebug("Shutdown complete");
|
||||
|
||||
// wake up any threads waiting for server shutdown
|
||||
if (m_messageReceivedEvent != null)
|
||||
m_messageReceivedEvent.Set();
|
||||
}
|
||||
|
||||
m_lastSocketBind = float.MinValue;
|
||||
m_receiveBuffer = null;
|
||||
m_sendBuffer = null;
|
||||
m_unsentUnconnectedMessages.Clear();
|
||||
m_connections.Clear();
|
||||
m_connectionLookup.Clear();
|
||||
m_handshakes.Clear();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private void Heartbeat()
|
||||
{
|
||||
VerifyNetworkThread();
|
||||
|
||||
double now = NetTime.Now;
|
||||
double delta = now - m_lastHeartbeat;
|
||||
|
||||
int maxCHBpS = 1250 - m_connections.Count;
|
||||
if (maxCHBpS < 250)
|
||||
maxCHBpS = 250;
|
||||
if (delta > (1.0 / (double)maxCHBpS) || delta < 0.0) // max connection heartbeats/second max
|
||||
{
|
||||
m_frameCounter++;
|
||||
m_lastHeartbeat = now;
|
||||
|
||||
// do handshake heartbeats
|
||||
if ((m_frameCounter % 3) == 0)
|
||||
{
|
||||
foreach (var kvp in m_handshakes)
|
||||
{
|
||||
NetConnection conn = kvp.Value as NetConnection;
|
||||
#if DEBUG
|
||||
// sanity check
|
||||
if (kvp.Key != kvp.Key)
|
||||
LogWarning("Sanity fail! Connection in handshake list under wrong key!");
|
||||
#endif
|
||||
conn.UnconnectedHeartbeat(now);
|
||||
if (conn.m_status == NetConnectionStatus.Connected || conn.m_status == NetConnectionStatus.Disconnected)
|
||||
{
|
||||
#if DEBUG
|
||||
// sanity check
|
||||
if (conn.m_status == NetConnectionStatus.Disconnected && m_handshakes.ContainsKey(conn.RemoteEndPoint))
|
||||
{
|
||||
LogWarning("Sanity fail! Handshakes list contained disconnected connection!");
|
||||
m_handshakes.Remove(conn.RemoteEndPoint);
|
||||
}
|
||||
#endif
|
||||
break; // collection has been modified
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
SendDelayedPackets();
|
||||
#endif
|
||||
|
||||
// update m_executeFlushSendQueue
|
||||
if (m_configuration.m_autoFlushSendQueue && m_needFlushSendQueue == true)
|
||||
{
|
||||
m_executeFlushSendQueue = true;
|
||||
m_needFlushSendQueue = false; // a race condition to this variable will simply result in a single superfluous call to FlushSendQueue()
|
||||
}
|
||||
|
||||
// do connection heartbeats
|
||||
lock (m_connections)
|
||||
{
|
||||
for (int i = m_connections.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var conn = m_connections[i];
|
||||
conn.Heartbeat(now, m_frameCounter);
|
||||
if (conn.m_status == NetConnectionStatus.Disconnected)
|
||||
{
|
||||
//
|
||||
// remove connection
|
||||
//
|
||||
m_connections.RemoveAt(i);
|
||||
m_connectionLookup.Remove(conn.RemoteEndPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_executeFlushSendQueue = false;
|
||||
|
||||
// send unsent unconnected messages
|
||||
NetTuple<NetEndPoint, NetOutgoingMessage> unsent;
|
||||
while (m_unsentUnconnectedMessages.TryDequeue(out unsent))
|
||||
{
|
||||
NetOutgoingMessage om = unsent.Item2;
|
||||
|
||||
int len = om.Encode(m_sendBuffer, 0, 0);
|
||||
|
||||
Interlocked.Decrement(ref om.m_recyclingCount);
|
||||
if (om.m_recyclingCount <= 0)
|
||||
Recycle(om);
|
||||
|
||||
bool connReset;
|
||||
SendPacket(len, unsent.Item1, 1, out connReset);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_upnp != null)
|
||||
m_upnp.CheckForDiscoveryTimeout();
|
||||
|
||||
//
|
||||
// read from socket
|
||||
//
|
||||
if (m_socket == null)
|
||||
return;
|
||||
|
||||
if (!m_socket.Poll(1000, SelectMode.SelectRead)) // wait up to 1 ms for data to arrive
|
||||
return;
|
||||
|
||||
//if (m_socket == null || m_socket.Available < 1)
|
||||
// return;
|
||||
|
||||
// update now
|
||||
now = NetTime.Now;
|
||||
|
||||
do
|
||||
{
|
||||
int bytesReceived = 0;
|
||||
try
|
||||
{
|
||||
bytesReceived = m_socket.ReceiveFrom(m_receiveBuffer, 0, m_receiveBuffer.Length, SocketFlags.None, ref m_senderRemote);
|
||||
}
|
||||
catch (SocketException sx)
|
||||
{
|
||||
switch (sx.SocketErrorCode)
|
||||
{
|
||||
case SocketError.ConnectionReset:
|
||||
// connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable"
|
||||
// we should shut down the connection; but m_senderRemote seemingly cannot be trusted, so which connection should we shut down?!
|
||||
// So, what to do?
|
||||
LogWarning("ConnectionReset");
|
||||
return;
|
||||
|
||||
case SocketError.NotConnected:
|
||||
// socket is unbound; try to rebind it (happens on mobile when process goes to sleep)
|
||||
BindSocket(true);
|
||||
return;
|
||||
|
||||
default:
|
||||
LogWarning("Socket exception: " + sx.ToString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (bytesReceived < NetConstants.HeaderByteSize)
|
||||
return;
|
||||
|
||||
//LogVerbose("Received " + bytesReceived + " bytes");
|
||||
|
||||
var ipsender = (NetEndPoint)m_senderRemote;
|
||||
|
||||
if (m_upnp != null && now < m_upnp.m_discoveryResponseDeadline && bytesReceived > 32)
|
||||
{
|
||||
// is this an UPnP response?
|
||||
string resp = System.Text.Encoding.UTF8.GetString(m_receiveBuffer, 0, bytesReceived);
|
||||
if (resp.Contains("upnp:rootdevice") || resp.Contains("UPnP/1.0"))
|
||||
{
|
||||
try
|
||||
{
|
||||
resp = resp.Substring(resp.ToLower().IndexOf("location:") + 9);
|
||||
resp = resp.Substring(0, resp.IndexOf("\r")).Trim();
|
||||
m_upnp.ExtractServiceUrl(resp);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogDebug("Failed to parse UPnP response: " + ex.ToString());
|
||||
|
||||
// don't try to parse this packet further
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NetConnection sender = null;
|
||||
m_connectionLookup.TryGetValue(ipsender, out sender);
|
||||
|
||||
//
|
||||
// parse packet into messages
|
||||
//
|
||||
int numMessages = 0;
|
||||
int numFragments = 0;
|
||||
int ptr = 0;
|
||||
while ((bytesReceived - ptr) >= NetConstants.HeaderByteSize)
|
||||
{
|
||||
// decode header
|
||||
// 8 bits - NetMessageType
|
||||
// 1 bit - Fragment?
|
||||
// 15 bits - Sequence number
|
||||
// 16 bits - Payload length in bits
|
||||
|
||||
numMessages++;
|
||||
|
||||
NetMessageType tp = (NetMessageType)m_receiveBuffer[ptr++];
|
||||
|
||||
byte low = m_receiveBuffer[ptr++];
|
||||
byte high = m_receiveBuffer[ptr++];
|
||||
|
||||
bool isFragment = ((low & 1) == 1);
|
||||
ushort sequenceNumber = (ushort)((low >> 1) | (((int)high) << 7));
|
||||
|
||||
if (isFragment)
|
||||
numFragments++;
|
||||
|
||||
ushort payloadBitLength = (ushort)(m_receiveBuffer[ptr++] | (m_receiveBuffer[ptr++] << 8));
|
||||
int payloadByteLength = NetUtility.BytesToHoldBits(payloadBitLength);
|
||||
|
||||
if (bytesReceived - ptr < payloadByteLength)
|
||||
{
|
||||
LogWarning("Malformed packet; stated payload length " + payloadByteLength + ", remaining bytes " + (bytesReceived - ptr));
|
||||
return;
|
||||
}
|
||||
|
||||
if (tp >= NetMessageType.Unused1 && tp <= NetMessageType.Unused29)
|
||||
{
|
||||
ThrowOrLog("Unexpected NetMessageType: " + tp);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (tp >= NetMessageType.LibraryError)
|
||||
{
|
||||
if (sender != null)
|
||||
sender.ReceivedLibraryMessage(tp, ptr, payloadByteLength);
|
||||
else
|
||||
ReceivedUnconnectedLibraryMessage(now, ipsender, tp, ptr, payloadByteLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sender == null && !m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData))
|
||||
return; // dropping unconnected message since it's not enabled
|
||||
|
||||
NetIncomingMessage msg = CreateIncomingMessage(NetIncomingMessageType.Data, payloadByteLength);
|
||||
msg.m_isFragment = isFragment;
|
||||
msg.m_receiveTime = now;
|
||||
msg.m_sequenceNumber = sequenceNumber;
|
||||
msg.m_receivedMessageType = tp;
|
||||
msg.m_senderConnection = sender;
|
||||
msg.m_senderEndPoint = ipsender;
|
||||
msg.m_bitLength = payloadBitLength;
|
||||
|
||||
Buffer.BlockCopy(m_receiveBuffer, ptr, msg.m_data, 0, payloadByteLength);
|
||||
if (sender != null)
|
||||
{
|
||||
if (tp == NetMessageType.Unconnected)
|
||||
{
|
||||
// We're connected; but we can still send unconnected messages to this peer
|
||||
msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData;
|
||||
ReleaseMessage(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
// connected application (non-library) message
|
||||
sender.ReceivedMessage(msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// at this point we know the message type is enabled
|
||||
// unconnected application (non-library) message
|
||||
msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData;
|
||||
ReleaseMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError("Packet parsing error: " + ex.Message + " from " + ipsender);
|
||||
}
|
||||
ptr += payloadByteLength;
|
||||
}
|
||||
|
||||
m_statistics.PacketReceived(bytesReceived, numMessages, numFragments);
|
||||
if (sender != null)
|
||||
sender.m_statistics.PacketReceived(bytesReceived, numMessages, numFragments);
|
||||
|
||||
} while (m_socket.Available > 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If NetPeerConfiguration.AutoFlushSendQueue() is false; you need to call this to send all messages queued using SendMessage()
|
||||
/// </summary>
|
||||
public void FlushSendQueue()
|
||||
{
|
||||
m_executeFlushSendQueue = true;
|
||||
}
|
||||
|
||||
internal void HandleIncomingDiscoveryRequest(double now, NetEndPoint senderEndPoint, int ptr, int payloadByteLength)
|
||||
{
|
||||
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DiscoveryRequest))
|
||||
{
|
||||
NetIncomingMessage dm = CreateIncomingMessage(NetIncomingMessageType.DiscoveryRequest, payloadByteLength);
|
||||
if (payloadByteLength > 0)
|
||||
Buffer.BlockCopy(m_receiveBuffer, ptr, dm.m_data, 0, payloadByteLength);
|
||||
dm.m_receiveTime = now;
|
||||
dm.m_bitLength = payloadByteLength * 8;
|
||||
dm.m_senderEndPoint = senderEndPoint;
|
||||
ReleaseMessage(dm);
|
||||
}
|
||||
}
|
||||
|
||||
internal void HandleIncomingDiscoveryResponse(double now, NetEndPoint senderEndPoint, int ptr, int payloadByteLength)
|
||||
{
|
||||
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DiscoveryResponse))
|
||||
{
|
||||
NetIncomingMessage dr = CreateIncomingMessage(NetIncomingMessageType.DiscoveryResponse, payloadByteLength);
|
||||
if (payloadByteLength > 0)
|
||||
Buffer.BlockCopy(m_receiveBuffer, ptr, dr.m_data, 0, payloadByteLength);
|
||||
dr.m_receiveTime = now;
|
||||
dr.m_bitLength = payloadByteLength * 8;
|
||||
dr.m_senderEndPoint = senderEndPoint;
|
||||
ReleaseMessage(dr);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReceivedUnconnectedLibraryMessage(double now, NetEndPoint senderEndPoint, NetMessageType tp, int ptr, int payloadByteLength)
|
||||
{
|
||||
NetConnection shake;
|
||||
if (m_handshakes.TryGetValue(senderEndPoint, out shake))
|
||||
{
|
||||
shake.ReceivedHandshake(now, tp, ptr, payloadByteLength);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Library message from a completely unknown sender; lets just accept Connect
|
||||
//
|
||||
switch (tp)
|
||||
{
|
||||
case NetMessageType.Discovery:
|
||||
HandleIncomingDiscoveryRequest(now, senderEndPoint, ptr, payloadByteLength);
|
||||
return;
|
||||
case NetMessageType.DiscoveryResponse:
|
||||
HandleIncomingDiscoveryResponse(now, senderEndPoint, ptr, payloadByteLength);
|
||||
return;
|
||||
case NetMessageType.NatIntroduction:
|
||||
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess))
|
||||
HandleNatIntroduction(ptr);
|
||||
return;
|
||||
case NetMessageType.NatPunchMessage:
|
||||
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess))
|
||||
HandleNatPunch(ptr, senderEndPoint);
|
||||
return;
|
||||
case NetMessageType.NatIntroductionConfirmRequest:
|
||||
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess))
|
||||
HandleNatPunchConfirmRequest(ptr, senderEndPoint);
|
||||
return;
|
||||
case NetMessageType.NatIntroductionConfirmed:
|
||||
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess))
|
||||
HandleNatPunchConfirmed(ptr, senderEndPoint);
|
||||
return;
|
||||
case NetMessageType.ConnectResponse:
|
||||
|
||||
lock (m_handshakes)
|
||||
{
|
||||
foreach (var hs in m_handshakes)
|
||||
{
|
||||
if (hs.Key.Address.Equals(senderEndPoint.Address))
|
||||
{
|
||||
if (hs.Value.m_connectionInitiator)
|
||||
{
|
||||
//
|
||||
// We are currently trying to connection to XX.XX.XX.XX:Y
|
||||
// ... but we just received a ConnectResponse from XX.XX.XX.XX:Z
|
||||
// Lets just assume the router decided to use this port instead
|
||||
//
|
||||
var hsconn = hs.Value;
|
||||
m_connectionLookup.Remove(hs.Key);
|
||||
m_handshakes.Remove(hs.Key);
|
||||
|
||||
LogDebug("Detected host port change; rerouting connection to " + senderEndPoint);
|
||||
hsconn.MutateEndPoint(senderEndPoint);
|
||||
|
||||
m_connectionLookup.Add(senderEndPoint, hsconn);
|
||||
m_handshakes.Add(senderEndPoint, hsconn);
|
||||
|
||||
hsconn.ReceivedHandshake(now, tp, ptr, payloadByteLength);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogWarning("Received unhandled library message " + tp + " from " + senderEndPoint);
|
||||
return;
|
||||
case NetMessageType.Connect:
|
||||
if (m_configuration.AcceptIncomingConnections == false)
|
||||
{
|
||||
LogWarning(m_configuration.AppIdentifier + " Received Connect, but we're not accepting incoming connections!");
|
||||
return;
|
||||
}
|
||||
// handle connect
|
||||
// It's someone wanting to shake hands with us!
|
||||
|
||||
int reservedSlots = m_handshakes.Count + m_connections.Count;
|
||||
if (reservedSlots >= m_configuration.m_maximumConnections)
|
||||
{
|
||||
// server full
|
||||
NetOutgoingMessage full = CreateMessage("Server full");
|
||||
full.m_messageType = NetMessageType.Disconnect;
|
||||
SendLibrary(full, senderEndPoint);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ok, start handshake!
|
||||
NetConnection conn = new NetConnection(this, senderEndPoint);
|
||||
conn.m_status = NetConnectionStatus.ReceivedInitiation;
|
||||
m_handshakes.Add(senderEndPoint, conn);
|
||||
conn.ReceivedHandshake(now, tp, ptr, payloadByteLength);
|
||||
return;
|
||||
|
||||
case NetMessageType.Disconnect:
|
||||
// this is probably ok
|
||||
LogVerbose("Received Disconnect from unconnected source: " + senderEndPoint);
|
||||
return;
|
||||
default:
|
||||
LogWarning("Received unhandled library message " + tp + " from " + senderEndPoint);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
internal void AcceptConnection(NetConnection conn)
|
||||
{
|
||||
// LogDebug("Accepted connection " + conn);
|
||||
conn.InitExpandMTU(NetTime.Now);
|
||||
|
||||
if (m_handshakes.Remove(conn.m_remoteEndPoint) == false)
|
||||
LogWarning("AcceptConnection called but m_handshakes did not contain it!");
|
||||
|
||||
lock (m_connections)
|
||||
{
|
||||
if (m_connections.Contains(conn))
|
||||
{
|
||||
LogWarning("AcceptConnection called but m_connection already contains it!");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_connections.Add(conn);
|
||||
m_connectionLookup.Add(conn.m_remoteEndPoint, conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
internal void VerifyNetworkThread()
|
||||
{
|
||||
Thread ct = Thread.CurrentThread;
|
||||
if (Thread.CurrentThread != m_networkThread)
|
||||
throw new NetException("Executing on wrong thread! Should be library system thread (is " + ct.Name + " mId " + ct.ManagedThreadId + ")");
|
||||
}
|
||||
|
||||
internal NetIncomingMessage SetupReadHelperMessage(int ptr, int payloadLength)
|
||||
{
|
||||
VerifyNetworkThread();
|
||||
|
||||
m_readHelperMessage.m_bitLength = (ptr + payloadLength) * 8;
|
||||
m_readHelperMessage.m_readPosition = (ptr * 8);
|
||||
return m_readHelperMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
311
SRMP/Lidgren.Network/NetPeer.LatencySimulation.cs
Normal file
311
SRMP/Lidgren.Network/NetPeer.LatencySimulation.cs
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
//#define USE_RELEASE_STATISTICS
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Diagnostics;
|
||||
|
||||
#if !__NOIPENDPOINT__
|
||||
using NetEndPoint = System.Net.IPEndPoint;
|
||||
#endif
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetPeer
|
||||
{
|
||||
|
||||
#if DEBUG
|
||||
private readonly List<DelayedPacket> m_delayedPackets = new List<DelayedPacket>();
|
||||
|
||||
private class DelayedPacket
|
||||
{
|
||||
public byte[] Data;
|
||||
public double DelayedUntil;
|
||||
public NetEndPoint Target;
|
||||
}
|
||||
|
||||
internal void SendPacket(int numBytes, NetEndPoint target, int numMessages, out bool connectionReset)
|
||||
{
|
||||
connectionReset = false;
|
||||
|
||||
// simulate loss
|
||||
float loss = m_configuration.m_loss;
|
||||
if (loss > 0.0f)
|
||||
{
|
||||
if ((float)MWCRandom.Instance.NextDouble() < loss)
|
||||
{
|
||||
LogVerbose("Sending packet " + numBytes + " bytes - SIMULATED LOST!");
|
||||
return; // packet "lost"
|
||||
}
|
||||
}
|
||||
|
||||
m_statistics.PacketSent(numBytes, numMessages);
|
||||
|
||||
// simulate latency
|
||||
float m = m_configuration.m_minimumOneWayLatency;
|
||||
float r = m_configuration.m_randomOneWayLatency;
|
||||
if (m == 0.0f && r == 0.0f)
|
||||
{
|
||||
// no latency simulation
|
||||
// LogVerbose("Sending packet " + numBytes + " bytes");
|
||||
bool wasSent = ActuallySendPacket(m_sendBuffer, numBytes, target, out connectionReset);
|
||||
// TODO: handle wasSent == false?
|
||||
|
||||
if (m_configuration.m_duplicates > 0.0f && MWCRandom.Instance.NextDouble() < m_configuration.m_duplicates)
|
||||
ActuallySendPacket(m_sendBuffer, numBytes, target, out connectionReset); // send it again!
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int num = 1;
|
||||
if (m_configuration.m_duplicates > 0.0f && MWCRandom.Instance.NextSingle() < m_configuration.m_duplicates)
|
||||
num++;
|
||||
|
||||
float delay = 0;
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
delay = m_configuration.m_minimumOneWayLatency + (MWCRandom.Instance.NextSingle() * m_configuration.m_randomOneWayLatency);
|
||||
|
||||
// Enqueue delayed packet
|
||||
DelayedPacket p = new DelayedPacket();
|
||||
p.Target = target;
|
||||
p.Data = new byte[numBytes];
|
||||
Buffer.BlockCopy(m_sendBuffer, 0, p.Data, 0, numBytes);
|
||||
p.DelayedUntil = NetTime.Now + delay;
|
||||
|
||||
m_delayedPackets.Add(p);
|
||||
}
|
||||
|
||||
// LogVerbose("Sending packet " + numBytes + " bytes - delayed " + NetTime.ToReadable(delay));
|
||||
}
|
||||
|
||||
private void SendDelayedPackets()
|
||||
{
|
||||
if (m_delayedPackets.Count <= 0)
|
||||
return;
|
||||
|
||||
double now = NetTime.Now;
|
||||
|
||||
bool connectionReset;
|
||||
|
||||
RestartDelaySending:
|
||||
foreach (DelayedPacket p in m_delayedPackets)
|
||||
{
|
||||
if (now > p.DelayedUntil)
|
||||
{
|
||||
ActuallySendPacket(p.Data, p.Data.Length, p.Target, out connectionReset);
|
||||
m_delayedPackets.Remove(p);
|
||||
goto RestartDelaySending;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FlushDelayedPackets()
|
||||
{
|
||||
try
|
||||
{
|
||||
bool connectionReset;
|
||||
foreach (DelayedPacket p in m_delayedPackets)
|
||||
ActuallySendPacket(p.Data, p.Data.Length, p.Target, out connectionReset);
|
||||
m_delayedPackets.Clear();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
internal bool ActuallySendPacket(byte[] data, int numBytes, NetEndPoint target, out bool connectionReset)
|
||||
{
|
||||
connectionReset = false;
|
||||
IPAddress ba = default(IPAddress);
|
||||
try
|
||||
{
|
||||
ba = NetUtility.GetCachedBroadcastAddress();
|
||||
|
||||
// TODO: refactor this check outta here
|
||||
if (target.Address.Equals(ba))
|
||||
{
|
||||
// Some networks do not allow
|
||||
// a global broadcast so we use the BroadcastAddress from the configuration
|
||||
// this can be resolved to a local broadcast addresss e.g 192.168.x.255
|
||||
target.Address = m_configuration.BroadcastAddress;
|
||||
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
|
||||
}
|
||||
|
||||
int bytesSent = m_socket.SendTo(data, 0, numBytes, SocketFlags.None, target);
|
||||
if (numBytes != bytesSent)
|
||||
LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!");
|
||||
|
||||
// LogDebug("Sent " + numBytes + " bytes");
|
||||
}
|
||||
catch (SocketException sx)
|
||||
{
|
||||
if (sx.SocketErrorCode == SocketError.WouldBlock)
|
||||
{
|
||||
// send buffer full?
|
||||
LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration");
|
||||
return false;
|
||||
}
|
||||
if (sx.SocketErrorCode == SocketError.ConnectionReset)
|
||||
{
|
||||
// connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable"
|
||||
connectionReset = true;
|
||||
return false;
|
||||
}
|
||||
LogError("Failed to send packet: " + sx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError("Failed to send packet: " + ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (target.Address == ba)
|
||||
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool SendMTUPacket(int numBytes, NetEndPoint target)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_socket.DontFragment = true;
|
||||
int bytesSent = m_socket.SendTo(m_sendBuffer, 0, numBytes, SocketFlags.None, target);
|
||||
if (numBytes != bytesSent)
|
||||
LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!");
|
||||
|
||||
m_statistics.PacketSent(numBytes, 1);
|
||||
}
|
||||
catch (SocketException sx)
|
||||
{
|
||||
if (sx.SocketErrorCode == SocketError.MessageSize)
|
||||
return false;
|
||||
if (sx.SocketErrorCode == SocketError.WouldBlock)
|
||||
{
|
||||
// send buffer full?
|
||||
LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration");
|
||||
return true;
|
||||
}
|
||||
if (sx.SocketErrorCode == SocketError.ConnectionReset)
|
||||
return true;
|
||||
LogError("Failed to send packet: (" + sx.SocketErrorCode + ") " + sx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError("Failed to send packet: " + ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_socket.DontFragment = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
internal bool SendMTUPacket(int numBytes, NetEndPoint target)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_socket.DontFragment = true;
|
||||
int bytesSent = m_socket.SendTo(m_sendBuffer, 0, numBytes, SocketFlags.None, target);
|
||||
if (numBytes != bytesSent)
|
||||
LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!");
|
||||
}
|
||||
catch (SocketException sx)
|
||||
{
|
||||
if (sx.SocketErrorCode == SocketError.MessageSize)
|
||||
return false;
|
||||
if (sx.SocketErrorCode == SocketError.WouldBlock)
|
||||
{
|
||||
// send buffer full?
|
||||
LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration");
|
||||
return true;
|
||||
}
|
||||
if (sx.SocketErrorCode == SocketError.ConnectionReset)
|
||||
return true;
|
||||
LogError("Failed to send packet: (" + sx.SocketErrorCode + ") " + sx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError("Failed to send packet: " + ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_socket.DontFragment = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Release - just send the packet straight away
|
||||
//
|
||||
internal void SendPacket(int numBytes, NetEndPoint target, int numMessages, out bool connectionReset)
|
||||
{
|
||||
#if USE_RELEASE_STATISTICS
|
||||
m_statistics.PacketSent(numBytes, numMessages);
|
||||
#endif
|
||||
connectionReset = false;
|
||||
IPAddress ba = default(IPAddress);
|
||||
try
|
||||
{
|
||||
// TODO: refactor this check outta here
|
||||
ba = NetUtility.GetCachedBroadcastAddress();
|
||||
if (target.Address == ba)
|
||||
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
|
||||
|
||||
int bytesSent = m_socket.SendTo(m_sendBuffer, 0, numBytes, SocketFlags.None, target);
|
||||
if (numBytes != bytesSent)
|
||||
LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!");
|
||||
}
|
||||
catch (SocketException sx)
|
||||
{
|
||||
if (sx.SocketErrorCode == SocketError.WouldBlock)
|
||||
{
|
||||
// send buffer full?
|
||||
LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration");
|
||||
return;
|
||||
}
|
||||
if (sx.SocketErrorCode == SocketError.ConnectionReset)
|
||||
{
|
||||
// connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable"
|
||||
connectionReset = true;
|
||||
return;
|
||||
}
|
||||
LogError("Failed to send packet: " + sx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError("Failed to send packet: " + ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (target.Address == ba)
|
||||
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private void FlushDelayedPackets()
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
63
SRMP/Lidgren.Network/NetPeer.Logging.cs
Normal file
63
SRMP/Lidgren.Network/NetPeer.Logging.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetPeer
|
||||
{
|
||||
[Conditional("DEBUG")]
|
||||
internal void LogVerbose(string message)
|
||||
{
|
||||
#if __ANDROID__
|
||||
Android.Util.Log.WriteLine(Android.Util.LogPriority.Verbose, "", message);
|
||||
#endif
|
||||
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.VerboseDebugMessage))
|
||||
ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.VerboseDebugMessage, message));
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
internal void LogDebug(string message)
|
||||
{
|
||||
#if __ANDROID__
|
||||
Android.Util.Log.WriteLine(Android.Util.LogPriority.Debug, "", message);
|
||||
#endif
|
||||
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DebugMessage))
|
||||
ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.DebugMessage, message));
|
||||
}
|
||||
|
||||
internal void LogWarning(string message)
|
||||
{
|
||||
#if __ANDROID__
|
||||
Android.Util.Log.WriteLine(Android.Util.LogPriority.Warn, "", message);
|
||||
#endif
|
||||
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.WarningMessage))
|
||||
ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.WarningMessage, message));
|
||||
}
|
||||
|
||||
internal void LogError(string message)
|
||||
{
|
||||
#if __ANDROID__
|
||||
Android.Util.Log.WriteLine(Android.Util.LogPriority.Error, "", message);
|
||||
#endif
|
||||
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.ErrorMessage))
|
||||
ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.ErrorMessage, message));
|
||||
}
|
||||
}
|
||||
}
|
||||
243
SRMP/Lidgren.Network/NetPeer.MessagePools.cs
Normal file
243
SRMP/Lidgren.Network/NetPeer.MessagePools.cs
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetPeer
|
||||
{
|
||||
internal List<byte[]> m_storagePool;
|
||||
private NetQueue<NetOutgoingMessage> m_outgoingMessagesPool;
|
||||
private NetQueue<NetIncomingMessage> m_incomingMessagesPool;
|
||||
|
||||
internal int m_storagePoolBytes;
|
||||
internal int m_storageSlotsUsedCount;
|
||||
private int m_maxCacheCount;
|
||||
|
||||
private void InitializePools()
|
||||
{
|
||||
m_storageSlotsUsedCount = 0;
|
||||
|
||||
if (m_configuration.UseMessageRecycling)
|
||||
{
|
||||
m_storagePool = new List<byte[]>(16);
|
||||
m_outgoingMessagesPool = new NetQueue<NetOutgoingMessage>(4);
|
||||
m_incomingMessagesPool = new NetQueue<NetIncomingMessage>(4);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_storagePool = null;
|
||||
m_outgoingMessagesPool = null;
|
||||
m_incomingMessagesPool = null;
|
||||
}
|
||||
|
||||
m_maxCacheCount = m_configuration.RecycledCacheMaxCount;
|
||||
}
|
||||
|
||||
internal byte[] GetStorage(int minimumCapacityInBytes)
|
||||
{
|
||||
if (m_storagePool == null)
|
||||
return new byte[minimumCapacityInBytes];
|
||||
|
||||
lock (m_storagePool)
|
||||
{
|
||||
for (int i = 0; i < m_storagePool.Count; i++)
|
||||
{
|
||||
byte[] retval = m_storagePool[i];
|
||||
if (retval != null && retval.Length >= minimumCapacityInBytes)
|
||||
{
|
||||
m_storagePool[i] = null;
|
||||
m_storageSlotsUsedCount--;
|
||||
m_storagePoolBytes -= retval.Length;
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_statistics.m_bytesAllocated += minimumCapacityInBytes;
|
||||
return new byte[minimumCapacityInBytes];
|
||||
}
|
||||
|
||||
internal void Recycle(byte[] storage)
|
||||
{
|
||||
if (m_storagePool == null || storage == null)
|
||||
return;
|
||||
|
||||
lock (m_storagePool)
|
||||
{
|
||||
int cnt = m_storagePool.Count;
|
||||
for (int i = 0; i < cnt; i++)
|
||||
{
|
||||
if (m_storagePool[i] == null)
|
||||
{
|
||||
m_storageSlotsUsedCount++;
|
||||
m_storagePoolBytes += storage.Length;
|
||||
m_storagePool[i] = storage;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_storagePool.Count >= m_maxCacheCount)
|
||||
{
|
||||
// pool is full; replace randomly chosen entry to keep size distribution
|
||||
var idx = NetRandom.Instance.Next(m_storagePool.Count);
|
||||
|
||||
m_storagePoolBytes -= m_storagePool[idx].Length;
|
||||
m_storagePoolBytes += storage.Length;
|
||||
|
||||
m_storagePool[idx] = storage; // replace
|
||||
}
|
||||
else
|
||||
{
|
||||
m_storageSlotsUsedCount++;
|
||||
m_storagePoolBytes += storage.Length;
|
||||
m_storagePool.Add(storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new message for sending
|
||||
/// </summary>
|
||||
public NetOutgoingMessage CreateMessage()
|
||||
{
|
||||
return CreateMessage(m_configuration.m_defaultOutgoingMessageCapacity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new message for sending and writes the provided string to it
|
||||
/// </summary>
|
||||
public NetOutgoingMessage CreateMessage(string content)
|
||||
{
|
||||
NetOutgoingMessage om;
|
||||
|
||||
// Since this could be null.
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
om = CreateMessage(1); // One byte for the internal variable-length zero byte.
|
||||
}
|
||||
else
|
||||
{
|
||||
om = CreateMessage(2 + content.Length); // Fair guess.
|
||||
}
|
||||
|
||||
om.Write(content);
|
||||
return om;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new message for sending
|
||||
/// </summary>
|
||||
/// <param name="initialCapacity">initial capacity in bytes</param>
|
||||
public NetOutgoingMessage CreateMessage(int initialCapacity)
|
||||
{
|
||||
NetOutgoingMessage retval;
|
||||
if (m_outgoingMessagesPool == null || !m_outgoingMessagesPool.TryDequeue(out retval))
|
||||
retval = new NetOutgoingMessage();
|
||||
|
||||
NetException.Assert(retval.m_recyclingCount == 0, "Wrong recycling count! Should be zero" + retval.m_recyclingCount);
|
||||
|
||||
if (initialCapacity > 0)
|
||||
retval.m_data = GetStorage(initialCapacity);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, byte[] useStorageData)
|
||||
{
|
||||
NetIncomingMessage retval;
|
||||
if (m_incomingMessagesPool == null || !m_incomingMessagesPool.TryDequeue(out retval))
|
||||
retval = new NetIncomingMessage(tp);
|
||||
else
|
||||
retval.m_incomingMessageType = tp;
|
||||
retval.m_data = useStorageData;
|
||||
return retval;
|
||||
}
|
||||
|
||||
internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, int minimumByteSize)
|
||||
{
|
||||
NetIncomingMessage retval;
|
||||
if (m_incomingMessagesPool == null || !m_incomingMessagesPool.TryDequeue(out retval))
|
||||
retval = new NetIncomingMessage(tp);
|
||||
else
|
||||
retval.m_incomingMessageType = tp;
|
||||
retval.m_data = GetStorage(minimumByteSize);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recycles a NetIncomingMessage instance for reuse; taking pressure off the garbage collector
|
||||
/// </summary>
|
||||
public void Recycle(NetIncomingMessage msg)
|
||||
{
|
||||
if (m_incomingMessagesPool == null || msg == null)
|
||||
return;
|
||||
|
||||
NetException.Assert(m_incomingMessagesPool.Contains(msg) == false, "Recyling already recycled incoming message! Thread race?");
|
||||
|
||||
byte[] storage = msg.m_data;
|
||||
msg.m_data = null;
|
||||
Recycle(storage);
|
||||
msg.Reset();
|
||||
|
||||
if (m_incomingMessagesPool.Count < m_maxCacheCount)
|
||||
m_incomingMessagesPool.Enqueue(msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recycles a list of NetIncomingMessage instances for reuse; taking pressure off the garbage collector
|
||||
/// </summary>
|
||||
public void Recycle(IEnumerable<NetIncomingMessage> toRecycle)
|
||||
{
|
||||
if (m_incomingMessagesPool == null)
|
||||
return;
|
||||
foreach (var im in toRecycle)
|
||||
Recycle(im);
|
||||
}
|
||||
|
||||
internal void Recycle(NetOutgoingMessage msg)
|
||||
{
|
||||
if (m_outgoingMessagesPool == null)
|
||||
return;
|
||||
#if DEBUG
|
||||
NetException.Assert(m_outgoingMessagesPool.Contains(msg) == false, "Recyling already recycled outgoing message! Thread race?");
|
||||
if (msg.m_recyclingCount != 0)
|
||||
LogWarning("Wrong recycling count! should be zero; found " + msg.m_recyclingCount);
|
||||
#endif
|
||||
// setting m_recyclingCount to zero SHOULD be an unnecessary maneuver, if it's not zero something is wrong
|
||||
// however, in RELEASE, we'll just have to accept this and move on with life
|
||||
msg.m_recyclingCount = 0;
|
||||
|
||||
byte[] storage = msg.m_data;
|
||||
msg.m_data = null;
|
||||
|
||||
// message fragments cannot be recycled
|
||||
// TODO: find a way to recycle large message after all fragments has been acknowledged; or? possibly better just to garbage collect them
|
||||
if (msg.m_fragmentGroup == 0)
|
||||
Recycle(storage);
|
||||
|
||||
msg.Reset();
|
||||
if (m_outgoingMessagesPool.Count < m_maxCacheCount)
|
||||
m_outgoingMessagesPool.Enqueue(msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an incoming message with the required capacity for releasing to the application
|
||||
/// </summary>
|
||||
internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, string text)
|
||||
{
|
||||
NetIncomingMessage retval;
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
retval = CreateIncomingMessage(tp, 1);
|
||||
retval.Write(string.Empty);
|
||||
return retval;
|
||||
}
|
||||
|
||||
int numBytes = System.Text.Encoding.UTF8.GetByteCount(text);
|
||||
retval = CreateIncomingMessage(tp, numBytes + (numBytes > 127 ? 2 : 1));
|
||||
retval.Write(text);
|
||||
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
}
|
||||
255
SRMP/Lidgren.Network/NetPeer.Send.cs
Normal file
255
SRMP/Lidgren.Network/NetPeer.Send.cs
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Net;
|
||||
|
||||
#if !__NOIPENDPOINT__
|
||||
using NetEndPoint = System.Net.IPEndPoint;
|
||||
#endif
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetPeer
|
||||
{
|
||||
/// <summary>
|
||||
/// Send a message to a specific connection
|
||||
/// </summary>
|
||||
/// <param name="msg">The message to send</param>
|
||||
/// <param name="recipient">The recipient connection</param>
|
||||
/// <param name="method">How to deliver the message</param>
|
||||
public NetSendResult SendMessage(NetOutgoingMessage msg, NetConnection recipient, NetDeliveryMethod method)
|
||||
{
|
||||
return SendMessage(msg, recipient, method, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to a specific connection
|
||||
/// </summary>
|
||||
/// <param name="msg">The message to send</param>
|
||||
/// <param name="recipient">The recipient connection</param>
|
||||
/// <param name="method">How to deliver the message</param>
|
||||
/// <param name="sequenceChannel">Sequence channel within the delivery method</param>
|
||||
public NetSendResult SendMessage(NetOutgoingMessage msg, NetConnection recipient, NetDeliveryMethod method, int sequenceChannel)
|
||||
{
|
||||
if (msg == null)
|
||||
throw new ArgumentNullException("msg");
|
||||
if (recipient == null)
|
||||
throw new ArgumentNullException("recipient");
|
||||
if (sequenceChannel >= NetConstants.NetChannelsPerDeliveryMethod)
|
||||
throw new ArgumentOutOfRangeException("sequenceChannel");
|
||||
|
||||
NetException.Assert(
|
||||
((method != NetDeliveryMethod.Unreliable && method != NetDeliveryMethod.ReliableUnordered) ||
|
||||
((method == NetDeliveryMethod.Unreliable || method == NetDeliveryMethod.ReliableUnordered) && sequenceChannel == 0)),
|
||||
"Delivery method " + method + " cannot use sequence channels other than 0!"
|
||||
);
|
||||
|
||||
NetException.Assert(method != NetDeliveryMethod.Unknown, "Bad delivery method!");
|
||||
|
||||
if (msg.m_isSent)
|
||||
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
|
||||
msg.m_isSent = true;
|
||||
|
||||
bool suppressFragmentation = (method == NetDeliveryMethod.Unreliable || method == NetDeliveryMethod.UnreliableSequenced) && m_configuration.UnreliableSizeBehaviour != NetUnreliableSizeBehaviour.NormalFragmentation;
|
||||
|
||||
int len = NetConstants.UnfragmentedMessageHeaderSize + msg.LengthBytes; // headers + length, faster than calling msg.GetEncodedSize
|
||||
if (len <= recipient.m_currentMTU || suppressFragmentation)
|
||||
{
|
||||
Interlocked.Increment(ref msg.m_recyclingCount);
|
||||
return recipient.EnqueueMessage(msg, method, sequenceChannel);
|
||||
}
|
||||
else
|
||||
{
|
||||
// message must be fragmented!
|
||||
if (recipient.m_status != NetConnectionStatus.Connected)
|
||||
return NetSendResult.FailedNotConnected;
|
||||
return SendFragmentedMessage(msg, new NetConnection[] { recipient }, method, sequenceChannel);
|
||||
}
|
||||
}
|
||||
|
||||
internal static int GetMTU(IList<NetConnection> recipients)
|
||||
{
|
||||
int count = recipients.Count;
|
||||
|
||||
int mtu = int.MaxValue;
|
||||
if (count < 1)
|
||||
{
|
||||
#if DEBUG
|
||||
throw new NetException("GetMTU called with no recipients");
|
||||
#else
|
||||
// we don't have access to the particular peer, so just use default MTU
|
||||
return NetPeerConfiguration.kDefaultMTU;
|
||||
#endif
|
||||
}
|
||||
|
||||
for(int i=0;i<count;i++)
|
||||
{
|
||||
var conn = recipients[i];
|
||||
int cmtu = conn.m_currentMTU;
|
||||
if (cmtu < mtu)
|
||||
mtu = cmtu;
|
||||
}
|
||||
return mtu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to a list of connections
|
||||
/// </summary>
|
||||
/// <param name="msg">The message to send</param>
|
||||
/// <param name="recipients">The list of recipients to send to</param>
|
||||
/// <param name="method">How to deliver the message</param>
|
||||
/// <param name="sequenceChannel">Sequence channel within the delivery method</param>
|
||||
public void SendMessage(NetOutgoingMessage msg, IList<NetConnection> recipients, NetDeliveryMethod method, int sequenceChannel)
|
||||
{
|
||||
if (msg == null)
|
||||
throw new ArgumentNullException("msg");
|
||||
if (recipients == null)
|
||||
{
|
||||
if (msg.m_isSent == false)
|
||||
Recycle(msg);
|
||||
throw new ArgumentNullException("recipients");
|
||||
}
|
||||
if (recipients.Count < 1)
|
||||
{
|
||||
if (msg.m_isSent == false)
|
||||
Recycle(msg);
|
||||
throw new NetException("recipients must contain at least one item");
|
||||
}
|
||||
if (method == NetDeliveryMethod.Unreliable || method == NetDeliveryMethod.ReliableUnordered)
|
||||
NetException.Assert(sequenceChannel == 0, "Delivery method " + method + " cannot use sequence channels other than 0!");
|
||||
if (msg.m_isSent)
|
||||
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
|
||||
msg.m_isSent = true;
|
||||
|
||||
int mtu = GetMTU(recipients);
|
||||
|
||||
int len = msg.GetEncodedSize();
|
||||
if (len <= mtu)
|
||||
{
|
||||
Interlocked.Add(ref msg.m_recyclingCount, recipients.Count);
|
||||
foreach (NetConnection conn in recipients)
|
||||
{
|
||||
if (conn == null)
|
||||
{
|
||||
Interlocked.Decrement(ref msg.m_recyclingCount);
|
||||
continue;
|
||||
}
|
||||
NetSendResult res = conn.EnqueueMessage(msg, method, sequenceChannel);
|
||||
if (res == NetSendResult.Dropped)
|
||||
Interlocked.Decrement(ref msg.m_recyclingCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// message must be fragmented!
|
||||
SendFragmentedMessage(msg, recipients, method, sequenceChannel);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to an unconnected host
|
||||
/// </summary>
|
||||
public void SendUnconnectedMessage(NetOutgoingMessage msg, string host, int port)
|
||||
{
|
||||
if (msg == null)
|
||||
throw new ArgumentNullException("msg");
|
||||
if (host == null)
|
||||
throw new ArgumentNullException("host");
|
||||
if (msg.m_isSent)
|
||||
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
|
||||
if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit)
|
||||
throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")");
|
||||
|
||||
msg.m_isSent = true;
|
||||
msg.m_messageType = NetMessageType.Unconnected;
|
||||
|
||||
var adr = NetUtility.Resolve(host);
|
||||
if (adr == null)
|
||||
throw new NetException("Failed to resolve " + host);
|
||||
|
||||
Interlocked.Increment(ref msg.m_recyclingCount);
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<NetEndPoint, NetOutgoingMessage>(new NetEndPoint(adr, port), msg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to an unconnected host
|
||||
/// </summary>
|
||||
public void SendUnconnectedMessage(NetOutgoingMessage msg, NetEndPoint recipient)
|
||||
{
|
||||
if (msg == null)
|
||||
throw new ArgumentNullException("msg");
|
||||
if (recipient == null)
|
||||
throw new ArgumentNullException("recipient");
|
||||
if (msg.m_isSent)
|
||||
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
|
||||
if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit)
|
||||
throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")");
|
||||
|
||||
msg.m_messageType = NetMessageType.Unconnected;
|
||||
msg.m_isSent = true;
|
||||
|
||||
Interlocked.Increment(ref msg.m_recyclingCount);
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<NetEndPoint, NetOutgoingMessage>(recipient, msg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to an unconnected host
|
||||
/// </summary>
|
||||
public void SendUnconnectedMessage(NetOutgoingMessage msg, IList<NetEndPoint> recipients)
|
||||
{
|
||||
if (msg == null)
|
||||
throw new ArgumentNullException("msg");
|
||||
if (recipients == null)
|
||||
throw new ArgumentNullException("recipients");
|
||||
if (recipients.Count < 1)
|
||||
throw new NetException("recipients must contain at least one item");
|
||||
if (msg.m_isSent)
|
||||
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
|
||||
if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit)
|
||||
throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")");
|
||||
|
||||
msg.m_messageType = NetMessageType.Unconnected;
|
||||
msg.m_isSent = true;
|
||||
|
||||
Interlocked.Add(ref msg.m_recyclingCount, recipients.Count);
|
||||
foreach (NetEndPoint ep in recipients)
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<NetEndPoint, NetOutgoingMessage>(ep, msg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to this exact same netpeer (loopback)
|
||||
/// </summary>
|
||||
public void SendUnconnectedToSelf(NetOutgoingMessage om)
|
||||
{
|
||||
if (om == null)
|
||||
throw new ArgumentNullException("msg");
|
||||
if (om.m_isSent)
|
||||
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
|
||||
|
||||
om.m_messageType = NetMessageType.Unconnected;
|
||||
om.m_isSent = true;
|
||||
|
||||
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData) == false)
|
||||
{
|
||||
Interlocked.Decrement(ref om.m_recyclingCount);
|
||||
return; // dropping unconnected message since it's not enabled for receiving
|
||||
}
|
||||
|
||||
// convert outgoing to incoming
|
||||
NetIncomingMessage im = CreateIncomingMessage(NetIncomingMessageType.UnconnectedData, om.LengthBytes);
|
||||
im.Write(om);
|
||||
im.m_isFragment = false;
|
||||
im.m_receiveTime = NetTime.Now;
|
||||
im.m_senderConnection = null;
|
||||
im.m_senderEndPoint = m_socket.LocalEndPoint as NetEndPoint;
|
||||
NetException.Assert(im.m_bitLength == om.LengthBits);
|
||||
|
||||
// recycle outgoing message
|
||||
Recycle(om);
|
||||
|
||||
ReleaseMessage(im);
|
||||
}
|
||||
}
|
||||
}
|
||||
389
SRMP/Lidgren.Network/NetPeer.cs
Normal file
389
SRMP/Lidgren.Network/NetPeer.cs
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
||||
#if !__NOIPENDPOINT__
|
||||
using NetEndPoint = System.Net.IPEndPoint;
|
||||
#endif
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a local peer capable of holding zero, one or more connections to remote peers
|
||||
/// </summary>
|
||||
public partial class NetPeer
|
||||
{
|
||||
private static int s_initializedPeersCount;
|
||||
|
||||
private int m_listenPort;
|
||||
private object m_tag;
|
||||
private object m_messageReceivedEventCreationLock = new object();
|
||||
|
||||
internal readonly List<NetConnection> m_connections;
|
||||
private readonly Dictionary<NetEndPoint, NetConnection> m_connectionLookup;
|
||||
|
||||
private string m_shutdownReason;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetPeerStatus of the NetPeer
|
||||
/// </summary>
|
||||
public NetPeerStatus Status { get { return m_status; } }
|
||||
|
||||
/// <summary>
|
||||
/// Signalling event which can be waited on to determine when a message is queued for reading.
|
||||
/// Note that there is no guarantee that after the event is signaled the blocked thread will
|
||||
/// find the message in the queue. Other user created threads could be preempted and dequeue
|
||||
/// the message before the waiting thread wakes up.
|
||||
/// </summary>
|
||||
public AutoResetEvent MessageReceivedEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_messageReceivedEvent == null)
|
||||
{
|
||||
lock (m_messageReceivedEventCreationLock) // make sure we don't create more than one event object
|
||||
{
|
||||
if (m_messageReceivedEvent == null)
|
||||
m_messageReceivedEvent = new AutoResetEvent(false);
|
||||
}
|
||||
}
|
||||
return m_messageReceivedEvent;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a unique identifier for this NetPeer based on Mac address and ip/port. Note! Not available until Start() has been called!
|
||||
/// </summary>
|
||||
public long UniqueIdentifier { get { return m_uniqueIdentifier; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the port number this NetPeer is listening and sending on, if Start() has been called
|
||||
/// </summary>
|
||||
public int Port { get { return m_listenPort; } }
|
||||
|
||||
/// <summary>
|
||||
/// Returns an UPnP object if enabled in the NetPeerConfiguration
|
||||
/// </summary>
|
||||
public NetUPnP UPnP { get { return m_upnp; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the application defined object containing data about the peer
|
||||
/// </summary>
|
||||
public object Tag
|
||||
{
|
||||
get { return m_tag; }
|
||||
set { m_tag = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a copy of the list of connections
|
||||
/// </summary>
|
||||
public List<NetConnection> Connections
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (m_connections)
|
||||
return new List<NetConnection>(m_connections);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of active connections
|
||||
/// </summary>
|
||||
public int ConnectionsCount
|
||||
{
|
||||
get { return m_connections.Count; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Statistics on this NetPeer since it was initialized
|
||||
/// </summary>
|
||||
public NetPeerStatistics Statistics
|
||||
{
|
||||
get { return m_statistics; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the configuration used to instanciate this NetPeer
|
||||
/// </summary>
|
||||
public NetPeerConfiguration Configuration { get { return m_configuration; } }
|
||||
|
||||
/// <summary>
|
||||
/// NetPeer constructor
|
||||
/// </summary>
|
||||
public NetPeer(NetPeerConfiguration config)
|
||||
{
|
||||
m_configuration = config;
|
||||
m_statistics = new NetPeerStatistics(this);
|
||||
m_releasedIncomingMessages = new NetQueue<NetIncomingMessage>(4);
|
||||
m_unsentUnconnectedMessages = new NetQueue<NetTuple<NetEndPoint, NetOutgoingMessage>>(2);
|
||||
m_connections = new List<NetConnection>();
|
||||
m_connectionLookup = new Dictionary<NetEndPoint, NetConnection>();
|
||||
m_handshakes = new Dictionary<NetEndPoint, NetConnection>();
|
||||
m_senderRemote = (EndPoint)new NetEndPoint(IPAddress.Any, 0);
|
||||
m_status = NetPeerStatus.NotRunning;
|
||||
m_receivedFragmentGroups = new Dictionary<NetConnection, Dictionary<int, ReceivedFragmentGroup>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds to socket and spawns the networking thread
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
if (m_status != NetPeerStatus.NotRunning)
|
||||
{
|
||||
// already running! Just ignore...
|
||||
LogWarning("Start() called on already running NetPeer - ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_status = NetPeerStatus.Starting;
|
||||
|
||||
// fix network thread name
|
||||
if (m_configuration.NetworkThreadName == "Lidgren network thread")
|
||||
{
|
||||
int pc = Interlocked.Increment(ref s_initializedPeersCount);
|
||||
m_configuration.NetworkThreadName = "Lidgren network thread " + pc.ToString();
|
||||
}
|
||||
|
||||
InitializeNetwork();
|
||||
|
||||
// start network thread
|
||||
m_networkThread = new Thread(new ThreadStart(NetworkLoop));
|
||||
m_networkThread.Name = m_configuration.NetworkThreadName;
|
||||
m_networkThread.IsBackground = true;
|
||||
m_networkThread.Start();
|
||||
|
||||
// send upnp discovery
|
||||
if (m_upnp != null)
|
||||
m_upnp.Discover(this);
|
||||
|
||||
// allow some time for network thread to start up in case they call Connect() or UPnP calls immediately
|
||||
NetUtility.Sleep(50);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the connection, if any, for a certain remote endpoint
|
||||
/// </summary>
|
||||
public NetConnection GetConnection(NetEndPoint ep)
|
||||
{
|
||||
NetConnection retval;
|
||||
|
||||
// this should not pose a threading problem, m_connectionLookup is never added to concurrently
|
||||
// and TryGetValue will not throw an exception on fail, only yield null, which is acceptable
|
||||
m_connectionLookup.TryGetValue(ep, out retval);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a pending message from any connection, blocking up to maxMillis if needed
|
||||
/// </summary>
|
||||
public NetIncomingMessage WaitMessage(int maxMillis)
|
||||
{
|
||||
NetIncomingMessage msg = ReadMessage();
|
||||
|
||||
while (msg == null)
|
||||
{
|
||||
// This could return true...
|
||||
if (!MessageReceivedEvent.WaitOne(maxMillis))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// ... while this will still returns null. That's why we need to cycle.
|
||||
msg = ReadMessage();
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a pending message from any connection, if any
|
||||
/// </summary>
|
||||
public NetIncomingMessage ReadMessage()
|
||||
{
|
||||
NetIncomingMessage retval;
|
||||
if (m_releasedIncomingMessages.TryDequeue(out retval))
|
||||
{
|
||||
if (retval.MessageType == NetIncomingMessageType.StatusChanged)
|
||||
{
|
||||
NetConnectionStatus status = (NetConnectionStatus)retval.PeekByte();
|
||||
retval.SenderConnection.m_visibleStatus = status;
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a pending message from any connection, if any.
|
||||
/// Returns true if message was read, otherwise false.
|
||||
/// </summary>
|
||||
/// <returns>True, if message was read.</returns>
|
||||
public bool ReadMessage(out NetIncomingMessage message)
|
||||
{
|
||||
message = ReadMessage();
|
||||
return message != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a pending message from any connection, if any
|
||||
/// </summary>
|
||||
public int ReadMessages(IList<NetIncomingMessage> addTo)
|
||||
{
|
||||
int added = m_releasedIncomingMessages.TryDrain(addTo);
|
||||
if (added > 0)
|
||||
{
|
||||
for (int i = 0; i < added; i++)
|
||||
{
|
||||
var index = addTo.Count - added + i;
|
||||
var nim = addTo[index];
|
||||
if (nim.MessageType == NetIncomingMessageType.StatusChanged)
|
||||
{
|
||||
NetConnectionStatus status = (NetConnectionStatus)nim.PeekByte();
|
||||
nim.SenderConnection.m_visibleStatus = status;
|
||||
}
|
||||
}
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
// send message immediately and recycle it
|
||||
internal void SendLibrary(NetOutgoingMessage msg, NetEndPoint recipient)
|
||||
{
|
||||
VerifyNetworkThread();
|
||||
NetException.Assert(msg.m_isSent == false);
|
||||
|
||||
bool connReset;
|
||||
int len = msg.Encode(m_sendBuffer, 0, 0);
|
||||
SendPacket(len, recipient, 1, out connReset);
|
||||
|
||||
// no reliability, no multiple recipients - we can just recycle this message immediately
|
||||
msg.m_recyclingCount = 0;
|
||||
Recycle(msg);
|
||||
}
|
||||
|
||||
static NetEndPoint GetNetEndPoint(string host, int port)
|
||||
{
|
||||
IPAddress address = NetUtility.Resolve(host);
|
||||
if (address == null)
|
||||
throw new NetException("Could not resolve host");
|
||||
return new NetEndPoint(address, port);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a connection to a remote endpoint
|
||||
/// </summary>
|
||||
public NetConnection Connect(string host, int port)
|
||||
{
|
||||
return Connect(GetNetEndPoint(host, port), null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a connection to a remote endpoint
|
||||
/// </summary>
|
||||
public NetConnection Connect(string host, int port, NetOutgoingMessage hailMessage)
|
||||
{
|
||||
return Connect(GetNetEndPoint(host, port), hailMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a connection to a remote endpoint
|
||||
/// </summary>
|
||||
public NetConnection Connect(NetEndPoint remoteEndPoint)
|
||||
{
|
||||
return Connect(remoteEndPoint, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a connection to a remote endpoint
|
||||
/// </summary>
|
||||
public virtual NetConnection Connect(NetEndPoint remoteEndPoint, NetOutgoingMessage hailMessage)
|
||||
{
|
||||
if (remoteEndPoint == null)
|
||||
throw new ArgumentNullException("remoteEndPoint");
|
||||
|
||||
lock (m_connections)
|
||||
{
|
||||
if (m_status == NetPeerStatus.NotRunning)
|
||||
throw new NetException("Must call Start() first");
|
||||
|
||||
if (m_connectionLookup.ContainsKey(remoteEndPoint))
|
||||
throw new NetException("Already connected to that endpoint!");
|
||||
|
||||
NetConnection hs;
|
||||
if (m_handshakes.TryGetValue(remoteEndPoint, out hs))
|
||||
{
|
||||
// already trying to connect to that endpoint; make another try
|
||||
switch (hs.m_status)
|
||||
{
|
||||
case NetConnectionStatus.InitiatedConnect:
|
||||
// send another connect
|
||||
hs.m_connectRequested = true;
|
||||
break;
|
||||
case NetConnectionStatus.RespondedConnect:
|
||||
// send another response
|
||||
hs.SendConnectResponse(NetTime.Now, false);
|
||||
break;
|
||||
default:
|
||||
// weird
|
||||
LogWarning("Weird situation; Connect() already in progress to remote endpoint; but hs status is " + hs.m_status);
|
||||
break;
|
||||
}
|
||||
return hs;
|
||||
}
|
||||
|
||||
NetConnection conn = new NetConnection(this, remoteEndPoint);
|
||||
conn.SetStatus(NetConnectionStatus.InitiatedConnect, "user called connect");
|
||||
conn.m_localHailMessage = hailMessage;
|
||||
|
||||
// handle on network thread
|
||||
conn.m_connectRequested = true;
|
||||
conn.m_connectionInitiator = true;
|
||||
|
||||
m_handshakes.Add(remoteEndPoint, conn);
|
||||
|
||||
return conn;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send raw bytes; only used for debugging
|
||||
/// </summary>
|
||||
public void RawSend(byte[] arr, int offset, int length, NetEndPoint destination)
|
||||
{
|
||||
// wrong thread - this miiiight crash with network thread... but what's a boy to do.
|
||||
Array.Copy(arr, offset, m_sendBuffer, 0, length);
|
||||
bool unused;
|
||||
SendPacket(length, destination, 1, out unused);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In DEBUG, throws an exception, in RELEASE logs an error message
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
internal void ThrowOrLog(string message)
|
||||
{
|
||||
#if DEBUG
|
||||
throw new NetException(message);
|
||||
#else
|
||||
LogError(message);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects all active connections and closes the socket
|
||||
/// </summary>
|
||||
public void Shutdown(string bye)
|
||||
{
|
||||
// called on user thread
|
||||
if (m_socket == null)
|
||||
return; // already shut down
|
||||
|
||||
LogDebug("Shutdown requested");
|
||||
m_shutdownReason = bye;
|
||||
m_status = NetPeerStatus.ShutdownRequested;
|
||||
}
|
||||
}
|
||||
}
|
||||
541
SRMP/Lidgren.Network/NetPeerConfiguration.cs
Normal file
541
SRMP/Lidgren.Network/NetPeerConfiguration.cs
Normal file
|
|
@ -0,0 +1,541 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Partly immutable after NetPeer has been initialized
|
||||
/// </summary>
|
||||
public sealed class NetPeerConfiguration
|
||||
{
|
||||
// Maximum transmission unit
|
||||
// Ethernet can take 1500 bytes of payload, so lets stay below that.
|
||||
// The aim is for a max full packet to be 1440 bytes (30 x 48 bytes, lower than 1468)
|
||||
// -20 bytes IP header
|
||||
// -8 bytes UDP header
|
||||
// -4 bytes to be on the safe side and align to 8-byte boundary
|
||||
// Total 1408 bytes
|
||||
// Note that lidgren headers (5 bytes) are not included here; since it's part of the "mtu payload"
|
||||
|
||||
/// <summary>
|
||||
/// Default MTU value in bytes
|
||||
/// </summary>
|
||||
public const int kDefaultMTU = 1408;
|
||||
|
||||
private const string c_isLockedMessage = "You may not modify the NetPeerConfiguration after it has been used to initialize a NetPeer";
|
||||
|
||||
private bool m_isLocked;
|
||||
private readonly string m_appIdentifier;
|
||||
private string m_networkThreadName;
|
||||
private IPAddress m_localAddress;
|
||||
private IPAddress m_broadcastAddress;
|
||||
internal bool m_acceptIncomingConnections;
|
||||
internal int m_maximumConnections;
|
||||
internal int m_defaultOutgoingMessageCapacity;
|
||||
internal float m_pingInterval;
|
||||
internal bool m_useMessageRecycling;
|
||||
internal int m_recycledCacheMaxCount;
|
||||
internal float m_connectionTimeout;
|
||||
internal bool m_enableUPnP;
|
||||
internal bool m_autoFlushSendQueue;
|
||||
private NetUnreliableSizeBehaviour m_unreliableSizeBehaviour;
|
||||
internal bool m_suppressUnreliableUnorderedAcks;
|
||||
|
||||
internal NetIncomingMessageType m_disabledTypes;
|
||||
internal int m_port;
|
||||
internal int m_receiveBufferSize;
|
||||
internal int m_sendBufferSize;
|
||||
internal float m_resendHandshakeInterval;
|
||||
internal int m_maximumHandshakeAttempts;
|
||||
|
||||
// bad network simulation
|
||||
internal float m_loss;
|
||||
internal float m_duplicates;
|
||||
internal float m_minimumOneWayLatency;
|
||||
internal float m_randomOneWayLatency;
|
||||
|
||||
// MTU
|
||||
internal int m_maximumTransmissionUnit;
|
||||
internal bool m_autoExpandMTU;
|
||||
internal float m_expandMTUFrequency;
|
||||
internal int m_expandMTUFailAttempts;
|
||||
|
||||
/// <summary>
|
||||
/// NetPeerConfiguration constructor
|
||||
/// </summary>
|
||||
public NetPeerConfiguration(string appIdentifier)
|
||||
{
|
||||
if (string.IsNullOrEmpty(appIdentifier))
|
||||
throw new NetException("App identifier must be at least one character long");
|
||||
m_appIdentifier = appIdentifier;
|
||||
|
||||
//
|
||||
// default values
|
||||
//
|
||||
m_disabledTypes = NetIncomingMessageType.ConnectionApproval | NetIncomingMessageType.UnconnectedData | NetIncomingMessageType.VerboseDebugMessage | NetIncomingMessageType.ConnectionLatencyUpdated | NetIncomingMessageType.NatIntroductionSuccess;
|
||||
m_networkThreadName = "Lidgren network thread";
|
||||
m_localAddress = IPAddress.Any;
|
||||
m_broadcastAddress = IPAddress.Broadcast;
|
||||
var ip = NetUtility.GetBroadcastAddress();
|
||||
if (ip != null)
|
||||
{
|
||||
m_broadcastAddress = ip;
|
||||
}
|
||||
m_port = 0;
|
||||
m_receiveBufferSize = 131071;
|
||||
m_sendBufferSize = 131071;
|
||||
m_acceptIncomingConnections = false;
|
||||
m_maximumConnections = 32;
|
||||
m_defaultOutgoingMessageCapacity = 16;
|
||||
m_pingInterval = 4.0f;
|
||||
m_connectionTimeout = 25.0f;
|
||||
m_useMessageRecycling = true;
|
||||
m_recycledCacheMaxCount = 64;
|
||||
m_resendHandshakeInterval = 3.0f;
|
||||
m_maximumHandshakeAttempts = 5;
|
||||
m_autoFlushSendQueue = true;
|
||||
m_suppressUnreliableUnorderedAcks = false;
|
||||
|
||||
m_maximumTransmissionUnit = kDefaultMTU;
|
||||
m_autoExpandMTU = false;
|
||||
m_expandMTUFrequency = 2.0f;
|
||||
m_expandMTUFailAttempts = 5;
|
||||
m_unreliableSizeBehaviour = NetUnreliableSizeBehaviour.IgnoreMTU;
|
||||
|
||||
m_loss = 0.0f;
|
||||
m_minimumOneWayLatency = 0.0f;
|
||||
m_randomOneWayLatency = 0.0f;
|
||||
m_duplicates = 0.0f;
|
||||
|
||||
m_isLocked = false;
|
||||
}
|
||||
|
||||
internal void Lock()
|
||||
{
|
||||
m_isLocked = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the identifier of this application; the library can only connect to matching app identifier peers
|
||||
/// </summary>
|
||||
public string AppIdentifier
|
||||
{
|
||||
get { return m_appIdentifier; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables receiving of the specified type of message
|
||||
/// </summary>
|
||||
public void EnableMessageType(NetIncomingMessageType type)
|
||||
{
|
||||
m_disabledTypes &= (~type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables receiving of the specified type of message
|
||||
/// </summary>
|
||||
public void DisableMessageType(NetIncomingMessageType type)
|
||||
{
|
||||
m_disabledTypes |= type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables receiving of the specified type of message
|
||||
/// </summary>
|
||||
public void SetMessageTypeEnabled(NetIncomingMessageType type, bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
m_disabledTypes &= (~type);
|
||||
else
|
||||
m_disabledTypes |= type;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets if receiving of the specified type of message is enabled
|
||||
/// </summary>
|
||||
public bool IsMessageTypeEnabled(NetIncomingMessageType type)
|
||||
{
|
||||
return !((m_disabledTypes & type) == type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the behaviour of unreliable sends above MTU
|
||||
/// </summary>
|
||||
public NetUnreliableSizeBehaviour UnreliableSizeBehaviour
|
||||
{
|
||||
get { return m_unreliableSizeBehaviour; }
|
||||
set { m_unreliableSizeBehaviour = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the library network thread. Cannot be changed once NetPeer is initialized.
|
||||
/// </summary>
|
||||
public string NetworkThreadName
|
||||
{
|
||||
get { return m_networkThreadName; }
|
||||
set
|
||||
{
|
||||
if (m_isLocked)
|
||||
throw new NetException("NetworkThreadName may not be set after the NetPeer which uses the configuration has been started");
|
||||
m_networkThreadName = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum amount of connections this peer can hold. Cannot be changed once NetPeer is initialized.
|
||||
/// </summary>
|
||||
public int MaximumConnections
|
||||
{
|
||||
get { return m_maximumConnections; }
|
||||
set
|
||||
{
|
||||
if (m_isLocked)
|
||||
throw new NetException(c_isLockedMessage);
|
||||
m_maximumConnections = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum amount of bytes to send in a single packet, excluding ip, udp and lidgren headers. Cannot be changed once NetPeer is initialized.
|
||||
/// </summary>
|
||||
public int MaximumTransmissionUnit
|
||||
{
|
||||
get { return m_maximumTransmissionUnit; }
|
||||
set
|
||||
{
|
||||
if (m_isLocked)
|
||||
throw new NetException(c_isLockedMessage);
|
||||
if (value < 1 || value >= ((ushort.MaxValue + 1) / 8))
|
||||
throw new NetException("MaximumTransmissionUnit must be between 1 and " + (((ushort.MaxValue + 1) / 8) - 1) + " bytes");
|
||||
m_maximumTransmissionUnit = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default capacity in bytes when NetPeer.CreateMessage() is called without argument
|
||||
/// </summary>
|
||||
public int DefaultOutgoingMessageCapacity
|
||||
{
|
||||
get { return m_defaultOutgoingMessageCapacity; }
|
||||
set { m_defaultOutgoingMessageCapacity = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time between latency calculating pings
|
||||
/// </summary>
|
||||
public float PingInterval
|
||||
{
|
||||
get { return m_pingInterval; }
|
||||
set { m_pingInterval = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the library should recycling messages to avoid excessive garbage collection. Cannot be changed once NetPeer is initialized.
|
||||
/// </summary>
|
||||
public bool UseMessageRecycling
|
||||
{
|
||||
get { return m_useMessageRecycling; }
|
||||
set
|
||||
{
|
||||
if (m_isLocked)
|
||||
throw new NetException(c_isLockedMessage);
|
||||
m_useMessageRecycling = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of incoming/outgoing messages to keep in the recycle cache.
|
||||
/// </summary>
|
||||
public int RecycledCacheMaxCount
|
||||
{
|
||||
get { return m_recycledCacheMaxCount; }
|
||||
set
|
||||
{
|
||||
if (m_isLocked)
|
||||
throw new NetException(c_isLockedMessage);
|
||||
m_recycledCacheMaxCount = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of seconds timeout will be postponed on a successful ping/pong
|
||||
/// </summary>
|
||||
public float ConnectionTimeout
|
||||
{
|
||||
get { return m_connectionTimeout; }
|
||||
set
|
||||
{
|
||||
if (value < m_pingInterval)
|
||||
throw new NetException("Connection timeout cannot be lower than ping interval!");
|
||||
m_connectionTimeout = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables UPnP support; enabling port forwarding and getting external ip
|
||||
/// </summary>
|
||||
public bool EnableUPnP
|
||||
{
|
||||
get { return m_enableUPnP; }
|
||||
set
|
||||
{
|
||||
if (m_isLocked)
|
||||
throw new NetException(c_isLockedMessage);
|
||||
m_enableUPnP = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables automatic flushing of the send queue. If disabled, you must manully call NetPeer.FlushSendQueue() to flush sent messages to network.
|
||||
/// </summary>
|
||||
public bool AutoFlushSendQueue
|
||||
{
|
||||
get { return m_autoFlushSendQueue; }
|
||||
set { m_autoFlushSendQueue = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true, will not send acks for unreliable unordered messages. This will save bandwidth, but disable flow control and duplicate detection for this type of messages.
|
||||
/// </summary>
|
||||
public bool SuppressUnreliableUnorderedAcks
|
||||
{
|
||||
get { return m_suppressUnreliableUnorderedAcks; }
|
||||
set
|
||||
{
|
||||
if (m_isLocked)
|
||||
throw new NetException(c_isLockedMessage);
|
||||
m_suppressUnreliableUnorderedAcks = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the local ip address to bind to. Defaults to IPAddress.Any. Cannot be changed once NetPeer is initialized.
|
||||
/// </summary>
|
||||
public IPAddress LocalAddress
|
||||
{
|
||||
get { return m_localAddress; }
|
||||
set
|
||||
{
|
||||
if (m_isLocked)
|
||||
throw new NetException(c_isLockedMessage);
|
||||
m_localAddress = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the local broadcast address to use when broadcasting
|
||||
/// </summary>
|
||||
public IPAddress BroadcastAddress
|
||||
{
|
||||
get { return m_broadcastAddress; }
|
||||
set
|
||||
{
|
||||
if (m_isLocked)
|
||||
throw new NetException(c_isLockedMessage);
|
||||
m_broadcastAddress = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the local port to bind to. Defaults to 0. Cannot be changed once NetPeer is initialized.
|
||||
/// </summary>
|
||||
public int Port
|
||||
{
|
||||
get { return m_port; }
|
||||
set
|
||||
{
|
||||
if (m_isLocked)
|
||||
throw new NetException(c_isLockedMessage);
|
||||
m_port = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size in bytes of the receiving buffer. Defaults to 131071 bytes. Cannot be changed once NetPeer is initialized.
|
||||
/// </summary>
|
||||
public int ReceiveBufferSize
|
||||
{
|
||||
get { return m_receiveBufferSize; }
|
||||
set
|
||||
{
|
||||
if (m_isLocked)
|
||||
throw new NetException(c_isLockedMessage);
|
||||
m_receiveBufferSize = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size in bytes of the sending buffer. Defaults to 131071 bytes. Cannot be changed once NetPeer is initialized.
|
||||
/// </summary>
|
||||
public int SendBufferSize
|
||||
{
|
||||
get { return m_sendBufferSize; }
|
||||
set
|
||||
{
|
||||
if (m_isLocked)
|
||||
throw new NetException(c_isLockedMessage);
|
||||
m_sendBufferSize = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the NetPeer should accept incoming connections. This is automatically set to true in NetServer and false in NetClient.
|
||||
/// </summary>
|
||||
public bool AcceptIncomingConnections
|
||||
{
|
||||
get { return m_acceptIncomingConnections; }
|
||||
set { m_acceptIncomingConnections = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of seconds between handshake attempts
|
||||
/// </summary>
|
||||
public float ResendHandshakeInterval
|
||||
{
|
||||
get { return m_resendHandshakeInterval; }
|
||||
set { m_resendHandshakeInterval = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of handshake attempts before failing to connect
|
||||
/// </summary>
|
||||
public int MaximumHandshakeAttempts
|
||||
{
|
||||
get { return m_maximumHandshakeAttempts; }
|
||||
set
|
||||
{
|
||||
if (value < 1)
|
||||
throw new NetException("MaximumHandshakeAttempts must be at least 1");
|
||||
m_maximumHandshakeAttempts = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the NetPeer should send large messages to try to expand the maximum transmission unit size
|
||||
/// </summary>
|
||||
public bool AutoExpandMTU
|
||||
{
|
||||
get { return m_autoExpandMTU; }
|
||||
set
|
||||
{
|
||||
if (m_isLocked)
|
||||
throw new NetException(c_isLockedMessage);
|
||||
m_autoExpandMTU = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets how often to send large messages to expand MTU if AutoExpandMTU is enabled
|
||||
/// </summary>
|
||||
public float ExpandMTUFrequency
|
||||
{
|
||||
get { return m_expandMTUFrequency; }
|
||||
set { m_expandMTUFrequency = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of failed expand mtu attempts to perform before setting final MTU
|
||||
/// </summary>
|
||||
public int ExpandMTUFailAttempts
|
||||
{
|
||||
get { return m_expandMTUFailAttempts; }
|
||||
set { m_expandMTUFailAttempts = value; }
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
/// Gets or sets the simulated amount of sent packets lost from 0.0f to 1.0f
|
||||
/// </summary>
|
||||
public float SimulatedLoss
|
||||
{
|
||||
get { return m_loss; }
|
||||
set { m_loss = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum simulated amount of one way latency for sent packets in seconds
|
||||
/// </summary>
|
||||
public float SimulatedMinimumLatency
|
||||
{
|
||||
get { return m_minimumOneWayLatency; }
|
||||
set { m_minimumOneWayLatency = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the simulated added random amount of one way latency for sent packets in seconds
|
||||
/// </summary>
|
||||
public float SimulatedRandomLatency
|
||||
{
|
||||
get { return m_randomOneWayLatency; }
|
||||
set { m_randomOneWayLatency = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the average simulated one way latency in seconds
|
||||
/// </summary>
|
||||
public float SimulatedAverageLatency
|
||||
{
|
||||
get { return m_minimumOneWayLatency + (m_randomOneWayLatency * 0.5f); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the simulated amount of duplicated packets from 0.0f to 1.0f
|
||||
/// </summary>
|
||||
public float SimulatedDuplicatesChance
|
||||
{
|
||||
get { return m_duplicates; }
|
||||
set { m_duplicates = value; }
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Creates a memberwise shallow clone of this configuration
|
||||
/// </summary>
|
||||
public NetPeerConfiguration Clone()
|
||||
{
|
||||
NetPeerConfiguration retval = this.MemberwiseClone() as NetPeerConfiguration;
|
||||
retval.m_isLocked = false;
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Behaviour of unreliable sends above MTU
|
||||
/// </summary>
|
||||
public enum NetUnreliableSizeBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Sending an unreliable message will ignore MTU and send everything in a single packet; this is the new default
|
||||
/// </summary>
|
||||
IgnoreMTU = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Old behaviour; use normal fragmentation for unreliable messages - if a fragment is dropped, memory for received fragments are never reclaimed!
|
||||
/// </summary>
|
||||
NormalFragmentation = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Alternate behaviour; just drops unreliable messages above MTU
|
||||
/// </summary>
|
||||
DropAboveMTU = 2,
|
||||
}
|
||||
}
|
||||
158
SRMP/Lidgren.Network/NetPeerStatistics.cs
Normal file
158
SRMP/Lidgren.Network/NetPeerStatistics.cs
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
// Uncomment the line below to get statistics in RELEASE builds
|
||||
//#define USE_RELEASE_STATISTICS
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Statistics for a NetPeer instance
|
||||
/// </summary>
|
||||
public sealed class NetPeerStatistics
|
||||
{
|
||||
private readonly NetPeer m_peer;
|
||||
|
||||
internal int m_sentPackets;
|
||||
internal int m_receivedPackets;
|
||||
|
||||
internal int m_sentMessages;
|
||||
internal int m_receivedMessages;
|
||||
internal int m_receivedFragments;
|
||||
|
||||
internal int m_sentBytes;
|
||||
internal int m_receivedBytes;
|
||||
|
||||
internal long m_bytesAllocated;
|
||||
|
||||
internal NetPeerStatistics(NetPeer peer)
|
||||
{
|
||||
m_peer = peer;
|
||||
Reset();
|
||||
}
|
||||
|
||||
internal void Reset()
|
||||
{
|
||||
m_sentPackets = 0;
|
||||
m_receivedPackets = 0;
|
||||
|
||||
m_sentMessages = 0;
|
||||
m_receivedMessages = 0;
|
||||
m_receivedFragments = 0;
|
||||
|
||||
m_sentBytes = 0;
|
||||
m_receivedBytes = 0;
|
||||
|
||||
m_bytesAllocated = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of sent packets since the NetPeer was initialized
|
||||
/// </summary>
|
||||
public int SentPackets { get { return m_sentPackets; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of received packets since the NetPeer was initialized
|
||||
/// </summary>
|
||||
public int ReceivedPackets { get { return m_receivedPackets; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of sent messages since the NetPeer was initialized
|
||||
/// </summary>
|
||||
public int SentMessages { get { return m_sentMessages; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of received messages since the NetPeer was initialized
|
||||
/// </summary>
|
||||
public int ReceivedMessages { get { return m_receivedMessages; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of sent bytes since the NetPeer was initialized
|
||||
/// </summary>
|
||||
public int SentBytes { get { return m_sentBytes; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of received bytes since the NetPeer was initialized
|
||||
/// </summary>
|
||||
public int ReceivedBytes { get { return m_receivedBytes; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bytes allocated (and possibly garbage collected) for message storage
|
||||
/// </summary>
|
||||
public long StorageBytesAllocated { get { return m_bytesAllocated; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bytes in the recycled pool
|
||||
/// </summary>
|
||||
public int BytesInRecyclePool
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (m_peer.m_storagePool)
|
||||
return m_peer.m_storagePoolBytes;
|
||||
}
|
||||
}
|
||||
|
||||
#if !USE_RELEASE_STATISTICS
|
||||
[Conditional("DEBUG")]
|
||||
#endif
|
||||
internal void PacketSent(int numBytes, int numMessages)
|
||||
{
|
||||
m_sentPackets++;
|
||||
m_sentBytes += numBytes;
|
||||
m_sentMessages += numMessages;
|
||||
}
|
||||
|
||||
#if !USE_RELEASE_STATISTICS
|
||||
[Conditional("DEBUG")]
|
||||
#endif
|
||||
internal void PacketReceived(int numBytes, int numMessages, int numFragments)
|
||||
{
|
||||
m_receivedPackets++;
|
||||
m_receivedBytes += numBytes;
|
||||
m_receivedMessages += numMessages;
|
||||
m_receivedFragments += numFragments;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents this object
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder bdr = new StringBuilder();
|
||||
bdr.AppendLine(m_peer.ConnectionsCount.ToString() + " connections");
|
||||
#if DEBUG || USE_RELEASE_STATISTICS
|
||||
bdr.AppendLine("Sent " + m_sentBytes + " bytes in " + m_sentMessages + " messages in " + m_sentPackets + " packets");
|
||||
bdr.AppendLine("Received " + m_receivedBytes + " bytes in " + m_receivedMessages + " messages (of which " + m_receivedFragments + " fragments) in " + m_receivedPackets + " packets");
|
||||
#else
|
||||
bdr.AppendLine("Sent (n/a) bytes in (n/a) messages in (n/a) packets");
|
||||
bdr.AppendLine("Received (n/a) bytes in (n/a) messages in (n/a) packets");
|
||||
#endif
|
||||
bdr.AppendLine("Storage allocated " + m_bytesAllocated + " bytes");
|
||||
if (m_peer.m_storagePool != null)
|
||||
bdr.AppendLine("Recycled pool " + m_peer.m_storagePoolBytes + " bytes (" + m_peer.m_storageSlotsUsedCount + " entries)");
|
||||
return bdr.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
49
SRMP/Lidgren.Network/NetPeerStatus.cs
Normal file
49
SRMP/Lidgren.Network/NetPeerStatus.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Status for a NetPeer instance
|
||||
/// </summary>
|
||||
public enum NetPeerStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// NetPeer is not running; socket is not bound
|
||||
/// </summary>
|
||||
NotRunning = 0,
|
||||
|
||||
/// <summary>
|
||||
/// NetPeer is in the process of starting up
|
||||
/// </summary>
|
||||
Starting = 1,
|
||||
|
||||
/// <summary>
|
||||
/// NetPeer is bound to socket and listening for packets
|
||||
/// </summary>
|
||||
Running = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Shutdown has been requested and will be executed shortly
|
||||
/// </summary>
|
||||
ShutdownRequested = 3,
|
||||
}
|
||||
}
|
||||
356
SRMP/Lidgren.Network/NetQueue.cs
Normal file
356
SRMP/Lidgren.Network/NetQueue.cs
Normal file
|
|
@ -0,0 +1,356 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
//
|
||||
// Comment for Linux Mono users: reports of library thread hangs on EnterReadLock() suggests switching to plain lock() works better
|
||||
//
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Thread safe (blocking) expanding queue with TryDequeue() and EnqueueFirst()
|
||||
/// </summary>
|
||||
[DebuggerDisplay("Count={Count} Capacity={Capacity}")]
|
||||
public sealed class NetQueue<T>
|
||||
{
|
||||
// Example:
|
||||
// m_capacity = 8
|
||||
// m_size = 6
|
||||
// m_head = 4
|
||||
//
|
||||
// [0] item
|
||||
// [1] item (tail = ((head + size - 1) % capacity)
|
||||
// [2]
|
||||
// [3]
|
||||
// [4] item (head)
|
||||
// [5] item
|
||||
// [6] item
|
||||
// [7] item
|
||||
//
|
||||
private T[] m_items;
|
||||
private readonly ReaderWriterLockSlim m_lock = new ReaderWriterLockSlim();
|
||||
private int m_size;
|
||||
private int m_head;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of items in the queue
|
||||
/// </summary>
|
||||
public int Count {
|
||||
get
|
||||
{
|
||||
m_lock.EnterReadLock();
|
||||
int count = m_size;
|
||||
m_lock.ExitReadLock();
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current capacity for the queue
|
||||
/// </summary>
|
||||
public int Capacity
|
||||
{
|
||||
get
|
||||
{
|
||||
m_lock.EnterReadLock();
|
||||
int capacity = m_items.Length;
|
||||
m_lock.ExitReadLock();
|
||||
return capacity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NetQueue constructor
|
||||
/// </summary>
|
||||
public NetQueue(int initialCapacity)
|
||||
{
|
||||
m_items = new T[initialCapacity];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an item last/tail of the queue
|
||||
/// </summary>
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
m_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (m_size == m_items.Length)
|
||||
SetCapacity(m_items.Length + 8);
|
||||
|
||||
int slot = (m_head + m_size) % m_items.Length;
|
||||
m_items[slot] = item;
|
||||
m_size++;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an item last/tail of the queue
|
||||
/// </summary>
|
||||
public void Enqueue(IEnumerable<T> items)
|
||||
{
|
||||
m_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (m_size == m_items.Length)
|
||||
SetCapacity(m_items.Length + 8); // @TODO move this out of loop
|
||||
|
||||
int slot = (m_head + m_size) % m_items.Length;
|
||||
m_items[slot] = item;
|
||||
m_size++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Places an item first, at the head of the queue
|
||||
/// </summary>
|
||||
public void EnqueueFirst(T item)
|
||||
{
|
||||
m_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (m_size >= m_items.Length)
|
||||
SetCapacity(m_items.Length + 8);
|
||||
|
||||
m_head--;
|
||||
if (m_head < 0)
|
||||
m_head = m_items.Length - 1;
|
||||
m_items[m_head] = item;
|
||||
m_size++;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
// must be called from within a write locked m_lock!
|
||||
private void SetCapacity(int newCapacity)
|
||||
{
|
||||
if (m_size == 0)
|
||||
{
|
||||
if (m_size == 0)
|
||||
{
|
||||
m_items = new T[newCapacity];
|
||||
m_head = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
T[] newItems = new T[newCapacity];
|
||||
|
||||
if (m_head + m_size - 1 < m_items.Length)
|
||||
{
|
||||
Array.Copy(m_items, m_head, newItems, 0, m_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(m_items, m_head, newItems, 0, m_items.Length - m_head);
|
||||
Array.Copy(m_items, 0, newItems, m_items.Length - m_head, (m_size - (m_items.Length - m_head)));
|
||||
}
|
||||
|
||||
m_items = newItems;
|
||||
m_head = 0;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an item from the head of the queue, or returns default(T) if empty
|
||||
/// </summary>
|
||||
public bool TryDequeue(out T item)
|
||||
{
|
||||
if (m_size == 0)
|
||||
{
|
||||
item = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (m_size == 0)
|
||||
{
|
||||
item = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
item = m_items[m_head];
|
||||
m_items[m_head] = default(T);
|
||||
|
||||
m_head = (m_head + 1) % m_items.Length;
|
||||
m_size--;
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
#if DEBUG
|
||||
throw;
|
||||
#else
|
||||
item = default(T);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all items from the head of the queue, or returns number of items popped
|
||||
/// </summary>
|
||||
public int TryDrain(IList<T> addTo)
|
||||
{
|
||||
if (m_size == 0)
|
||||
return 0;
|
||||
|
||||
m_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
int added = m_size;
|
||||
while (m_size > 0)
|
||||
{
|
||||
var item = m_items[m_head];
|
||||
addTo.Add(item);
|
||||
|
||||
m_items[m_head] = default(T);
|
||||
m_head = (m_head + 1) % m_items.Length;
|
||||
m_size--;
|
||||
}
|
||||
return added;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns default(T) if queue is empty
|
||||
/// </summary>
|
||||
public T TryPeek(int offset)
|
||||
{
|
||||
if (m_size == 0)
|
||||
return default(T);
|
||||
|
||||
m_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (m_size == 0)
|
||||
return default(T);
|
||||
return m_items[(m_head + offset) % m_items.Length];
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether an item is in the queue
|
||||
/// </summary>
|
||||
public bool Contains(T item)
|
||||
{
|
||||
m_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
int ptr = m_head;
|
||||
for (int i = 0; i < m_size; i++)
|
||||
{
|
||||
if (m_items[ptr] == null)
|
||||
{
|
||||
if (item == null)
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_items[ptr].Equals(item))
|
||||
return true;
|
||||
}
|
||||
ptr = (ptr + 1) % m_items.Length;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the queue items to a new array
|
||||
/// </summary>
|
||||
public T[] ToArray()
|
||||
{
|
||||
m_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
T[] retval = new T[m_size];
|
||||
int ptr = m_head;
|
||||
for (int i = 0; i < m_size; i++)
|
||||
{
|
||||
retval[i] = m_items[ptr++];
|
||||
if (ptr >= m_items.Length)
|
||||
ptr = 0;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all objects from the queue
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
m_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < m_items.Length; i++)
|
||||
m_items[i] = default(T);
|
||||
m_head = 0;
|
||||
m_size = 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_lock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
281
SRMP/Lidgren.Network/NetRandom.Implementations.cs
Normal file
281
SRMP/Lidgren.Network/NetRandom.Implementations.cs
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Multiply With Carry random
|
||||
/// </summary>
|
||||
public class MWCRandom : NetRandom
|
||||
{
|
||||
/// <summary>
|
||||
/// Get global instance of MWCRandom
|
||||
/// </summary>
|
||||
public static new readonly MWCRandom Instance = new MWCRandom();
|
||||
|
||||
private uint m_w, m_z;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with randomized seed
|
||||
/// </summary>
|
||||
public MWCRandom()
|
||||
{
|
||||
Initialize(NetRandomSeed.GetUInt64());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// (Re)initialize this instance with provided 32 bit seed
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public override void Initialize(uint seed)
|
||||
{
|
||||
m_w = seed;
|
||||
m_z = seed * 16777619;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// (Re)initialize this instance with provided 64 bit seed
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public void Initialize(ulong seed)
|
||||
{
|
||||
m_w = (uint)seed;
|
||||
m_z = (uint)(seed >> 32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public override uint NextUInt32()
|
||||
{
|
||||
m_z = 36969 * (m_z & 65535) + (m_z >> 16);
|
||||
m_w = 18000 * (m_w & 65535) + (m_w >> 16);
|
||||
return ((m_z << 16) + m_w);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Xor Shift based random
|
||||
/// </summary>
|
||||
public sealed class XorShiftRandom : NetRandom
|
||||
{
|
||||
/// <summary>
|
||||
/// Get global instance of XorShiftRandom
|
||||
/// </summary>
|
||||
public static new readonly XorShiftRandom Instance = new XorShiftRandom();
|
||||
|
||||
private const uint c_x = 123456789;
|
||||
private const uint c_y = 362436069;
|
||||
private const uint c_z = 521288629;
|
||||
private const uint c_w = 88675123;
|
||||
|
||||
private uint m_x, m_y, m_z, m_w;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with randomized seed
|
||||
/// </summary>
|
||||
public XorShiftRandom()
|
||||
{
|
||||
Initialize(NetRandomSeed.GetUInt64());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with provided 64 bit seed
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public XorShiftRandom(ulong seed)
|
||||
{
|
||||
Initialize(seed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// (Re)initialize this instance with provided 32 bit seed
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public override void Initialize(uint seed)
|
||||
{
|
||||
m_x = (uint)seed;
|
||||
m_y = c_y;
|
||||
m_z = c_z;
|
||||
m_w = c_w;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// (Re)initialize this instance with provided 64 bit seed
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public void Initialize(ulong seed)
|
||||
{
|
||||
m_x = (uint)seed;
|
||||
m_y = c_y;
|
||||
m_z = (uint)(seed << 32);
|
||||
m_w = c_w;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public override uint NextUInt32()
|
||||
{
|
||||
uint t = (m_x ^ (m_x << 11));
|
||||
m_x = m_y; m_y = m_z; m_z = m_w;
|
||||
return (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mersenne Twister based random
|
||||
/// </summary>
|
||||
public sealed class MersenneTwisterRandom : NetRandom
|
||||
{
|
||||
/// <summary>
|
||||
/// Get global instance of MersenneTwisterRandom
|
||||
/// </summary>
|
||||
public static new readonly MersenneTwisterRandom Instance = new MersenneTwisterRandom();
|
||||
|
||||
private const int N = 624;
|
||||
private const int M = 397;
|
||||
private const uint MATRIX_A = 0x9908b0dfU;
|
||||
private const uint UPPER_MASK = 0x80000000U;
|
||||
private const uint LOWER_MASK = 0x7fffffffU;
|
||||
private const uint TEMPER1 = 0x9d2c5680U;
|
||||
private const uint TEMPER2 = 0xefc60000U;
|
||||
private const int TEMPER3 = 11;
|
||||
private const int TEMPER4 = 7;
|
||||
private const int TEMPER5 = 15;
|
||||
private const int TEMPER6 = 18;
|
||||
|
||||
private UInt32[] mt;
|
||||
private int mti;
|
||||
private UInt32[] mag01;
|
||||
|
||||
private const double c_realUnitInt = 1.0 / ((double)int.MaxValue + 1.0);
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with randomized seed
|
||||
/// </summary>
|
||||
public MersenneTwisterRandom()
|
||||
{
|
||||
Initialize(NetRandomSeed.GetUInt32());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with provided 32 bit seed
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public MersenneTwisterRandom(uint seed)
|
||||
{
|
||||
Initialize(seed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// (Re)initialize this instance with provided 32 bit seed
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public override void Initialize(uint seed)
|
||||
{
|
||||
mt = new UInt32[N];
|
||||
mti = N + 1;
|
||||
mag01 = new UInt32[] { 0x0U, MATRIX_A };
|
||||
mt[0] = seed;
|
||||
for (int i = 1; i < N; i++)
|
||||
mt[i] = (UInt32)(1812433253 * (mt[i - 1] ^ (mt[i - 1] >> 30)) + i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public override uint NextUInt32()
|
||||
{
|
||||
UInt32 y;
|
||||
if (mti >= N)
|
||||
{
|
||||
GenRandAll();
|
||||
mti = 0;
|
||||
}
|
||||
y = mt[mti++];
|
||||
y ^= (y >> TEMPER3);
|
||||
y ^= (y << TEMPER4) & TEMPER1;
|
||||
y ^= (y << TEMPER5) & TEMPER2;
|
||||
y ^= (y >> TEMPER6);
|
||||
return y;
|
||||
}
|
||||
|
||||
private void GenRandAll()
|
||||
{
|
||||
int kk = 1;
|
||||
UInt32 y;
|
||||
UInt32 p;
|
||||
y = mt[0] & UPPER_MASK;
|
||||
do
|
||||
{
|
||||
p = mt[kk];
|
||||
mt[kk - 1] = mt[kk + (M - 1)] ^ ((y | (p & LOWER_MASK)) >> 1) ^ mag01[p & 1];
|
||||
y = p & UPPER_MASK;
|
||||
} while (++kk < N - M + 1);
|
||||
do
|
||||
{
|
||||
p = mt[kk];
|
||||
mt[kk - 1] = mt[kk + (M - N - 1)] ^ ((y | (p & LOWER_MASK)) >> 1) ^ mag01[p & 1];
|
||||
y = p & UPPER_MASK;
|
||||
} while (++kk < N);
|
||||
p = mt[0];
|
||||
mt[N - 1] = mt[M - 1] ^ ((y | (p & LOWER_MASK)) >> 1) ^ mag01[p & 1];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RNGCryptoServiceProvider based random; very slow but cryptographically safe
|
||||
/// </summary>
|
||||
public class CryptoRandom : NetRandom
|
||||
{
|
||||
/// <summary>
|
||||
/// Global instance of CryptoRandom
|
||||
/// </summary>
|
||||
public static new readonly CryptoRandom Instance = new CryptoRandom();
|
||||
|
||||
private RandomNumberGenerator m_rnd = new RNGCryptoServiceProvider();
|
||||
|
||||
/// <summary>
|
||||
/// Seed in CryptoRandom does not create deterministic sequences
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public override void Initialize(uint seed)
|
||||
{
|
||||
byte[] tmp = new byte[seed % 16];
|
||||
m_rnd.GetBytes(tmp); // just prime it
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public override uint NextUInt32()
|
||||
{
|
||||
var bytes = new byte[4];
|
||||
m_rnd.GetBytes(bytes);
|
||||
return (uint)bytes[0] | (((uint)bytes[1]) << 8) | (((uint)bytes[2]) << 16) | (((uint)bytes[3]) << 24);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill the specified buffer with random values
|
||||
/// </summary>
|
||||
public override void NextBytes(byte[] buffer)
|
||||
{
|
||||
m_rnd.GetBytes(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills all bytes from offset to offset + length in buffer with random values
|
||||
/// </summary>
|
||||
public override void NextBytes(byte[] buffer, int offset, int length)
|
||||
{
|
||||
var bytes = new byte[length];
|
||||
m_rnd.GetBytes(bytes);
|
||||
Array.Copy(bytes, 0, buffer, offset, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
177
SRMP/Lidgren.Network/NetRandom.cs
Normal file
177
SRMP/Lidgren.Network/NetRandom.cs
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// NetRandom base class
|
||||
/// </summary>
|
||||
public abstract class NetRandom : Random
|
||||
{
|
||||
/// <summary>
|
||||
/// Get global instance of NetRandom (uses MWCRandom)
|
||||
/// </summary>
|
||||
public static NetRandom Instance = new MWCRandom();
|
||||
|
||||
private const double c_realUnitInt = 1.0 / ((double)int.MaxValue + 1.0);
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with randomized seed
|
||||
/// </summary>
|
||||
public NetRandom()
|
||||
{
|
||||
Initialize(NetRandomSeed.GetUInt32());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with provided 32 bit seed
|
||||
/// </summary>
|
||||
public NetRandom(int seed)
|
||||
{
|
||||
Initialize((uint)seed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// (Re)initialize this instance with provided 32 bit seed
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public virtual void Initialize(uint seed)
|
||||
{
|
||||
// should be abstract, but non-CLS compliant methods can't be abstract!
|
||||
throw new NotImplementedException("Implement this in inherited classes");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public virtual uint NextUInt32()
|
||||
{
|
||||
// should be abstract, but non-CLS compliant methods can't be abstract!
|
||||
throw new NotImplementedException("Implement this in inherited classes");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random value that is greater or equal than 0 and less than Int32.MaxValue
|
||||
/// </summary>
|
||||
public override int Next()
|
||||
{
|
||||
var retval = (int)(0x7FFFFFFF & NextUInt32());
|
||||
if (retval == 0x7FFFFFFF)
|
||||
return NextInt32();
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random value greater or equal than 0 and less or equal than Int32.MaxValue (inclusively)
|
||||
/// </summary>
|
||||
public int NextInt32()
|
||||
{
|
||||
return (int)(0x7FFFFFFF & NextUInt32());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns random value larger or equal to 0.0 and less than 1.0
|
||||
/// </summary>
|
||||
public override double NextDouble()
|
||||
{
|
||||
return c_realUnitInt * NextInt32();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns random value is greater or equal than 0.0 and less than 1.0
|
||||
/// </summary>
|
||||
protected override double Sample()
|
||||
{
|
||||
return c_realUnitInt * NextInt32();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns random value is greater or equal than 0.0f and less than 1.0f
|
||||
/// </summary>
|
||||
public float NextSingle()
|
||||
{
|
||||
var retval = (float)(c_realUnitInt * NextInt32());
|
||||
if (retval == 1.0f)
|
||||
return NextSingle();
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random value is greater or equal than 0 and less than maxValue
|
||||
/// </summary>
|
||||
public override int Next(int maxValue)
|
||||
{
|
||||
return (int)(NextDouble() * maxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random value is greater or equal than minValue and less than maxValue
|
||||
/// </summary>
|
||||
public override int Next(int minValue, int maxValue)
|
||||
{
|
||||
return minValue + (int)(NextDouble() * (double)(maxValue - minValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random value between UInt64.MinValue to UInt64.MaxValue
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public ulong NextUInt64()
|
||||
{
|
||||
ulong retval = NextUInt32();
|
||||
retval |= NextUInt32() << 32;
|
||||
return retval;
|
||||
}
|
||||
|
||||
private uint m_boolValues;
|
||||
private int m_nextBoolIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true or false, randomly
|
||||
/// </summary>
|
||||
public bool NextBool()
|
||||
{
|
||||
if (m_nextBoolIndex >= 32)
|
||||
{
|
||||
m_boolValues = NextUInt32();
|
||||
m_nextBoolIndex = 1;
|
||||
}
|
||||
|
||||
var retval = ((m_boolValues >> m_nextBoolIndex) & 1) == 1;
|
||||
m_nextBoolIndex++;
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Fills all bytes from offset to offset + length in buffer with random values
|
||||
/// </summary>
|
||||
public virtual void NextBytes(byte[] buffer, int offset, int length)
|
||||
{
|
||||
int full = length / 4;
|
||||
int ptr = offset;
|
||||
for (int i = 0; i < full; i++)
|
||||
{
|
||||
uint r = NextUInt32();
|
||||
buffer[ptr++] = (byte)r;
|
||||
buffer[ptr++] = (byte)(r >> 8);
|
||||
buffer[ptr++] = (byte)(r >> 16);
|
||||
buffer[ptr++] = (byte)(r >> 24);
|
||||
}
|
||||
|
||||
int rest = length - (full * 4);
|
||||
for (int i = 0; i < rest; i++)
|
||||
buffer[ptr++] = (byte)NextUInt32();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill the specified buffer with random values
|
||||
/// </summary>
|
||||
public override void NextBytes(byte[] buffer)
|
||||
{
|
||||
NextBytes(buffer, 0, buffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
45
SRMP/Lidgren.Network/NetRandomSeed.cs
Normal file
45
SRMP/Lidgren.Network/NetRandomSeed.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for generating random seeds
|
||||
/// </summary>
|
||||
public static class NetRandomSeed
|
||||
{
|
||||
private static int m_seedIncrement = -1640531527;
|
||||
|
||||
/// <summary>
|
||||
/// Generates a 32 bit random seed
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public static uint GetUInt32()
|
||||
{
|
||||
ulong seed = GetUInt64();
|
||||
uint low = (uint)seed;
|
||||
uint high = (uint)(seed >> 32);
|
||||
return low ^ high;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a 64 bit random seed
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public static ulong GetUInt64()
|
||||
{
|
||||
var guidBytes = Guid.NewGuid().ToByteArray();
|
||||
ulong seed =
|
||||
((ulong)guidBytes[0] << (8 * 0)) |
|
||||
((ulong)guidBytes[1] << (8 * 1)) |
|
||||
((ulong)guidBytes[2] << (8 * 2)) |
|
||||
((ulong)guidBytes[3] << (8 * 3)) |
|
||||
((ulong)guidBytes[4] << (8 * 4)) |
|
||||
((ulong)guidBytes[5] << (8 * 5)) |
|
||||
((ulong)guidBytes[6] << (8 * 6)) |
|
||||
((ulong)guidBytes[7] << (8 * 7));
|
||||
|
||||
return seed ^ NetUtility.GetPlatformSeed(m_seedIncrement);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
SRMP/Lidgren.Network/NetReceiverChannelBase.cs
Normal file
18
SRMP/Lidgren.Network/NetReceiverChannelBase.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal abstract class NetReceiverChannelBase
|
||||
{
|
||||
internal NetPeer m_peer;
|
||||
internal NetConnection m_connection;
|
||||
|
||||
public NetReceiverChannelBase(NetConnection connection)
|
||||
{
|
||||
m_connection = connection;
|
||||
m_peer = connection.m_peer;
|
||||
}
|
||||
|
||||
internal abstract void ReceiveMessage(NetIncomingMessage msg);
|
||||
}
|
||||
}
|
||||
89
SRMP/Lidgren.Network/NetReliableOrderedReceiver.cs
Normal file
89
SRMP/Lidgren.Network/NetReliableOrderedReceiver.cs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal sealed class NetReliableOrderedReceiver : NetReceiverChannelBase
|
||||
{
|
||||
private int m_windowStart;
|
||||
private int m_windowSize;
|
||||
private NetBitVector m_earlyReceived;
|
||||
internal NetIncomingMessage[] m_withheldMessages;
|
||||
|
||||
public NetReliableOrderedReceiver(NetConnection connection, int windowSize)
|
||||
: base(connection)
|
||||
{
|
||||
m_windowSize = windowSize;
|
||||
m_withheldMessages = new NetIncomingMessage[windowSize];
|
||||
m_earlyReceived = new NetBitVector(windowSize);
|
||||
}
|
||||
|
||||
private void AdvanceWindow()
|
||||
{
|
||||
m_earlyReceived.Set(m_windowStart % m_windowSize, false);
|
||||
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
}
|
||||
|
||||
internal override void ReceiveMessage(NetIncomingMessage message)
|
||||
{
|
||||
int relate = NetUtility.RelativeSequenceNumber(message.m_sequenceNumber, m_windowStart);
|
||||
|
||||
// ack no matter what
|
||||
m_connection.QueueAck(message.m_receivedMessageType, message.m_sequenceNumber);
|
||||
|
||||
if (relate == 0)
|
||||
{
|
||||
// Log("Received message #" + message.SequenceNumber + " right on time");
|
||||
|
||||
//
|
||||
// excellent, right on time
|
||||
//
|
||||
//m_peer.LogVerbose("Received RIGHT-ON-TIME " + message);
|
||||
|
||||
AdvanceWindow();
|
||||
m_peer.ReleaseMessage(message);
|
||||
|
||||
// release withheld messages
|
||||
int nextSeqNr = (message.m_sequenceNumber + 1) % NetConstants.NumSequenceNumbers;
|
||||
|
||||
while (m_earlyReceived[nextSeqNr % m_windowSize])
|
||||
{
|
||||
message = m_withheldMessages[nextSeqNr % m_windowSize];
|
||||
NetException.Assert(message != null);
|
||||
|
||||
// remove it from withheld messages
|
||||
m_withheldMessages[nextSeqNr % m_windowSize] = null;
|
||||
|
||||
m_peer.LogVerbose("Releasing withheld message #" + message);
|
||||
|
||||
m_peer.ReleaseMessage(message);
|
||||
|
||||
AdvanceWindow();
|
||||
nextSeqNr++;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (relate < 0)
|
||||
{
|
||||
// duplicate
|
||||
m_connection.m_statistics.MessageDropped();
|
||||
m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING DUPLICATE");
|
||||
return;
|
||||
}
|
||||
|
||||
// relate > 0 = early message
|
||||
if (relate > m_windowSize)
|
||||
{
|
||||
// too early message!
|
||||
m_connection.m_statistics.MessageDropped();
|
||||
m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart);
|
||||
return;
|
||||
}
|
||||
|
||||
m_earlyReceived.Set(message.m_sequenceNumber % m_windowSize, true);
|
||||
m_peer.LogVerbose("Received " + message + " WITHHOLDING, waiting for " + m_windowStart);
|
||||
m_withheldMessages[message.m_sequenceNumber % m_windowSize] = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
290
SRMP/Lidgren.Network/NetReliableSenderChannel.cs
Normal file
290
SRMP/Lidgren.Network/NetReliableSenderChannel.cs
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Sender part of Selective repeat ARQ for a particular NetChannel
|
||||
/// </summary>
|
||||
internal sealed class NetReliableSenderChannel : NetSenderChannelBase
|
||||
{
|
||||
private NetConnection m_connection;
|
||||
private int m_windowStart;
|
||||
private int m_windowSize;
|
||||
private int m_sendStart;
|
||||
|
||||
private bool m_anyStoredResends;
|
||||
|
||||
private NetBitVector m_receivedAcks;
|
||||
internal NetStoredReliableMessage[] m_storedMessages;
|
||||
|
||||
internal double m_resendDelay;
|
||||
|
||||
internal override int WindowSize { get { return m_windowSize; } }
|
||||
|
||||
internal override bool NeedToSendMessages()
|
||||
{
|
||||
return base.NeedToSendMessages() || m_anyStoredResends;
|
||||
}
|
||||
|
||||
internal NetReliableSenderChannel(NetConnection connection, int windowSize)
|
||||
{
|
||||
m_connection = connection;
|
||||
m_windowSize = windowSize;
|
||||
m_windowStart = 0;
|
||||
m_sendStart = 0;
|
||||
m_anyStoredResends = false;
|
||||
m_receivedAcks = new NetBitVector(NetConstants.NumSequenceNumbers);
|
||||
m_storedMessages = new NetStoredReliableMessage[m_windowSize];
|
||||
m_queuedSends = new NetQueue<NetOutgoingMessage>(8);
|
||||
m_resendDelay = m_connection.GetResendDelay();
|
||||
}
|
||||
|
||||
internal override int GetAllowedSends()
|
||||
{
|
||||
int retval = m_windowSize - ((m_sendStart + NetConstants.NumSequenceNumbers) - m_windowStart) % NetConstants.NumSequenceNumbers;
|
||||
NetException.Assert(retval >= 0 && retval <= m_windowSize);
|
||||
return retval;
|
||||
}
|
||||
|
||||
internal override void Reset()
|
||||
{
|
||||
m_receivedAcks.Clear();
|
||||
for (int i = 0; i < m_storedMessages.Length; i++)
|
||||
m_storedMessages[i].Reset();
|
||||
m_anyStoredResends = false;
|
||||
m_queuedSends.Clear();
|
||||
m_windowStart = 0;
|
||||
m_sendStart = 0;
|
||||
}
|
||||
|
||||
internal override NetSendResult Enqueue(NetOutgoingMessage message)
|
||||
{
|
||||
m_queuedSends.Enqueue(message);
|
||||
m_connection.m_peer.m_needFlushSendQueue = true; // a race condition to this variable will simply result in a single superflous call to FlushSendQueue()
|
||||
if (m_queuedSends.Count <= GetAllowedSends())
|
||||
return NetSendResult.Sent;
|
||||
return NetSendResult.Queued;
|
||||
}
|
||||
|
||||
// call this regularely
|
||||
internal override void SendQueuedMessages(double now)
|
||||
{
|
||||
//
|
||||
// resends
|
||||
//
|
||||
m_anyStoredResends = false;
|
||||
for (int i = 0; i < m_storedMessages.Length; i++)
|
||||
{
|
||||
var storedMsg = m_storedMessages[i];
|
||||
NetOutgoingMessage om = storedMsg.Message;
|
||||
if (om == null)
|
||||
continue;
|
||||
|
||||
m_anyStoredResends = true;
|
||||
|
||||
double t = storedMsg.LastSent;
|
||||
if (t > 0 && (now - t) > m_resendDelay)
|
||||
{
|
||||
// deduce sequence number
|
||||
/*
|
||||
int startSlot = m_windowStart % m_windowSize;
|
||||
int seqNr = m_windowStart;
|
||||
while (startSlot != i)
|
||||
{
|
||||
startSlot--;
|
||||
if (startSlot < 0)
|
||||
startSlot = m_windowSize - 1;
|
||||
seqNr--;
|
||||
}
|
||||
*/
|
||||
|
||||
//m_connection.m_peer.LogVerbose("Resending due to delay #" + m_storedMessages[i].SequenceNumber + " " + om.ToString());
|
||||
m_connection.m_statistics.MessageResent(MessageResendReason.Delay);
|
||||
|
||||
Interlocked.Increment(ref om.m_recyclingCount); // increment this since it's being decremented in QueueSendMessage
|
||||
m_connection.QueueSendMessage(om, storedMsg.SequenceNumber);
|
||||
|
||||
m_storedMessages[i].LastSent = now;
|
||||
m_storedMessages[i].NumSent++;
|
||||
}
|
||||
}
|
||||
|
||||
int num = GetAllowedSends();
|
||||
if (num < 1)
|
||||
return;
|
||||
|
||||
// queued sends
|
||||
while (num > 0 && m_queuedSends.Count > 0)
|
||||
{
|
||||
NetOutgoingMessage om;
|
||||
if (m_queuedSends.TryDequeue(out om))
|
||||
ExecuteSend(now, om);
|
||||
num--;
|
||||
NetException.Assert(num == GetAllowedSends());
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteSend(double now, NetOutgoingMessage message)
|
||||
{
|
||||
int seqNr = m_sendStart;
|
||||
m_sendStart = (m_sendStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
|
||||
// must increment recycle count here, since it's decremented in QueueSendMessage and we want to keep it for the future in case or resends
|
||||
// we will decrement once more in DestoreMessage for final recycling
|
||||
Interlocked.Increment(ref message.m_recyclingCount);
|
||||
|
||||
m_connection.QueueSendMessage(message, seqNr);
|
||||
|
||||
int storeIndex = seqNr % m_windowSize;
|
||||
NetException.Assert(m_storedMessages[storeIndex].Message == null);
|
||||
|
||||
m_storedMessages[storeIndex].NumSent++;
|
||||
m_storedMessages[storeIndex].Message = message;
|
||||
m_storedMessages[storeIndex].LastSent = now;
|
||||
m_storedMessages[storeIndex].SequenceNumber = seqNr;
|
||||
m_anyStoredResends = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private void DestoreMessage(double now, int storeIndex, out bool resetTimeout)
|
||||
{
|
||||
// reset timeout if we receive ack within kThreshold of sending it
|
||||
const double kThreshold = 2.0;
|
||||
var srm = m_storedMessages[storeIndex];
|
||||
resetTimeout = (srm.NumSent == 1) && (now - srm.LastSent < kThreshold);
|
||||
|
||||
var storedMessage = srm.Message;
|
||||
|
||||
// on each destore; reduce recyclingcount so that when all instances are destored, the outgoing message can be recycled
|
||||
Interlocked.Decrement(ref storedMessage.m_recyclingCount);
|
||||
#if DEBUG
|
||||
if (storedMessage == null)
|
||||
throw new NetException("m_storedMessages[" + storeIndex + "].Message is null; sent " + m_storedMessages[storeIndex].NumSent + " times, last time " + (NetTime.Now - m_storedMessages[storeIndex].LastSent) + " seconds ago");
|
||||
#else
|
||||
if (storedMessage != null)
|
||||
{
|
||||
#endif
|
||||
if (storedMessage.m_recyclingCount <= 0)
|
||||
m_connection.m_peer.Recycle(storedMessage);
|
||||
|
||||
#if !DEBUG
|
||||
}
|
||||
#endif
|
||||
m_storedMessages[storeIndex] = new NetStoredReliableMessage();
|
||||
}
|
||||
|
||||
// remoteWindowStart is remote expected sequence number; everything below this has arrived properly
|
||||
// seqNr is the actual nr received
|
||||
internal override void ReceiveAcknowledge(double now, int seqNr)
|
||||
{
|
||||
// late (dupe), on time or early ack?
|
||||
int relate = NetUtility.RelativeSequenceNumber(seqNr, m_windowStart);
|
||||
|
||||
if (relate < 0)
|
||||
{
|
||||
//m_connection.m_peer.LogDebug("Received late/dupe ack for #" + seqNr);
|
||||
return; // late/duplicate ack
|
||||
}
|
||||
|
||||
if (relate == 0)
|
||||
{
|
||||
//m_connection.m_peer.LogDebug("Received right-on-time ack for #" + seqNr);
|
||||
|
||||
// ack arrived right on time
|
||||
NetException.Assert(seqNr == m_windowStart);
|
||||
|
||||
bool resetTimeout;
|
||||
m_receivedAcks[m_windowStart] = false;
|
||||
DestoreMessage(now, m_windowStart % m_windowSize, out resetTimeout);
|
||||
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
|
||||
// advance window if we already have early acks
|
||||
while (m_receivedAcks.Get(m_windowStart))
|
||||
{
|
||||
//m_connection.m_peer.LogDebug("Using early ack for #" + m_windowStart + "...");
|
||||
m_receivedAcks[m_windowStart] = false;
|
||||
bool rt;
|
||||
DestoreMessage(now, m_windowStart % m_windowSize, out rt);
|
||||
resetTimeout |= rt;
|
||||
|
||||
NetException.Assert(m_storedMessages[m_windowStart % m_windowSize].Message == null); // should already be destored
|
||||
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
//m_connection.m_peer.LogDebug("Advancing window to #" + m_windowStart);
|
||||
}
|
||||
if (resetTimeout)
|
||||
m_connection.ResetTimeout(now);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// early ack... (if it has been sent!)
|
||||
//
|
||||
// If it has been sent either the m_windowStart message was lost
|
||||
// ... or the ack for that message was lost
|
||||
//
|
||||
|
||||
//m_connection.m_peer.LogDebug("Received early ack for #" + seqNr);
|
||||
|
||||
int sendRelate = NetUtility.RelativeSequenceNumber(seqNr, m_sendStart);
|
||||
if (sendRelate <= 0)
|
||||
{
|
||||
// yes, we've sent this message - it's an early (but valid) ack
|
||||
if (m_receivedAcks[seqNr])
|
||||
{
|
||||
// we've already destored/been acked for this message
|
||||
}
|
||||
else
|
||||
{
|
||||
m_receivedAcks[seqNr] = true;
|
||||
}
|
||||
}
|
||||
else if (sendRelate > 0)
|
||||
{
|
||||
// uh... we haven't sent this message yet? Weird, dupe or error...
|
||||
NetException.Assert(false, "Got ack for message not yet sent?");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ok, lets resend all missing acks
|
||||
int rnr = seqNr;
|
||||
do
|
||||
{
|
||||
rnr--;
|
||||
if (rnr < 0)
|
||||
rnr = NetConstants.NumSequenceNumbers - 1;
|
||||
|
||||
if (m_receivedAcks[rnr])
|
||||
{
|
||||
// m_connection.m_peer.LogDebug("Not resending #" + rnr + " (since we got ack)");
|
||||
}
|
||||
else
|
||||
{
|
||||
int slot = rnr % m_windowSize;
|
||||
NetException.Assert(m_storedMessages[slot].Message != null);
|
||||
if (m_storedMessages[slot].NumSent == 1)
|
||||
{
|
||||
// just sent once; resend immediately since we found gap in ack sequence
|
||||
NetOutgoingMessage rmsg = m_storedMessages[slot].Message;
|
||||
//m_connection.m_peer.LogVerbose("Resending #" + rnr + " (" + rmsg + ")");
|
||||
|
||||
if (now - m_storedMessages[slot].LastSent < (m_resendDelay * 0.35))
|
||||
{
|
||||
// already resent recently
|
||||
}
|
||||
else
|
||||
{
|
||||
m_storedMessages[slot].LastSent = now;
|
||||
m_storedMessages[slot].NumSent++;
|
||||
m_connection.m_statistics.MessageResent(MessageResendReason.HoleInSequence);
|
||||
Interlocked.Increment(ref rmsg.m_recyclingCount); // increment this since it's being decremented in QueueSendMessage
|
||||
m_connection.QueueSendMessage(rmsg, rnr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} while (rnr != m_windowStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
65
SRMP/Lidgren.Network/NetReliableSequencedReceiver.cs
Normal file
65
SRMP/Lidgren.Network/NetReliableSequencedReceiver.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal sealed class NetReliableSequencedReceiver : NetReceiverChannelBase
|
||||
{
|
||||
private int m_windowStart;
|
||||
private int m_windowSize;
|
||||
|
||||
public NetReliableSequencedReceiver(NetConnection connection, int windowSize)
|
||||
: base(connection)
|
||||
{
|
||||
m_windowSize = windowSize;
|
||||
}
|
||||
|
||||
private void AdvanceWindow()
|
||||
{
|
||||
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
}
|
||||
|
||||
internal override void ReceiveMessage(NetIncomingMessage message)
|
||||
{
|
||||
int nr = message.m_sequenceNumber;
|
||||
|
||||
int relate = NetUtility.RelativeSequenceNumber(nr, m_windowStart);
|
||||
|
||||
// ack no matter what
|
||||
m_connection.QueueAck(message.m_receivedMessageType, nr);
|
||||
|
||||
if (relate == 0)
|
||||
{
|
||||
// Log("Received message #" + message.SequenceNumber + " right on time");
|
||||
|
||||
//
|
||||
// excellent, right on time
|
||||
//
|
||||
|
||||
AdvanceWindow();
|
||||
m_peer.ReleaseMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (relate < 0)
|
||||
{
|
||||
m_connection.m_statistics.MessageDropped();
|
||||
m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING LATE or DUPE");
|
||||
return;
|
||||
}
|
||||
|
||||
// relate > 0 = early message
|
||||
if (relate > m_windowSize)
|
||||
{
|
||||
// too early message!
|
||||
m_connection.m_statistics.MessageDropped();
|
||||
m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart);
|
||||
return;
|
||||
}
|
||||
|
||||
// ok
|
||||
m_windowStart = (m_windowStart + relate) % NetConstants.NumSequenceNumbers;
|
||||
m_peer.ReleaseMessage(message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
97
SRMP/Lidgren.Network/NetReliableUnorderedReceiver.cs
Normal file
97
SRMP/Lidgren.Network/NetReliableUnorderedReceiver.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal sealed class NetReliableUnorderedReceiver : NetReceiverChannelBase
|
||||
{
|
||||
private int m_windowStart;
|
||||
private int m_windowSize;
|
||||
private NetBitVector m_earlyReceived;
|
||||
|
||||
public NetReliableUnorderedReceiver(NetConnection connection, int windowSize)
|
||||
: base(connection)
|
||||
{
|
||||
m_windowSize = windowSize;
|
||||
m_earlyReceived = new NetBitVector(windowSize);
|
||||
}
|
||||
|
||||
private void AdvanceWindow()
|
||||
{
|
||||
m_earlyReceived.Set(m_windowStart % m_windowSize, false);
|
||||
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
}
|
||||
|
||||
internal override void ReceiveMessage(NetIncomingMessage message)
|
||||
{
|
||||
int relate = NetUtility.RelativeSequenceNumber(message.m_sequenceNumber, m_windowStart);
|
||||
|
||||
// ack no matter what
|
||||
m_connection.QueueAck(message.m_receivedMessageType, message.m_sequenceNumber);
|
||||
|
||||
if (relate == 0)
|
||||
{
|
||||
// Log("Received message #" + message.SequenceNumber + " right on time");
|
||||
|
||||
//
|
||||
// excellent, right on time
|
||||
//
|
||||
//m_peer.LogVerbose("Received RIGHT-ON-TIME " + message);
|
||||
|
||||
AdvanceWindow();
|
||||
m_peer.ReleaseMessage(message);
|
||||
|
||||
// release withheld messages
|
||||
int nextSeqNr = (message.m_sequenceNumber + 1) % NetConstants.NumSequenceNumbers;
|
||||
|
||||
while (m_earlyReceived[nextSeqNr % m_windowSize])
|
||||
{
|
||||
//message = m_withheldMessages[nextSeqNr % m_windowSize];
|
||||
//NetException.Assert(message != null);
|
||||
|
||||
// remove it from withheld messages
|
||||
//m_withheldMessages[nextSeqNr % m_windowSize] = null;
|
||||
|
||||
//m_peer.LogVerbose("Releasing withheld message #" + message);
|
||||
|
||||
//m_peer.ReleaseMessage(message);
|
||||
|
||||
AdvanceWindow();
|
||||
nextSeqNr++;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (relate < 0)
|
||||
{
|
||||
// duplicate
|
||||
m_connection.m_statistics.MessageDropped();
|
||||
m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING DUPLICATE");
|
||||
return;
|
||||
}
|
||||
|
||||
// relate > 0 = early message
|
||||
if (relate > m_windowSize)
|
||||
{
|
||||
// too early message!
|
||||
m_connection.m_statistics.MessageDropped();
|
||||
m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_earlyReceived.Get(message.m_sequenceNumber % m_windowSize))
|
||||
{
|
||||
// duplicate
|
||||
m_connection.m_statistics.MessageDropped();
|
||||
m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING DUPLICATE");
|
||||
return;
|
||||
}
|
||||
|
||||
m_earlyReceived.Set(message.m_sequenceNumber % m_windowSize, true);
|
||||
//m_peer.LogVerbose("Received " + message + " WITHHOLDING, waiting for " + m_windowStart);
|
||||
//m_withheldMessages[message.m_sequenceNumber % m_windowSize] = message;
|
||||
|
||||
m_peer.ReleaseMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
179
SRMP/Lidgren.Network/NetSRP.cs
Normal file
179
SRMP/Lidgren.Network/NetSRP.cs
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
#define USE_SHA256
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper methods for implementing SRP authentication
|
||||
/// </summary>
|
||||
public static class NetSRP
|
||||
{
|
||||
private static readonly NetBigInteger N = new NetBigInteger("0115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3", 16);
|
||||
private static readonly NetBigInteger g = NetBigInteger.Two;
|
||||
private static readonly NetBigInteger k = ComputeMultiplier();
|
||||
|
||||
/// <summary>
|
||||
/// Compute multiplier (k)
|
||||
/// </summary>
|
||||
private static NetBigInteger ComputeMultiplier()
|
||||
{
|
||||
string one = NetUtility.ToHexString(N.ToByteArrayUnsigned());
|
||||
string two = NetUtility.ToHexString(g.ToByteArrayUnsigned());
|
||||
|
||||
string ccstr = one + two.PadLeft(one.Length, '0');
|
||||
byte[] cc = NetUtility.ToByteArray(ccstr);
|
||||
|
||||
var ccHashed = NetUtility.ComputeSHAHash(cc);
|
||||
return new NetBigInteger(NetUtility.ToHexString(ccHashed), 16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create 16 bytes of random salt
|
||||
/// </summary>
|
||||
public static byte[] CreateRandomSalt()
|
||||
{
|
||||
byte[] retval = new byte[16];
|
||||
CryptoRandom.Instance.NextBytes(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create 32 bytes of random ephemeral value
|
||||
/// </summary>
|
||||
public static byte[] CreateRandomEphemeral()
|
||||
{
|
||||
byte[] retval = new byte[32];
|
||||
CryptoRandom.Instance.NextBytes(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computer private key (x)
|
||||
/// </summary>
|
||||
public static byte[] ComputePrivateKey(string username, string password, byte[] salt)
|
||||
{
|
||||
byte[] tmp = Encoding.UTF8.GetBytes(username + ":" + password);
|
||||
byte[] innerHash = NetUtility.ComputeSHAHash(tmp);
|
||||
|
||||
byte[] total = new byte[innerHash.Length + salt.Length];
|
||||
Buffer.BlockCopy(salt, 0, total, 0, salt.Length);
|
||||
Buffer.BlockCopy(innerHash, 0, total, salt.Length, innerHash.Length);
|
||||
|
||||
// x ie. H(salt || H(username || ":" || password))
|
||||
return new NetBigInteger(NetUtility.ToHexString(NetUtility.ComputeSHAHash(total)), 16).ToByteArrayUnsigned();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a verifier that the server can later use to authenticate users later on (v)
|
||||
/// </summary>
|
||||
public static byte[] ComputeServerVerifier(byte[] privateKey)
|
||||
{
|
||||
NetBigInteger x = new NetBigInteger(NetUtility.ToHexString(privateKey), 16);
|
||||
|
||||
// Verifier (v) = g^x (mod N)
|
||||
var serverVerifier = g.ModPow(x, N);
|
||||
|
||||
return serverVerifier.ToByteArrayUnsigned();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute client public ephemeral value (A)
|
||||
/// </summary>
|
||||
public static byte[] ComputeClientEphemeral(byte[] clientPrivateEphemeral) // a
|
||||
{
|
||||
// A= g^a (mod N)
|
||||
NetBigInteger a = new NetBigInteger(NetUtility.ToHexString(clientPrivateEphemeral), 16);
|
||||
NetBigInteger retval = g.ModPow(a, N);
|
||||
|
||||
return retval.ToByteArrayUnsigned();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute server ephemeral value (B)
|
||||
/// </summary>
|
||||
public static byte[] ComputeServerEphemeral(byte[] serverPrivateEphemeral, byte[] verifier) // b
|
||||
{
|
||||
var b = new NetBigInteger(NetUtility.ToHexString(serverPrivateEphemeral), 16);
|
||||
var v = new NetBigInteger(NetUtility.ToHexString(verifier), 16);
|
||||
|
||||
// B = kv + g^b (mod N)
|
||||
var bb = g.ModPow(b, N);
|
||||
var kv = v.Multiply(k);
|
||||
var B = (kv.Add(bb)).Mod(N);
|
||||
|
||||
return B.ToByteArrayUnsigned();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute intermediate value (u)
|
||||
/// </summary>
|
||||
public static byte[] ComputeU(byte[] clientPublicEphemeral, byte[] serverPublicEphemeral)
|
||||
{
|
||||
// u = SHA-1(A || B)
|
||||
string one = NetUtility.ToHexString(clientPublicEphemeral);
|
||||
string two = NetUtility.ToHexString(serverPublicEphemeral);
|
||||
|
||||
int len = 66; // Math.Max(one.Length, two.Length);
|
||||
string ccstr = one.PadLeft(len, '0') + two.PadLeft(len, '0');
|
||||
|
||||
byte[] cc = NetUtility.ToByteArray(ccstr);
|
||||
|
||||
var ccHashed = NetUtility.ComputeSHAHash(cc);
|
||||
|
||||
return new NetBigInteger(NetUtility.ToHexString(ccHashed), 16).ToByteArrayUnsigned();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the server session value
|
||||
/// </summary>
|
||||
public static byte[] ComputeServerSessionValue(byte[] clientPublicEphemeral, byte[] verifier, byte[] udata, byte[] serverPrivateEphemeral)
|
||||
{
|
||||
// S = (Av^u) ^ b (mod N)
|
||||
var A = new NetBigInteger(NetUtility.ToHexString(clientPublicEphemeral), 16);
|
||||
var v = new NetBigInteger(NetUtility.ToHexString(verifier), 16);
|
||||
var u = new NetBigInteger(NetUtility.ToHexString(udata), 16);
|
||||
var b = new NetBigInteger(NetUtility.ToHexString(serverPrivateEphemeral), 16);
|
||||
|
||||
NetBigInteger retval = v.ModPow(u, N).Multiply(A).Mod(N).ModPow(b, N).Mod(N);
|
||||
|
||||
return retval.ToByteArrayUnsigned();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the client session value
|
||||
/// </summary>
|
||||
public static byte[] ComputeClientSessionValue(byte[] serverPublicEphemeral, byte[] xdata, byte[] udata, byte[] clientPrivateEphemeral)
|
||||
{
|
||||
// (B - kg^x) ^ (a + ux) (mod N)
|
||||
var B = new NetBigInteger(NetUtility.ToHexString(serverPublicEphemeral), 16);
|
||||
var x = new NetBigInteger(NetUtility.ToHexString(xdata), 16);
|
||||
var u = new NetBigInteger(NetUtility.ToHexString(udata), 16);
|
||||
var a = new NetBigInteger(NetUtility.ToHexString(clientPrivateEphemeral), 16);
|
||||
|
||||
var bx = g.ModPow(x, N);
|
||||
var btmp = B.Add(N.Multiply(k)).Subtract(bx.Multiply(k)).Mod(N);
|
||||
return btmp.ModPow(x.Multiply(u).Add(a), N).ToByteArrayUnsigned();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create XTEA symmetrical encryption object from sessionValue
|
||||
/// </summary>
|
||||
public static NetXtea CreateEncryption(NetPeer peer, byte[] sessionValue)
|
||||
{
|
||||
var hash = NetUtility.ComputeSHAHash(sessionValue);
|
||||
|
||||
var key = new byte[16];
|
||||
for(int i=0;i<16;i++)
|
||||
{
|
||||
key[i] = hash[i];
|
||||
for (int j = 1; j < hash.Length / 16; j++)
|
||||
key[i] ^= hash[i + (j * 16)];
|
||||
}
|
||||
|
||||
return new NetXtea(peer, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
SRMP/Lidgren.Network/NetSendResult.cs
Normal file
30
SRMP/Lidgren.Network/NetSendResult.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Result of a SendMessage call
|
||||
/// </summary>
|
||||
public enum NetSendResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Message failed to enqueue because there is no connection
|
||||
/// </summary>
|
||||
FailedNotConnected = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Message was immediately sent
|
||||
/// </summary>
|
||||
Sent = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Message was queued for delivery
|
||||
/// </summary>
|
||||
Queued = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Message was dropped immediately since too many message were queued
|
||||
/// </summary>
|
||||
Dropped = 3
|
||||
}
|
||||
}
|
||||
28
SRMP/Lidgren.Network/NetSenderChannelBase.cs
Normal file
28
SRMP/Lidgren.Network/NetSenderChannelBase.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal abstract class NetSenderChannelBase
|
||||
{
|
||||
// access this directly to queue things in this channel
|
||||
protected NetQueue<NetOutgoingMessage> m_queuedSends;
|
||||
|
||||
internal abstract int WindowSize { get; }
|
||||
|
||||
internal abstract int GetAllowedSends();
|
||||
|
||||
internal int QueuedSendsCount { get { return m_queuedSends.Count; } }
|
||||
|
||||
internal virtual bool NeedToSendMessages() { return m_queuedSends.Count > 0; }
|
||||
|
||||
public int GetFreeWindowSlots()
|
||||
{
|
||||
return GetAllowedSends() - m_queuedSends.Count;
|
||||
}
|
||||
|
||||
internal abstract NetSendResult Enqueue(NetOutgoingMessage message);
|
||||
internal abstract void SendQueuedMessages(double now);
|
||||
internal abstract void Reset();
|
||||
internal abstract void ReceiveAcknowledge(double now, int sequenceNumber);
|
||||
}
|
||||
}
|
||||
97
SRMP/Lidgren.Network/NetServer.cs
Normal file
97
SRMP/Lidgren.Network/NetServer.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Specialized version of NetPeer used for "server" peers
|
||||
/// </summary>
|
||||
public class NetServer : NetPeer
|
||||
{
|
||||
/// <summary>
|
||||
/// NetServer constructor
|
||||
/// </summary>
|
||||
public NetServer(NetPeerConfiguration config)
|
||||
: base(config)
|
||||
{
|
||||
config.AcceptIncomingConnections = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to all connections
|
||||
/// </summary>
|
||||
/// <param name="msg">The message to send</param>
|
||||
/// <param name="method">How to deliver the message</param>
|
||||
public void SendToAll(NetOutgoingMessage msg, NetDeliveryMethod method)
|
||||
{
|
||||
// Modifying m_connections will modify the list of the connections of the NetPeer. Do only reads here
|
||||
var all = m_connections;
|
||||
if (all.Count <= 0) {
|
||||
if (msg.m_isSent == false)
|
||||
Recycle(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
SendMessage(msg, all, method, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to all connections
|
||||
/// </summary>
|
||||
/// <param name="msg">The message to send</param>
|
||||
/// <param name="method">How to deliver the message</param>
|
||||
/// <param name="sequenceChannel">Which sequence channel to use for the message</param>
|
||||
public void SendToAll(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel)
|
||||
{
|
||||
// Modifying m_connections will modify the list of the connections of the NetPeer. Do only reads here
|
||||
var all = m_connections;
|
||||
if (all.Count <= 0) {
|
||||
if (msg.m_isSent == false)
|
||||
Recycle(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
SendMessage(msg, all, method, sequenceChannel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to all connections except one
|
||||
/// </summary>
|
||||
/// <param name="msg">The message to send</param>
|
||||
/// <param name="method">How to deliver the message</param>
|
||||
/// <param name="except">Don't send to this particular connection</param>
|
||||
/// <param name="sequenceChannel">Which sequence channel to use for the message</param>
|
||||
public void SendToAll(NetOutgoingMessage msg, NetConnection except, NetDeliveryMethod method, int sequenceChannel)
|
||||
{
|
||||
// Modifying m_connections will modify the list of the connections of the NetPeer. Do only reads here
|
||||
var all = m_connections;
|
||||
if (all.Count <= 0) {
|
||||
if (msg.m_isSent == false)
|
||||
Recycle(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (except == null)
|
||||
{
|
||||
SendMessage(msg, all, method, sequenceChannel);
|
||||
return;
|
||||
}
|
||||
|
||||
List<NetConnection> recipients = new List<NetConnection>(all.Count - 1);
|
||||
foreach (var conn in all)
|
||||
if (conn != except)
|
||||
recipients.Add(conn);
|
||||
|
||||
if (recipients.Count > 0)
|
||||
SendMessage(msg, recipients, method, sequenceChannel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents this object
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return "[NetServer " + ConnectionsCount + " connections]";
|
||||
}
|
||||
}
|
||||
}
|
||||
19
SRMP/Lidgren.Network/NetStoredReliableMessage.cs
Normal file
19
SRMP/Lidgren.Network/NetStoredReliableMessage.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal struct NetStoredReliableMessage
|
||||
{
|
||||
public int NumSent;
|
||||
public double LastSent;
|
||||
public NetOutgoingMessage Message;
|
||||
public int SequenceNumber;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
NumSent = 0;
|
||||
LastSent = 0.0;
|
||||
Message = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
42
SRMP/Lidgren.Network/NetTime.cs
Normal file
42
SRMP/Lidgren.Network/NetTime.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Time service
|
||||
/// </summary>
|
||||
public static partial class NetTime
|
||||
{
|
||||
/// <summary>
|
||||
/// Given seconds it will output a human friendly readable string (milliseconds if less than 60 seconds)
|
||||
/// </summary>
|
||||
public static string ToReadable(double seconds)
|
||||
{
|
||||
if (seconds > 60)
|
||||
return TimeSpan.FromSeconds(seconds).ToString();
|
||||
return (seconds * 1000.0).ToString("N2") + " ms";
|
||||
}
|
||||
}
|
||||
}
|
||||
19
SRMP/Lidgren.Network/NetTuple.cs
Normal file
19
SRMP/Lidgren.Network/NetTuple.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
// replace with BCL 4.0 Tuple<> when appropriate
|
||||
internal struct NetTuple<A, B>
|
||||
{
|
||||
public A Item1;
|
||||
public B Item2;
|
||||
|
||||
public NetTuple(A item1, B item2)
|
||||
{
|
||||
Item1 = item1;
|
||||
Item2 = item2;
|
||||
}
|
||||
}
|
||||
}
|
||||
283
SRMP/Lidgren.Network/NetUPnP.cs
Normal file
283
SRMP/Lidgren.Network/NetUPnP.cs
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
#if !__NOIPENDPOINT__
|
||||
using NetEndPoint = System.Net.IPEndPoint;
|
||||
#endif
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Status of the UPnP capabilities
|
||||
/// </summary>
|
||||
public enum UPnPStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Still discovering UPnP capabilities
|
||||
/// </summary>
|
||||
Discovering,
|
||||
|
||||
/// <summary>
|
||||
/// UPnP is not available
|
||||
/// </summary>
|
||||
NotAvailable,
|
||||
|
||||
/// <summary>
|
||||
/// UPnP is available and ready to use
|
||||
/// </summary>
|
||||
Available
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UPnP support class
|
||||
/// </summary>
|
||||
public class NetUPnP
|
||||
{
|
||||
private const int c_discoveryTimeOutMillis = 1000;
|
||||
|
||||
private string m_serviceUrl;
|
||||
private string m_serviceName = "";
|
||||
private NetPeer m_peer;
|
||||
private ManualResetEvent m_discoveryComplete = new ManualResetEvent(false);
|
||||
|
||||
internal double m_discoveryResponseDeadline;
|
||||
|
||||
private UPnPStatus m_status;
|
||||
|
||||
/// <summary>
|
||||
/// Status of the UPnP capabilities of this NetPeer
|
||||
/// </summary>
|
||||
public UPnPStatus Status { get { return m_status; } }
|
||||
|
||||
/// <summary>
|
||||
/// NetUPnP constructor
|
||||
/// </summary>
|
||||
public NetUPnP(NetPeer peer)
|
||||
{
|
||||
m_peer = peer;
|
||||
m_discoveryResponseDeadline = double.MinValue;
|
||||
}
|
||||
|
||||
internal void Discover(NetPeer peer)
|
||||
{
|
||||
string str =
|
||||
"M-SEARCH * HTTP/1.1\r\n" +
|
||||
"HOST: 239.255.255.250:1900\r\n" +
|
||||
"ST:upnp:rootdevice\r\n" +
|
||||
"MAN:\"ssdp:discover\"\r\n" +
|
||||
"MX:3\r\n\r\n";
|
||||
|
||||
m_discoveryResponseDeadline = NetTime.Now + 6.0; // arbitrarily chosen number, router gets 6 seconds to respond
|
||||
m_status = UPnPStatus.Discovering;
|
||||
|
||||
byte[] arr = System.Text.Encoding.UTF8.GetBytes(str);
|
||||
|
||||
m_peer.LogDebug("Attempting UPnP discovery");
|
||||
peer.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
|
||||
peer.RawSend(arr, 0, arr.Length, new NetEndPoint(NetUtility.GetBroadcastAddress(), 1900));
|
||||
peer.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false);
|
||||
}
|
||||
|
||||
internal void CheckForDiscoveryTimeout()
|
||||
{
|
||||
if ((m_status != UPnPStatus.Discovering) || (NetTime.Now < m_discoveryResponseDeadline))
|
||||
return;
|
||||
m_peer.LogDebug("UPnP discovery timed out");
|
||||
m_status = UPnPStatus.NotAvailable;
|
||||
}
|
||||
|
||||
internal void ExtractServiceUrl(string resp)
|
||||
{
|
||||
#if !DEBUG
|
||||
try
|
||||
{
|
||||
#endif
|
||||
XmlDocument desc = new XmlDocument();
|
||||
using (var response = WebRequest.Create(resp).GetResponse())
|
||||
desc.Load(response.GetResponseStream());
|
||||
|
||||
XmlNamespaceManager nsMgr = new XmlNamespaceManager(desc.NameTable);
|
||||
nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
|
||||
XmlNode typen = desc.SelectSingleNode("//tns:device/tns:deviceType/text()", nsMgr);
|
||||
if (!typen.Value.Contains("InternetGatewayDevice"))
|
||||
return;
|
||||
|
||||
m_serviceName = "WANIPConnection";
|
||||
XmlNode node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:" + m_serviceName + ":1\"]/tns:controlURL/text()", nsMgr);
|
||||
if (node == null)
|
||||
{
|
||||
//try another service name
|
||||
m_serviceName = "WANPPPConnection";
|
||||
node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:" + m_serviceName + ":1\"]/tns:controlURL/text()", nsMgr);
|
||||
if (node == null)
|
||||
return;
|
||||
}
|
||||
|
||||
m_serviceUrl = CombineUrls(resp, node.Value);
|
||||
m_peer.LogDebug("UPnP service ready");
|
||||
m_status = UPnPStatus.Available;
|
||||
m_discoveryComplete.Set();
|
||||
#if !DEBUG
|
||||
}
|
||||
catch
|
||||
{
|
||||
m_peer.LogVerbose("Exception ignored trying to parse UPnP XML response");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private static string CombineUrls(string gatewayURL, string subURL)
|
||||
{
|
||||
// Is Control URL an absolute URL?
|
||||
if ((subURL.Contains("http:")) || (subURL.Contains(".")))
|
||||
return subURL;
|
||||
|
||||
gatewayURL = gatewayURL.Replace("http://", ""); // strip any protocol
|
||||
int n = gatewayURL.IndexOf("/");
|
||||
if (n != -1)
|
||||
gatewayURL = gatewayURL.Substring(0, n); // Use first portion of URL
|
||||
return "http://" + gatewayURL + subURL;
|
||||
}
|
||||
|
||||
private bool CheckAvailability()
|
||||
{
|
||||
switch (m_status)
|
||||
{
|
||||
case UPnPStatus.NotAvailable:
|
||||
return false;
|
||||
case UPnPStatus.Available:
|
||||
return true;
|
||||
case UPnPStatus.Discovering:
|
||||
if (m_discoveryComplete.WaitOne(c_discoveryTimeOutMillis))
|
||||
return true;
|
||||
if (NetTime.Now > m_discoveryResponseDeadline)
|
||||
m_status = UPnPStatus.NotAvailable;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a forwarding rule to the router using UPnP
|
||||
/// </summary>
|
||||
/// <param name="externalPort">The external, WAN facing, port</param>
|
||||
/// <param name="description">A description for the port forwarding rule</param>
|
||||
/// <param name="internalPort">The port on the client machine to send traffic to</param>
|
||||
public bool ForwardPort(int externalPort, string description, int internalPort = 0)
|
||||
{
|
||||
if (!CheckAvailability())
|
||||
return false;
|
||||
|
||||
IPAddress mask;
|
||||
var client = NetUtility.GetMyAddress(out mask);
|
||||
if (client == null)
|
||||
return false;
|
||||
|
||||
if (internalPort == 0)
|
||||
internalPort = externalPort;
|
||||
|
||||
try
|
||||
{
|
||||
SOAPRequest(m_serviceUrl,
|
||||
"<u:AddPortMapping xmlns:u=\"urn:schemas-upnp-org:service:" + m_serviceName + ":1\">" +
|
||||
"<NewRemoteHost></NewRemoteHost>" +
|
||||
"<NewExternalPort>" + externalPort.ToString() + "</NewExternalPort>" +
|
||||
"<NewProtocol>" + ProtocolType.Udp.ToString().ToUpper(System.Globalization.CultureInfo.InvariantCulture) + "</NewProtocol>" +
|
||||
"<NewInternalPort>" + internalPort.ToString() + "</NewInternalPort>" +
|
||||
"<NewInternalClient>" + client.ToString() + "</NewInternalClient>" +
|
||||
"<NewEnabled>1</NewEnabled>" +
|
||||
"<NewPortMappingDescription>" + description + "</NewPortMappingDescription>" +
|
||||
"<NewLeaseDuration>0</NewLeaseDuration>" +
|
||||
"</u:AddPortMapping>",
|
||||
"AddPortMapping");
|
||||
|
||||
m_peer.LogDebug("Sent UPnP port forward request");
|
||||
NetUtility.Sleep(50);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
m_peer.LogWarning("UPnP port forward failed: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a forwarding rule from the router using UPnP
|
||||
/// </summary>
|
||||
/// <param name="externalPort">The external, 'internet facing', port</param>
|
||||
public bool DeleteForwardingRule(int externalPort)
|
||||
{
|
||||
if (!CheckAvailability())
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
SOAPRequest(m_serviceUrl,
|
||||
"<u:DeletePortMapping xmlns:u=\"urn:schemas-upnp-org:service:" + m_serviceName + ":1\">" +
|
||||
"<NewRemoteHost>" +
|
||||
"</NewRemoteHost>" +
|
||||
"<NewExternalPort>" + externalPort + "</NewExternalPort>" +
|
||||
"<NewProtocol>" + ProtocolType.Udp.ToString().ToUpper(System.Globalization.CultureInfo.InvariantCulture) + "</NewProtocol>" +
|
||||
"</u:DeletePortMapping>", "DeletePortMapping");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
m_peer.LogWarning("UPnP delete forwarding rule failed: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the extern ip using UPnP
|
||||
/// </summary>
|
||||
public IPAddress GetExternalIP()
|
||||
{
|
||||
if (!CheckAvailability())
|
||||
return null;
|
||||
try
|
||||
{
|
||||
XmlDocument xdoc = SOAPRequest(m_serviceUrl, "<u:GetExternalIPAddress xmlns:u=\"urn:schemas-upnp-org:service:" + m_serviceName + ":1\">" +
|
||||
"</u:GetExternalIPAddress>", "GetExternalIPAddress");
|
||||
XmlNamespaceManager nsMgr = new XmlNamespaceManager(xdoc.NameTable);
|
||||
nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0");
|
||||
string IP = xdoc.SelectSingleNode("//NewExternalIPAddress/text()", nsMgr).Value;
|
||||
return IPAddress.Parse(IP);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
m_peer.LogWarning("Failed to get external IP: " + ex.Message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private XmlDocument SOAPRequest(string url, string soap, string function)
|
||||
{
|
||||
string req = "<?xml version=\"1.0\"?>" +
|
||||
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" +
|
||||
"<s:Body>" +
|
||||
soap +
|
||||
"</s:Body>" +
|
||||
"</s:Envelope>";
|
||||
WebRequest r = HttpWebRequest.Create(url);
|
||||
r.Method = "POST";
|
||||
byte[] b = System.Text.Encoding.UTF8.GetBytes(req);
|
||||
r.Headers.Add("SOAPACTION", "\"urn:schemas-upnp-org:service:" + m_serviceName + ":1#" + function + "\"");
|
||||
r.ContentType = "text/xml; charset=\"utf-8\"";
|
||||
r.ContentLength = b.Length;
|
||||
r.GetRequestStream().Write(b, 0, b.Length);
|
||||
using (WebResponse wres = r.GetResponse()) {
|
||||
XmlDocument resp = new XmlDocument();
|
||||
Stream ress = wres.GetResponseStream();
|
||||
resp.Load(ress);
|
||||
return resp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
142
SRMP/Lidgren.Network/NetUnreliableSenderChannel.cs
Normal file
142
SRMP/Lidgren.Network/NetUnreliableSenderChannel.cs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Sender part of Selective repeat ARQ for a particular NetChannel
|
||||
/// </summary>
|
||||
internal sealed class NetUnreliableSenderChannel : NetSenderChannelBase
|
||||
{
|
||||
private NetConnection m_connection;
|
||||
private int m_windowStart;
|
||||
private int m_windowSize;
|
||||
private int m_sendStart;
|
||||
private bool m_doFlowControl;
|
||||
|
||||
private NetBitVector m_receivedAcks;
|
||||
|
||||
internal override int WindowSize { get { return m_windowSize; } }
|
||||
|
||||
internal NetUnreliableSenderChannel(NetConnection connection, int windowSize, NetDeliveryMethod method)
|
||||
{
|
||||
m_connection = connection;
|
||||
m_windowSize = windowSize;
|
||||
m_windowStart = 0;
|
||||
m_sendStart = 0;
|
||||
m_receivedAcks = new NetBitVector(NetConstants.NumSequenceNumbers);
|
||||
m_queuedSends = new NetQueue<NetOutgoingMessage>(8);
|
||||
|
||||
m_doFlowControl = true;
|
||||
if (method == NetDeliveryMethod.Unreliable && connection.Peer.Configuration.SuppressUnreliableUnorderedAcks == true)
|
||||
m_doFlowControl = false;
|
||||
}
|
||||
|
||||
internal override int GetAllowedSends()
|
||||
{
|
||||
if (!m_doFlowControl)
|
||||
return int.MaxValue; // always allowed to send without flow control!
|
||||
int retval = m_windowSize - ((m_sendStart + NetConstants.NumSequenceNumbers) - m_windowStart) % m_windowSize;
|
||||
NetException.Assert(retval >= 0 && retval <= m_windowSize);
|
||||
return retval;
|
||||
}
|
||||
|
||||
internal override void Reset()
|
||||
{
|
||||
m_receivedAcks.Clear();
|
||||
m_queuedSends.Clear();
|
||||
m_windowStart = 0;
|
||||
m_sendStart = 0;
|
||||
}
|
||||
|
||||
internal override NetSendResult Enqueue(NetOutgoingMessage message)
|
||||
{
|
||||
int queueLen = m_queuedSends.Count + 1;
|
||||
int left = GetAllowedSends();
|
||||
if (queueLen > left || (message.LengthBytes > m_connection.m_currentMTU && m_connection.m_peerConfiguration.UnreliableSizeBehaviour == NetUnreliableSizeBehaviour.DropAboveMTU))
|
||||
{
|
||||
// drop message
|
||||
return NetSendResult.Dropped;
|
||||
}
|
||||
|
||||
m_queuedSends.Enqueue(message);
|
||||
m_connection.m_peer.m_needFlushSendQueue = true; // a race condition to this variable will simply result in a single superflous call to FlushSendQueue()
|
||||
return NetSendResult.Sent;
|
||||
}
|
||||
|
||||
// call this regularely
|
||||
internal override void SendQueuedMessages(double now)
|
||||
{
|
||||
int num = GetAllowedSends();
|
||||
if (num < 1)
|
||||
return;
|
||||
|
||||
// queued sends
|
||||
while (num > 0 && m_queuedSends.Count > 0)
|
||||
{
|
||||
NetOutgoingMessage om;
|
||||
if (m_queuedSends.TryDequeue(out om))
|
||||
ExecuteSend(om);
|
||||
num--;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteSend(NetOutgoingMessage message)
|
||||
{
|
||||
m_connection.m_peer.VerifyNetworkThread();
|
||||
|
||||
int seqNr = m_sendStart;
|
||||
m_sendStart = (m_sendStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
|
||||
m_connection.QueueSendMessage(message, seqNr);
|
||||
|
||||
if (message.m_recyclingCount <= 0)
|
||||
m_connection.m_peer.Recycle(message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// remoteWindowStart is remote expected sequence number; everything below this has arrived properly
|
||||
// seqNr is the actual nr received
|
||||
internal override void ReceiveAcknowledge(double now, int seqNr)
|
||||
{
|
||||
if (m_doFlowControl == false)
|
||||
{
|
||||
// we have no use for acks on this channel since we don't respect the window anyway
|
||||
m_connection.m_peer.LogWarning("SuppressUnreliableUnorderedAcks sender/receiver mismatch!");
|
||||
return;
|
||||
}
|
||||
|
||||
// late (dupe), on time or early ack?
|
||||
int relate = NetUtility.RelativeSequenceNumber(seqNr, m_windowStart);
|
||||
|
||||
if (relate < 0)
|
||||
{
|
||||
//m_connection.m_peer.LogDebug("Received late/dupe ack for #" + seqNr);
|
||||
return; // late/duplicate ack
|
||||
}
|
||||
|
||||
if (relate == 0)
|
||||
{
|
||||
//m_connection.m_peer.LogDebug("Received right-on-time ack for #" + seqNr);
|
||||
|
||||
// ack arrived right on time
|
||||
NetException.Assert(seqNr == m_windowStart);
|
||||
|
||||
m_receivedAcks[m_windowStart] = false;
|
||||
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Advance window to this position
|
||||
m_receivedAcks[seqNr] = true;
|
||||
|
||||
while (m_windowStart != seqNr)
|
||||
{
|
||||
m_receivedAcks[m_windowStart] = false;
|
||||
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
SRMP/Lidgren.Network/NetUnreliableSequencedReceiver.cs
Normal file
33
SRMP/Lidgren.Network/NetUnreliableSequencedReceiver.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal sealed class NetUnreliableSequencedReceiver : NetReceiverChannelBase
|
||||
{
|
||||
private int m_lastReceivedSequenceNumber = -1;
|
||||
|
||||
public NetUnreliableSequencedReceiver(NetConnection connection)
|
||||
: base(connection)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void ReceiveMessage(NetIncomingMessage msg)
|
||||
{
|
||||
int nr = msg.m_sequenceNumber;
|
||||
|
||||
// ack no matter what
|
||||
m_connection.QueueAck(msg.m_receivedMessageType, nr);
|
||||
|
||||
int relate = NetUtility.RelativeSequenceNumber(nr, m_lastReceivedSequenceNumber + 1);
|
||||
if (relate < 0)
|
||||
{
|
||||
m_connection.m_statistics.MessageDropped();
|
||||
m_peer.LogVerbose("Received message #" + nr + " DROPPING DUPLICATE");
|
||||
return; // drop if late
|
||||
}
|
||||
|
||||
m_lastReceivedSequenceNumber = nr;
|
||||
m_peer.ReleaseMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
SRMP/Lidgren.Network/NetUnreliableUnorderedReceiver.cs
Normal file
23
SRMP/Lidgren.Network/NetUnreliableUnorderedReceiver.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal sealed class NetUnreliableUnorderedReceiver : NetReceiverChannelBase
|
||||
{
|
||||
private bool m_doFlowControl;
|
||||
|
||||
public NetUnreliableUnorderedReceiver(NetConnection connection)
|
||||
: base(connection)
|
||||
{
|
||||
m_doFlowControl = connection.Peer.Configuration.SuppressUnreliableUnorderedAcks == false;
|
||||
}
|
||||
|
||||
internal override void ReceiveMessage(NetIncomingMessage msg)
|
||||
{
|
||||
if (m_doFlowControl)
|
||||
m_connection.QueueAck(msg.m_receivedMessageType, msg.m_sequenceNumber);
|
||||
|
||||
m_peer.ReleaseMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
469
SRMP/Lidgren.Network/NetUtility.cs
Normal file
469
SRMP/Lidgren.Network/NetUtility.cs
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#if !__NOIPENDPOINT__
|
||||
using NetEndPoint = System.Net.IPEndPoint;
|
||||
using NetAddress = System.Net.IPAddress;
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility methods
|
||||
/// </summary>
|
||||
public static partial class NetUtility
|
||||
{
|
||||
private static readonly bool IsMono = Type.GetType("Mono.Runtime") != null;
|
||||
|
||||
/// <summary>
|
||||
/// Resolve endpoint callback
|
||||
/// </summary>
|
||||
public delegate void ResolveEndPointCallback(NetEndPoint endPoint);
|
||||
|
||||
/// <summary>
|
||||
/// Resolve address callback
|
||||
/// </summary>
|
||||
public delegate void ResolveAddressCallback(NetAddress adr);
|
||||
|
||||
/// <summary>
|
||||
/// Get IPv4 endpoint from notation (xxx.xxx.xxx.xxx) or hostname and port number (asynchronous version)
|
||||
/// </summary>
|
||||
public static void ResolveAsync(string ipOrHost, int port, ResolveEndPointCallback callback)
|
||||
{
|
||||
ResolveAsync(ipOrHost, delegate(NetAddress adr)
|
||||
{
|
||||
if (adr == null)
|
||||
{
|
||||
callback(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(new NetEndPoint(adr, port));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get IPv4 endpoint from notation (xxx.xxx.xxx.xxx) or hostname and port number
|
||||
/// </summary>
|
||||
public static NetEndPoint Resolve(string ipOrHost, int port)
|
||||
{
|
||||
var adr = Resolve(ipOrHost);
|
||||
return adr == null ? null : new NetEndPoint(adr, port);
|
||||
}
|
||||
|
||||
private static IPAddress s_broadcastAddress;
|
||||
public static IPAddress GetCachedBroadcastAddress()
|
||||
{
|
||||
if (s_broadcastAddress == null)
|
||||
s_broadcastAddress = GetBroadcastAddress();
|
||||
return s_broadcastAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get IPv4 address from notation (xxx.xxx.xxx.xxx) or hostname (asynchronous version)
|
||||
/// </summary>
|
||||
public static void ResolveAsync(string ipOrHost, ResolveAddressCallback callback)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ipOrHost))
|
||||
throw new ArgumentException("Supplied string must not be empty", "ipOrHost");
|
||||
|
||||
ipOrHost = ipOrHost.Trim();
|
||||
|
||||
NetAddress ipAddress = null;
|
||||
if (NetAddress.TryParse(ipOrHost, out ipAddress))
|
||||
{
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
callback(ipAddress);
|
||||
return;
|
||||
}
|
||||
throw new ArgumentException("This method will not currently resolve other than ipv4 addresses");
|
||||
}
|
||||
|
||||
// ok must be a host name
|
||||
IPHostEntry entry;
|
||||
try
|
||||
{
|
||||
Dns.BeginGetHostEntry(ipOrHost, delegate(IAsyncResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
entry = Dns.EndGetHostEntry(result);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
if (ex.SocketErrorCode == SocketError.HostNotFound)
|
||||
{
|
||||
//LogWrite(string.Format(CultureInfo.InvariantCulture, "Failed to resolve host '{0}'.", ipOrHost));
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// check each entry for a valid IP address
|
||||
foreach (var ipCurrent in entry.AddressList)
|
||||
{
|
||||
if (ipCurrent.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
callback(ipCurrent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
callback(null);
|
||||
}, null);
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
if (ex.SocketErrorCode == SocketError.HostNotFound)
|
||||
{
|
||||
//LogWrite(string.Format(CultureInfo.InvariantCulture, "Failed to resolve host '{0}'.", ipOrHost));
|
||||
callback(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get IPv4 address from notation (xxx.xxx.xxx.xxx) or hostname
|
||||
/// </summary>
|
||||
public static NetAddress Resolve(string ipOrHost)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ipOrHost))
|
||||
throw new ArgumentException("Supplied string must not be empty", "ipOrHost");
|
||||
|
||||
ipOrHost = ipOrHost.Trim();
|
||||
|
||||
NetAddress ipAddress = null;
|
||||
if (NetAddress.TryParse(ipOrHost, out ipAddress))
|
||||
{
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
|
||||
return ipAddress;
|
||||
throw new ArgumentException("This method will not currently resolve other than ipv4 addresses");
|
||||
}
|
||||
|
||||
// ok must be a host name
|
||||
try
|
||||
{
|
||||
var addresses = Dns.GetHostAddresses(ipOrHost);
|
||||
if (addresses == null)
|
||||
return null;
|
||||
foreach (var address in addresses)
|
||||
{
|
||||
if (address.AddressFamily == AddressFamily.InterNetwork)
|
||||
return address;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
if (ex.SocketErrorCode == SocketError.HostNotFound)
|
||||
{
|
||||
//LogWrite(string.Format(CultureInfo.InvariantCulture, "Failed to resolve host '{0}'.", ipOrHost));
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a hex string from an Int64 value
|
||||
/// </summary>
|
||||
public static string ToHexString(long data)
|
||||
{
|
||||
return ToHexString(BitConverter.GetBytes(data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a hex string from an array of bytes
|
||||
/// </summary>
|
||||
public static string ToHexString(byte[] data)
|
||||
{
|
||||
return ToHexString(data, 0, data.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a hex string from an array of bytes
|
||||
/// </summary>
|
||||
public static string ToHexString(byte[] data, int offset, int length)
|
||||
{
|
||||
char[] c = new char[length * 2];
|
||||
byte b;
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
b = ((byte)(data[offset + i] >> 4));
|
||||
c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30);
|
||||
b = ((byte)(data[offset + i] & 0xF));
|
||||
c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30);
|
||||
}
|
||||
return new string(c);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the endpoint supplied is on the same subnet as this host
|
||||
/// </summary>
|
||||
public static bool IsLocal(NetEndPoint endPoint)
|
||||
{
|
||||
if (endPoint == null)
|
||||
return false;
|
||||
return IsLocal(endPoint.Address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the IPAddress supplied is on the same subnet as this host
|
||||
/// </summary>
|
||||
public static bool IsLocal(NetAddress remote)
|
||||
{
|
||||
NetAddress mask;
|
||||
var local = GetMyAddress(out mask);
|
||||
|
||||
if (mask == null)
|
||||
return false;
|
||||
|
||||
uint maskBits = BitConverter.ToUInt32(mask.GetAddressBytes(), 0);
|
||||
uint remoteBits = BitConverter.ToUInt32(remote.GetAddressBytes(), 0);
|
||||
uint localBits = BitConverter.ToUInt32(local.GetAddressBytes(), 0);
|
||||
|
||||
// compare network portions
|
||||
return ((remoteBits & maskBits) == (localBits & maskBits));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns how many bits are necessary to hold a certain number
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public static int BitsToHoldUInt(uint value)
|
||||
{
|
||||
int bits = 1;
|
||||
while ((value >>= 1) != 0)
|
||||
bits++;
|
||||
return bits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns how many bits are necessary to hold a certain number
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public static int BitsToHoldUInt64(ulong value)
|
||||
{
|
||||
int bits = 1;
|
||||
while ((value >>= 1) != 0)
|
||||
bits++;
|
||||
return bits;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns how many bytes are required to hold a certain number of bits
|
||||
/// </summary>
|
||||
public static int BytesToHoldBits(int numBits)
|
||||
{
|
||||
return (numBits + 7) / 8;
|
||||
}
|
||||
|
||||
internal static UInt32 SwapByteOrder(UInt32 value)
|
||||
{
|
||||
return
|
||||
((value & 0xff000000) >> 24) |
|
||||
((value & 0x00ff0000) >> 8) |
|
||||
((value & 0x0000ff00) << 8) |
|
||||
((value & 0x000000ff) << 24);
|
||||
}
|
||||
|
||||
internal static UInt64 SwapByteOrder(UInt64 value)
|
||||
{
|
||||
return
|
||||
((value & 0xff00000000000000L) >> 56) |
|
||||
((value & 0x00ff000000000000L) >> 40) |
|
||||
((value & 0x0000ff0000000000L) >> 24) |
|
||||
((value & 0x000000ff00000000L) >> 8) |
|
||||
((value & 0x00000000ff000000L) << 8) |
|
||||
((value & 0x0000000000ff0000L) << 24) |
|
||||
((value & 0x000000000000ff00L) << 40) |
|
||||
((value & 0x00000000000000ffL) << 56);
|
||||
}
|
||||
|
||||
internal static bool CompareElements(byte[] one, byte[] two)
|
||||
{
|
||||
if (one.Length != two.Length)
|
||||
return false;
|
||||
for (int i = 0; i < one.Length; i++)
|
||||
if (one[i] != two[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a hexadecimal string to a byte array
|
||||
/// </summary>
|
||||
public static byte[] ToByteArray(String hexString)
|
||||
{
|
||||
byte[] retval = new byte[hexString.Length / 2];
|
||||
for (int i = 0; i < hexString.Length; i += 2)
|
||||
retval[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a number of bytes to a shorter, more readable string representation
|
||||
/// </summary>
|
||||
public static string ToHumanReadable(long bytes)
|
||||
{
|
||||
if (bytes < 4000) // 1-4 kb is printed in bytes
|
||||
return bytes + " bytes";
|
||||
if (bytes < 1000 * 1000) // 4-999 kb is printed in kb
|
||||
return Math.Round(((double)bytes / 1000.0), 2) + " kilobytes";
|
||||
return Math.Round(((double)bytes / (1000.0 * 1000.0)), 2) + " megabytes"; // else megabytes
|
||||
}
|
||||
|
||||
internal static int RelativeSequenceNumber(int nr, int expected)
|
||||
{
|
||||
return (nr - expected + NetConstants.NumSequenceNumbers + (NetConstants.NumSequenceNumbers / 2)) % NetConstants.NumSequenceNumbers - (NetConstants.NumSequenceNumbers / 2);
|
||||
|
||||
// old impl:
|
||||
//int retval = ((nr + NetConstants.NumSequenceNumbers) - expected) % NetConstants.NumSequenceNumbers;
|
||||
//if (retval > (NetConstants.NumSequenceNumbers / 2))
|
||||
// retval -= NetConstants.NumSequenceNumbers;
|
||||
//return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the window size used internally in the library for a certain delivery method
|
||||
/// </summary>
|
||||
public static int GetWindowSize(NetDeliveryMethod method)
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
case NetDeliveryMethod.Unknown:
|
||||
return 0;
|
||||
|
||||
case NetDeliveryMethod.Unreliable:
|
||||
case NetDeliveryMethod.UnreliableSequenced:
|
||||
return NetConstants.UnreliableWindowSize;
|
||||
|
||||
case NetDeliveryMethod.ReliableOrdered:
|
||||
return NetConstants.ReliableOrderedWindowSize;
|
||||
|
||||
case NetDeliveryMethod.ReliableSequenced:
|
||||
case NetDeliveryMethod.ReliableUnordered:
|
||||
default:
|
||||
return NetConstants.DefaultWindowSize;
|
||||
}
|
||||
}
|
||||
|
||||
// shell sort
|
||||
internal static void SortMembersList(System.Reflection.MemberInfo[] list)
|
||||
{
|
||||
int h;
|
||||
int j;
|
||||
System.Reflection.MemberInfo tmp;
|
||||
|
||||
h = 1;
|
||||
while (h * 3 + 1 <= list.Length)
|
||||
h = 3 * h + 1;
|
||||
|
||||
while (h > 0)
|
||||
{
|
||||
for (int i = h - 1; i < list.Length; i++)
|
||||
{
|
||||
tmp = list[i];
|
||||
j = i;
|
||||
while (true)
|
||||
{
|
||||
if (j >= h)
|
||||
{
|
||||
if (string.Compare(list[j - h].Name, tmp.Name, StringComparison.InvariantCulture) > 0)
|
||||
{
|
||||
list[j] = list[j - h];
|
||||
j -= h;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
list[j] = tmp;
|
||||
}
|
||||
h /= 3;
|
||||
}
|
||||
}
|
||||
|
||||
internal static NetDeliveryMethod GetDeliveryMethod(NetMessageType mtp)
|
||||
{
|
||||
if (mtp >= NetMessageType.UserReliableOrdered1)
|
||||
return NetDeliveryMethod.ReliableOrdered;
|
||||
else if (mtp >= NetMessageType.UserReliableSequenced1)
|
||||
return NetDeliveryMethod.ReliableSequenced;
|
||||
else if (mtp >= NetMessageType.UserReliableUnordered)
|
||||
return NetDeliveryMethod.ReliableUnordered;
|
||||
else if (mtp >= NetMessageType.UserSequenced1)
|
||||
return NetDeliveryMethod.UnreliableSequenced;
|
||||
return NetDeliveryMethod.Unreliable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a comma delimited string from a lite of items
|
||||
/// </summary>
|
||||
public static string MakeCommaDelimitedList<T>(IList<T> list)
|
||||
{
|
||||
var cnt = list.Count;
|
||||
StringBuilder bdr = new StringBuilder(cnt * 5); // educated guess
|
||||
for(int i=0;i<cnt;i++)
|
||||
{
|
||||
bdr.Append(list[i].ToString());
|
||||
if (i != cnt - 1)
|
||||
bdr.Append(", ");
|
||||
}
|
||||
return bdr.ToString();
|
||||
}
|
||||
|
||||
public static byte[] ComputeSHAHash(byte[] bytes)
|
||||
{
|
||||
// this is defined in the platform specific files
|
||||
return ComputeSHAHash(bytes, 0, bytes.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
91
SRMP/Lidgren.Network/Platform/PlatformAndroid.cs
Normal file
91
SRMP/Lidgren.Network/Platform/PlatformAndroid.cs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#if __ANDROID__
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using System.Net;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public static partial class NetUtility
|
||||
{
|
||||
private static byte[] s_randomMacBytes;
|
||||
|
||||
static NetUtility()
|
||||
{
|
||||
}
|
||||
|
||||
[CLSCompliant(false)]
|
||||
public static ulong GetPlatformSeed(int seedInc)
|
||||
{
|
||||
ulong seed = (ulong)Environment.TickCount + (ulong)seedInc;
|
||||
return seed ^ ((ulong)(new object().GetHashCode()) << 32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets my local IPv4 address (not necessarily external) and subnet mask
|
||||
/// </summary>
|
||||
public static IPAddress GetMyAddress(out IPAddress mask)
|
||||
{
|
||||
mask = null;
|
||||
try
|
||||
{
|
||||
Android.Net.Wifi.WifiManager wifi = (Android.Net.Wifi.WifiManager)Android.App.Application.Context.GetSystemService(Android.App.Activity.WifiService);
|
||||
if (!wifi.IsWifiEnabled)
|
||||
return null;
|
||||
|
||||
var dhcp = wifi.DhcpInfo;
|
||||
int addr = dhcp.IpAddress;
|
||||
byte[] quads = new byte[4];
|
||||
for (int k = 0; k < 4; k++)
|
||||
quads[k] = (byte)((addr >> k * 8) & 0xFF);
|
||||
return new IPAddress(quads);
|
||||
}
|
||||
catch // Catch Access Denied errors
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] GetMacAddressBytes()
|
||||
{
|
||||
if (s_randomMacBytes == null)
|
||||
{
|
||||
s_randomMacBytes = new byte[8];
|
||||
MWCRandom.Instance.NextBytes(s_randomMacBytes);
|
||||
}
|
||||
return s_randomMacBytes;
|
||||
}
|
||||
|
||||
public static void Sleep(int milliseconds)
|
||||
{
|
||||
System.Threading.Thread.Sleep(milliseconds);
|
||||
}
|
||||
|
||||
public static IPAddress GetBroadcastAddress()
|
||||
{
|
||||
return IPAddress.Broadcast;
|
||||
}
|
||||
|
||||
public static IPAddress CreateAddressFromBytes(byte[] bytes)
|
||||
{
|
||||
return new IPAddress(bytes);
|
||||
}
|
||||
|
||||
private static readonly SHA1 s_sha = SHA1.Create();
|
||||
public static byte[] ComputeSHAHash(byte[] bytes, int offset, int count)
|
||||
{
|
||||
return s_sha.ComputeHash(bytes, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class NetTime
|
||||
{
|
||||
private static readonly long s_timeInitialized = Environment.TickCount;
|
||||
|
||||
/// <summary>
|
||||
/// Get number of seconds since the application started
|
||||
/// </summary>
|
||||
public static double Now { get { return (double)((uint)Environment.TickCount - s_timeInitialized) / 1000.0; } }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
98
SRMP/Lidgren.Network/Platform/PlatformConstrained.cs
Normal file
98
SRMP/Lidgren.Network/Platform/PlatformConstrained.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
#if __CONSTRAINED__ || UNITY_STANDALONE_LINUX || UNITY
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public static partial class NetUtility
|
||||
{
|
||||
private static byte[] s_randomMacBytes;
|
||||
|
||||
static NetUtility()
|
||||
{
|
||||
}
|
||||
|
||||
[CLSCompliant(false)]
|
||||
public static ulong GetPlatformSeed(int seedInc)
|
||||
{
|
||||
ulong seed = (ulong)Environment.TickCount + (ulong)seedInc;
|
||||
return seed ^ ((ulong)(new object().GetHashCode()) << 32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets my local IPv4 address (not necessarily external) and subnet mask
|
||||
/// </summary>
|
||||
public static IPAddress GetMyAddress(out IPAddress mask)
|
||||
{
|
||||
mask = null;
|
||||
var host = Dns.GetHostEntry(Dns.GetHostName());
|
||||
foreach (var ip in host.AddressList)
|
||||
{
|
||||
if (ip.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
#if UNITY_ANDROID || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_IOS || UNITY
|
||||
try
|
||||
{
|
||||
if (!(UnityEngine.Application.internetReachability == UnityEngine.NetworkReachability.NotReachable))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return IPAddress.Parse(UnityEngine.Network.player.externalIP);
|
||||
}
|
||||
catch // Catch Access Denied errors
|
||||
{
|
||||
return null;
|
||||
}
|
||||
#endif
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte[] GetMacAddressBytes()
|
||||
{
|
||||
if (s_randomMacBytes == null)
|
||||
{
|
||||
s_randomMacBytes = new byte[8];
|
||||
MWCRandom.Instance.NextBytes(s_randomMacBytes);
|
||||
}
|
||||
return s_randomMacBytes;
|
||||
}
|
||||
|
||||
public static IPAddress GetBroadcastAddress()
|
||||
{
|
||||
return IPAddress.Broadcast;
|
||||
}
|
||||
|
||||
public static void Sleep(int milliseconds)
|
||||
{
|
||||
System.Threading.Thread.Sleep(milliseconds);
|
||||
}
|
||||
|
||||
public static IPAddress CreateAddressFromBytes(byte[] bytes)
|
||||
{
|
||||
return new IPAddress(bytes);
|
||||
}
|
||||
|
||||
private static readonly SHA1 s_sha = SHA1.Create();
|
||||
public static byte[] ComputeSHAHash(byte[] bytes, int offset, int count)
|
||||
{
|
||||
return s_sha.ComputeHash(bytes, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class NetTime
|
||||
{
|
||||
private static readonly long s_timeInitialized = Environment.TickCount;
|
||||
|
||||
/// <summary>
|
||||
/// Get number of seconds since the application started
|
||||
/// </summary>
|
||||
public static double Now { get { return (double)((uint)Environment.TickCount - s_timeInitialized) / 1000.0; } }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
183
SRMP/Lidgren.Network/Platform/PlatformUnityExtras.cs
Normal file
183
SRMP/Lidgren.Network/Platform/PlatformUnityExtras.cs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
using SRMultiplayer;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetBuffer
|
||||
{
|
||||
public void Write(Vector2 vector)
|
||||
{
|
||||
Write(vector.x);
|
||||
Write(vector.y);
|
||||
}
|
||||
|
||||
public Vector2 ReadVector2()
|
||||
{
|
||||
return new Vector2(
|
||||
x: ReadSingle(),
|
||||
y: ReadSingle());
|
||||
}
|
||||
|
||||
public void Write(Vector3 vector)
|
||||
{
|
||||
Write(vector.x);
|
||||
Write(vector.y);
|
||||
Write(vector.z);
|
||||
}
|
||||
|
||||
public Vector3 ReadVector3()
|
||||
{
|
||||
return new Vector3(
|
||||
x: ReadSingle(),
|
||||
y: ReadSingle(),
|
||||
z: ReadSingle());
|
||||
}
|
||||
|
||||
public void Write(Vector4 vector)
|
||||
{
|
||||
Write(vector.x);
|
||||
Write(vector.y);
|
||||
Write(vector.z);
|
||||
Write(vector.w);
|
||||
}
|
||||
|
||||
public Vector4 ReadVector4()
|
||||
{
|
||||
return new Vector4(
|
||||
x: ReadSingle(),
|
||||
y: ReadSingle(),
|
||||
z: ReadSingle(),
|
||||
w: ReadSingle());
|
||||
}
|
||||
|
||||
public void Write(Quaternion quaternion)
|
||||
{
|
||||
uint comp = Compression.CompressQuaternion(quaternion);
|
||||
Write(comp);
|
||||
}
|
||||
|
||||
public Quaternion ReadQuaternion()
|
||||
{
|
||||
return Compression.DecompressQuaternion(ReadUInt32());
|
||||
}
|
||||
|
||||
public void WriteRgbColor(Color32 color)
|
||||
{
|
||||
Write(color.r);
|
||||
Write(color.g);
|
||||
Write(color.b);
|
||||
}
|
||||
|
||||
public Color32 ReadRgbColor()
|
||||
{
|
||||
return new Color32(
|
||||
r: ReadByte(),
|
||||
g: ReadByte(),
|
||||
b: ReadByte(),
|
||||
a: byte.MaxValue);
|
||||
}
|
||||
|
||||
public void WriteRgbaColor(Color32 color)
|
||||
{
|
||||
Write(color.r);
|
||||
Write(color.g);
|
||||
Write(color.b);
|
||||
Write(color.a);
|
||||
}
|
||||
|
||||
public Color32 ReadRgbaColor()
|
||||
{
|
||||
return new Color32(
|
||||
r: ReadByte(),
|
||||
g: ReadByte(),
|
||||
b: ReadByte(),
|
||||
a: ReadByte());
|
||||
}
|
||||
|
||||
public void Write(Ray ray)
|
||||
{
|
||||
Write(ray.direction);
|
||||
Write(ray.origin);
|
||||
}
|
||||
|
||||
public Ray ReadRay()
|
||||
{
|
||||
return new Ray(
|
||||
direction: ReadVector3(),
|
||||
origin: ReadVector3());
|
||||
}
|
||||
|
||||
public void Write(Plane plane)
|
||||
{
|
||||
Write(plane.normal);
|
||||
Write(plane.distance);
|
||||
}
|
||||
|
||||
public Plane ReadPlane()
|
||||
{
|
||||
return new Plane(
|
||||
inNormal: ReadVector3(),
|
||||
d: ReadSingle());
|
||||
}
|
||||
|
||||
public void Write(Matrix4x4 matrix)
|
||||
{
|
||||
Write(matrix.m00);
|
||||
Write(matrix.m01);
|
||||
Write(matrix.m02);
|
||||
Write(matrix.m03);
|
||||
Write(matrix.m10);
|
||||
Write(matrix.m11);
|
||||
Write(matrix.m12);
|
||||
Write(matrix.m13);
|
||||
Write(matrix.m20);
|
||||
Write(matrix.m21);
|
||||
Write(matrix.m22);
|
||||
Write(matrix.m23);
|
||||
Write(matrix.m30);
|
||||
Write(matrix.m31);
|
||||
Write(matrix.m32);
|
||||
Write(matrix.m33);
|
||||
}
|
||||
|
||||
public Matrix4x4 ReadMatrix4X4()
|
||||
{
|
||||
return new Matrix4x4
|
||||
{
|
||||
m00 = ReadSingle(),
|
||||
m01 = ReadSingle(),
|
||||
m02 = ReadSingle(),
|
||||
m03 = ReadSingle(),
|
||||
m10 = ReadSingle(),
|
||||
m11 = ReadSingle(),
|
||||
m12 = ReadSingle(),
|
||||
m13 = ReadSingle(),
|
||||
m20 = ReadSingle(),
|
||||
m21 = ReadSingle(),
|
||||
m22 = ReadSingle(),
|
||||
m23 = ReadSingle(),
|
||||
m30 = ReadSingle(),
|
||||
m31 = ReadSingle(),
|
||||
m32 = ReadSingle(),
|
||||
m33 = ReadSingle()
|
||||
};
|
||||
}
|
||||
|
||||
public void Write(Rect rect)
|
||||
{
|
||||
Write(rect.xMin);
|
||||
Write(rect.yMin);
|
||||
Write(rect.width);
|
||||
Write(rect.height);
|
||||
}
|
||||
|
||||
public Rect ReadRect()
|
||||
{
|
||||
return new Rect(
|
||||
x: ReadSingle(),
|
||||
y: ReadSingle(),
|
||||
width: ReadSingle(),
|
||||
height: ReadSingle());
|
||||
}
|
||||
}
|
||||
}
|
||||
156
SRMP/Lidgren.Network/Platform/PlatformWin32.cs
Normal file
156
SRMP/Lidgren.Network/Platform/PlatformWin32.cs
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
#if !__ANDROID__ && !__CONSTRAINED__ && !WINDOWS_RUNTIME && !UNITY_STANDALONE_LINUX
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public static partial class NetUtility
|
||||
{
|
||||
private static readonly long s_timeInitialized = Stopwatch.GetTimestamp();
|
||||
private static readonly double s_dInvFreq = 1.0 / (double)Stopwatch.Frequency;
|
||||
|
||||
[CLSCompliant(false)]
|
||||
public static ulong GetPlatformSeed(int seedInc)
|
||||
{
|
||||
ulong seed = (ulong)System.Diagnostics.Stopwatch.GetTimestamp();
|
||||
return seed ^ ((ulong)Environment.WorkingSet + (ulong)seedInc);
|
||||
}
|
||||
|
||||
public static double Now { get { return (double)(Stopwatch.GetTimestamp() - s_timeInitialized) * s_dInvFreq; } }
|
||||
|
||||
private static NetworkInterface GetNetworkInterface()
|
||||
{
|
||||
var computerProperties = IPGlobalProperties.GetIPGlobalProperties();
|
||||
if (computerProperties == null)
|
||||
return null;
|
||||
|
||||
var nics = NetworkInterface.GetAllNetworkInterfaces();
|
||||
if (nics == null || nics.Length < 1)
|
||||
return null;
|
||||
|
||||
NetworkInterface best = null;
|
||||
foreach (NetworkInterface adapter in nics)
|
||||
{
|
||||
if (adapter.NetworkInterfaceType == NetworkInterfaceType.Loopback || adapter.NetworkInterfaceType == NetworkInterfaceType.Unknown)
|
||||
continue;
|
||||
if (!adapter.Supports(NetworkInterfaceComponent.IPv4))
|
||||
continue;
|
||||
if (best == null)
|
||||
best = adapter;
|
||||
if (adapter.OperationalStatus != OperationalStatus.Up)
|
||||
continue;
|
||||
|
||||
// make sure this adapter has any ipv4 addresses
|
||||
IPInterfaceProperties properties = adapter.GetIPProperties();
|
||||
foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses)
|
||||
{
|
||||
if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
// Yes it does, return this network interface.
|
||||
return adapter;
|
||||
}
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If available, returns the bytes of the physical (MAC) address for the first usable network interface
|
||||
/// </summary>
|
||||
public static byte[] GetMacAddressBytes()
|
||||
{
|
||||
var ni = GetNetworkInterface();
|
||||
if (ni == null)
|
||||
return null;
|
||||
return ni.GetPhysicalAddress().GetAddressBytes();
|
||||
}
|
||||
|
||||
public static IPAddress GetBroadcastAddress()
|
||||
{
|
||||
var ni = GetNetworkInterface();
|
||||
if (ni == null)
|
||||
return null;
|
||||
|
||||
var properties = ni.GetIPProperties();
|
||||
foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses)
|
||||
{
|
||||
if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
var mask = unicastAddress.IPv4Mask;
|
||||
byte[] ipAdressBytes = unicastAddress.Address.GetAddressBytes();
|
||||
byte[] subnetMaskBytes = mask.GetAddressBytes();
|
||||
|
||||
if (ipAdressBytes.Length != subnetMaskBytes.Length)
|
||||
throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
|
||||
|
||||
byte[] broadcastAddress = new byte[ipAdressBytes.Length];
|
||||
for (int i = 0; i < broadcastAddress.Length; i++)
|
||||
{
|
||||
broadcastAddress[i] = (byte)(ipAdressBytes[i] | (subnetMaskBytes[i] ^ 255));
|
||||
}
|
||||
return new IPAddress(broadcastAddress);
|
||||
}
|
||||
}
|
||||
return IPAddress.Broadcast;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets my local IPv4 address (not necessarily external) and subnet mask
|
||||
/// </summary>
|
||||
public static IPAddress GetMyAddress(out IPAddress mask)
|
||||
{
|
||||
var ni = GetNetworkInterface();
|
||||
if (ni == null)
|
||||
{
|
||||
mask = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
IPInterfaceProperties properties = ni.GetIPProperties();
|
||||
foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses)
|
||||
{
|
||||
if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
mask = unicastAddress.IPv4Mask;
|
||||
return unicastAddress.Address;
|
||||
}
|
||||
}
|
||||
|
||||
mask = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void Sleep(int milliseconds)
|
||||
{
|
||||
System.Threading.Thread.Sleep(milliseconds);
|
||||
}
|
||||
|
||||
public static IPAddress CreateAddressFromBytes(byte[] bytes)
|
||||
{
|
||||
return new IPAddress(bytes);
|
||||
}
|
||||
|
||||
private static readonly SHA256 s_sha = SHA256.Create();
|
||||
public static byte[] ComputeSHAHash(byte[] bytes, int offset, int count)
|
||||
{
|
||||
return s_sha.ComputeHash(bytes, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class NetTime
|
||||
{
|
||||
private static readonly long s_timeInitialized = Stopwatch.GetTimestamp();
|
||||
private static readonly double s_dInvFreq = 1.0 / (double)Stopwatch.Frequency;
|
||||
|
||||
/// <summary>
|
||||
/// Get number of seconds since the application started
|
||||
/// </summary>
|
||||
public static double Now { get { return (double)(Stopwatch.GetTimestamp() - s_timeInitialized) * s_dInvFreq; } }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
102
SRMP/Lidgren.Network/Platform/PlatformWinRT.cs
Normal file
102
SRMP/Lidgren.Network/Platform/PlatformWinRT.cs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#if WINDOWS_RUNTIME
|
||||
//
|
||||
//
|
||||
//
|
||||
// Completely broken right now
|
||||
//
|
||||
//
|
||||
//
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public class NetAddress
|
||||
{
|
||||
public static readonly HostName Any = null;
|
||||
}
|
||||
|
||||
public class NetEndPoint
|
||||
{
|
||||
public NetEndPoint(HostName hostname, int port) { HostName = hostname; Port = port; }
|
||||
public NetEndPoint(HostName hostname, string port) { HostName = hostname; Port = int.Parse(port); }
|
||||
public NetEndPoint(string hostname, int port) { HostName = (hostname == null) ? null : new HostName(hostname); Port = port; }
|
||||
public HostName HostName;
|
||||
public int Port;
|
||||
public override string ToString() { return string.Format("{0}:{1}", HostName, Port); }
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HostName.RawName.GetHashCode() + Port;
|
||||
}
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var ep = obj as NetEndPoint;
|
||||
if (ep == null) return false;
|
||||
if (Port != ep.Port) return false;
|
||||
return HostName.RawName.Equals(ep.HostName.RawName);
|
||||
}
|
||||
};
|
||||
|
||||
public static partial class NetUtility
|
||||
{
|
||||
[CLSCompliant(false)]
|
||||
public static ulong GetPlatformSeed(int seedInc)
|
||||
{
|
||||
ulong seed = (ulong)Environment.TickCount + (ulong)seedInc;
|
||||
return seed ^ ((ulong)(new object().GetHashCode()) << 32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the physical (MAC) address for the first usable network interface
|
||||
/// </summary>
|
||||
public static PhysicalAddress GetMacAddress()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static IPAddress GetBroadcastAddress()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets my local IPv4 address (not necessarily external) and subnet mask
|
||||
/// </summary>
|
||||
public static IPAddress GetMyAddress(out IPAddress mask)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public static void Sleep(int milliseconds)
|
||||
{
|
||||
Task.Delay(50).Wait();
|
||||
}
|
||||
|
||||
public static NetAddress CreateAddressFromBytes(byte[] bytes)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private static readonly SHA1CryptoServiceProvider s_sha = new SHA1CryptoServiceProvider();
|
||||
public static byte[] ComputeSHAHash(byte[] bytes, int offset, int count)
|
||||
{
|
||||
return s_sha.ComputeHash(bytes, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class NetTime
|
||||
{
|
||||
private static readonly long s_timeInitialized = Environment.TickCount;
|
||||
|
||||
/// <summary>
|
||||
/// Get number of seconds since the application started
|
||||
/// </summary>
|
||||
public static double Now { get { return (double)((uint)Environment.TickCount - s_timeInitialized) / 1000.0; } }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
93
SRMP/MainSRML.cs
Normal file
93
SRMP/MainSRML.cs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#if SRML
|
||||
using Newtonsoft.Json;
|
||||
using SRML;
|
||||
using SRML.SR;
|
||||
using SRMultiplayer.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SRMultiplayer
|
||||
{
|
||||
public class MainSRML : ModEntryPoint
|
||||
{
|
||||
private static GameObject m_GameObject;
|
||||
|
||||
// Called before GameContext.Awake
|
||||
// this is where you want to register stuff (like custom enum values or identifiable id's)
|
||||
// and patch anything you want to patch with harmony
|
||||
public override void PreLoad()
|
||||
{
|
||||
base.PreLoad();
|
||||
}
|
||||
|
||||
|
||||
// Called right before PostLoad
|
||||
// Used to register stuff that needs lookupdirector access
|
||||
public override void Load()
|
||||
{
|
||||
if (m_GameObject != null) return;
|
||||
|
||||
SRMP.Log("Loading SRMP SRML Version");
|
||||
|
||||
if (!Directory.Exists(SRMP.ModDataPath))
|
||||
{
|
||||
Directory.CreateDirectory(SRMP.ModDataPath);
|
||||
}
|
||||
if (!File.Exists(Path.Combine(SRMP.ModDataPath, "userdata.json")))
|
||||
{
|
||||
Globals.UserData = new UserData()
|
||||
{
|
||||
UUID = System.Guid.NewGuid(),
|
||||
CheckDLC = true,
|
||||
IgnoredMods = new System.Collections.Generic.List<string>()
|
||||
};
|
||||
File.WriteAllText(Path.Combine(SRMP.ModDataPath, "userdata.json"), JsonConvert.SerializeObject(Globals.UserData));
|
||||
SRMP.Log("Created userdata with UUID " + Globals.UserData.UUID);
|
||||
}
|
||||
else
|
||||
{
|
||||
Globals.UserData = JsonConvert.DeserializeObject<UserData>(File.ReadAllText(Path.Combine(SRMP.ModDataPath, "userdata.json")));
|
||||
if(Globals.UserData.IgnoredMods == null)
|
||||
{
|
||||
Globals.UserData.IgnoredMods = new System.Collections.Generic.List<string>();
|
||||
}
|
||||
SRMP.Log("Loaded userdata with UUID " + Globals.UserData.UUID);
|
||||
}
|
||||
|
||||
string[] args = System.Environment.GetCommandLineArgs();
|
||||
|
||||
m_GameObject = new GameObject("SRMP");
|
||||
m_GameObject.AddComponent<SRMP>();
|
||||
m_GameObject.AddComponent<NetworkMasterServer>();
|
||||
m_GameObject.AddComponent<NetworkClient>();
|
||||
m_GameObject.AddComponent<NetworkServer>();
|
||||
m_GameObject.AddComponent<MultiplayerUI>();
|
||||
m_GameObject.AddComponent<ChatUI>();
|
||||
m_GameObject.AddComponent<SRMPConsole>();
|
||||
|
||||
GameObject.DontDestroyOnLoad(m_GameObject);
|
||||
|
||||
Globals.Version = Assembly.GetExecutingAssembly().GetName().Version.Revision;
|
||||
|
||||
Application.runInBackground = true;
|
||||
|
||||
HarmonyPatcher.GetInstance().PatchAll(Assembly.GetExecutingAssembly());
|
||||
}
|
||||
|
||||
|
||||
// Called after GameContext.Start
|
||||
// stuff like gamecontext.lookupdirector are available in this step, generally for when you want to access
|
||||
// ingame prefabs and the such
|
||||
public override void PostLoad()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
71
SRMP/MainStandalone.cs
Normal file
71
SRMP/MainStandalone.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#if Standalone
|
||||
using HarmonyLib;
|
||||
using Newtonsoft.Json;
|
||||
using SRMultiplayer.Networking;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using UnityCoreMod;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SRMultiplayer
|
||||
{
|
||||
public class MainSaty : IUnityMod
|
||||
{
|
||||
private static GameObject m_GameObject;
|
||||
|
||||
public void Load()
|
||||
{
|
||||
if (m_GameObject != null) return;
|
||||
|
||||
SRMP.Log("Loading SRMP Standalone Version");
|
||||
|
||||
if (!Directory.Exists(SRMP.ModDataPath))
|
||||
{
|
||||
Directory.CreateDirectory(SRMP.ModDataPath);
|
||||
}
|
||||
if (!File.Exists(Path.Combine(SRMP.ModDataPath, "userdata.json")))
|
||||
{
|
||||
Globals.UserData = new UserData()
|
||||
{
|
||||
UUID = System.Guid.NewGuid(),
|
||||
CheckDLC = true,
|
||||
IgnoredMods = new System.Collections.Generic.List<string>()
|
||||
};
|
||||
File.WriteAllText(Path.Combine(SRMP.ModDataPath, "userdata.json"), JsonConvert.SerializeObject(Globals.UserData));
|
||||
SRMP.Log("Created userdata with UUID " + Globals.UserData.UUID);
|
||||
}
|
||||
else
|
||||
{
|
||||
Globals.UserData = JsonConvert.DeserializeObject<UserData>(File.ReadAllText(Path.Combine(SRMP.ModDataPath, "userdata.json")));
|
||||
if(Globals.UserData.IgnoredMods == null)
|
||||
{
|
||||
Globals.UserData.IgnoredMods = new System.Collections.Generic.List<string>();
|
||||
}
|
||||
SRMP.Log("Loaded userdata with UUID " + Globals.UserData.UUID);
|
||||
}
|
||||
|
||||
string[] args = System.Environment.GetCommandLineArgs();
|
||||
|
||||
m_GameObject = new GameObject("SRMP");
|
||||
m_GameObject.AddComponent<SRMP>();
|
||||
m_GameObject.AddComponent<NetworkMasterServer>();
|
||||
m_GameObject.AddComponent<NetworkClient>();
|
||||
m_GameObject.AddComponent<NetworkServer>();
|
||||
m_GameObject.AddComponent<MultiplayerUI>();
|
||||
m_GameObject.AddComponent<ChatUI>();
|
||||
m_GameObject.AddComponent<SRMPConsole>();
|
||||
|
||||
GameObject.DontDestroyOnLoad(m_GameObject);
|
||||
|
||||
Globals.Version = Assembly.GetExecutingAssembly().GetName().Version.Revision;
|
||||
|
||||
var harmony = new Harmony("saty.mod.srmp");
|
||||
harmony.PatchAll(Assembly.GetExecutingAssembly());
|
||||
|
||||
Application.runInBackground = true;
|
||||
}
|
||||
|
||||
public void Unload() { }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
393
SRMP/MultiplayerUI.cs
Normal file
393
SRMP/MultiplayerUI.cs
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using SRMultiplayer;
|
||||
using UnityEngine.SceneManagement;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using SRMultiplayer.Networking;
|
||||
|
||||
public class MultiplayerUI : SRSingleton<MultiplayerUI>
|
||||
{
|
||||
private Rect windowRect = new Rect(Screen.width - 300, 20, 300, 500);
|
||||
private Vector2 playersScroll = Vector2.zero;
|
||||
private string ipaddress = "localhost";
|
||||
private string port = "16500";
|
||||
private string servercode = "";
|
||||
private bool menuOpen;
|
||||
private string username;
|
||||
private float lastCodeUse;
|
||||
private ConnectError error;
|
||||
private ConnectHelp help;
|
||||
private string errorMessage;
|
||||
|
||||
public enum ConnectError
|
||||
{
|
||||
None,
|
||||
InvalidServerCode,
|
||||
ServerCodeTimeout,
|
||||
Kicked,
|
||||
Message
|
||||
}
|
||||
|
||||
public enum ConnectHelp
|
||||
{
|
||||
None,
|
||||
ServerCode,
|
||||
Hosting,
|
||||
}
|
||||
|
||||
public override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
Globals.Username = PlayerPrefs.GetString("SRMP_Username", "");
|
||||
ipaddress = PlayerPrefs.GetString("SRMP_IP", "localhost");
|
||||
port = PlayerPrefs.GetString("SRMP_Port", "16500");
|
||||
|
||||
menuOpen = true;
|
||||
username = Globals.Username;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if(lastCodeUse > 0f)
|
||||
{
|
||||
var prevTime = lastCodeUse;
|
||||
lastCodeUse -= Time.deltaTime;
|
||||
if(prevTime > 0f && lastCodeUse <= 0f)
|
||||
{
|
||||
error = ConnectError.ServerCodeTimeout;
|
||||
}
|
||||
}
|
||||
|
||||
if(Input.GetKeyDown(KeyCode.F4))
|
||||
{
|
||||
menuOpen = !menuOpen;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (!menuOpen || (!Levels.isMainMenu() && !Globals.IsMultiplayer && Globals.PauseState != PauseState.Pause)) return;
|
||||
if (Globals.IsMultiplayer && Globals.PauseState != PauseState.Pause) return;
|
||||
|
||||
if (SceneManager.GetActiveScene().buildIndex >= 2)
|
||||
{
|
||||
windowRect = GUI.Window(1, windowRect, MultiplayerWindow, "SRMP v" + Globals.Version);
|
||||
}
|
||||
}
|
||||
|
||||
private void MultiplayerWindow(int id)
|
||||
{
|
||||
if (id != 1) return;
|
||||
|
||||
GUILayout.Label("You can close this menu with F4");
|
||||
GUILayout.Space(20);
|
||||
|
||||
if(string.IsNullOrWhiteSpace(Globals.Username))
|
||||
{
|
||||
UsernameGUI();
|
||||
}
|
||||
else
|
||||
{
|
||||
if(Globals.IsServer)
|
||||
{
|
||||
ServerGUI();
|
||||
}
|
||||
else if(Globals.IsClient)
|
||||
{
|
||||
ClientGUI();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (error != ConnectError.None)
|
||||
{
|
||||
ErrorGUI();
|
||||
}
|
||||
else if(help != ConnectHelp.None)
|
||||
{
|
||||
HelpGUI();
|
||||
}
|
||||
else
|
||||
{
|
||||
if(lastCodeUse > 0f)
|
||||
{
|
||||
GUILayout.Label("Trying to connect with server code...");
|
||||
}
|
||||
else if (Levels.isMainMenu())
|
||||
{
|
||||
ConnectGUI();
|
||||
}
|
||||
else
|
||||
{
|
||||
HostGUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUI.DragWindow(new Rect(0, 0, 10000, 10000));
|
||||
}
|
||||
|
||||
private void ServerGUI()
|
||||
{
|
||||
GUILayout.Label("You are the server");
|
||||
GUILayout.Space(20);
|
||||
|
||||
GUILayout.Label("Server Code: " + Globals.ServerCode);
|
||||
GUILayout.Label("Players");
|
||||
playersScroll = GUILayout.BeginScrollView(playersScroll, GUI.skin.box);
|
||||
foreach (var player in Globals.Players.Values)
|
||||
{
|
||||
if (player.IsLocal) continue;
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label(player.Username);
|
||||
if(GUILayout.Button("Kick"))
|
||||
{
|
||||
player.Connection.Disconnect("kicked");
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
GUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private void ClientGUI()
|
||||
{
|
||||
GUILayout.Label("You are a client");
|
||||
GUILayout.Space(20);
|
||||
|
||||
GUILayout.Label("Players");
|
||||
playersScroll = GUILayout.BeginScrollView(playersScroll, GUI.skin.box);
|
||||
foreach (var player in Globals.Players.Values)
|
||||
{
|
||||
if (player.IsLocal) continue;
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label(player.Username);
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
GUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private void ConnectGUI()
|
||||
{
|
||||
GUILayout.Label("Username: " + Globals.Username);
|
||||
if (GUILayout.Button("Change Username"))
|
||||
{
|
||||
Globals.Username = "";
|
||||
return;
|
||||
}
|
||||
GUILayout.Space(20);
|
||||
if(GUILayout.Button("How do I host a game?"))
|
||||
{
|
||||
help = ConnectHelp.Hosting;
|
||||
}
|
||||
GUILayout.Space(20);
|
||||
|
||||
if (SRSingleton<NetworkMasterServer>.Instance.Status == NetworkMasterServer.ConnectionStatus.Connected)
|
||||
{
|
||||
GUILayout.Label("Join with server code:");
|
||||
GUILayout.BeginHorizontal();
|
||||
servercode = GUILayout.TextField(servercode, 5, GUILayout.Width(80));
|
||||
servercode = servercode.Replace(" ", "").ToUpper();
|
||||
if (GUILayout.Button("Join"))
|
||||
{
|
||||
lastCodeUse = 5;
|
||||
SRSingleton<NetworkMasterServer>.Instance.JoinServer(servercode);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("Server codes currently not available");
|
||||
}
|
||||
|
||||
GUILayout.Space(20);
|
||||
|
||||
GUILayout.Label("Join with IP Address:");
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label("IP Address", GUILayout.Width(80));
|
||||
ipaddress = GUILayout.TextField(ipaddress);
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label("Port", GUILayout.Width(80));
|
||||
port = GUILayout.TextField(port);
|
||||
GUILayout.EndHorizontal();
|
||||
if (string.IsNullOrWhiteSpace(ipaddress))
|
||||
{
|
||||
GUILayout.Label("IPAddress invalid");
|
||||
}
|
||||
else if (!int.TryParse(port, out int numport) || numport < 1000 || numport > 65000)
|
||||
{
|
||||
GUILayout.Label("Invalid port, should be between 1000 and 65000");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("Connect"))
|
||||
{
|
||||
SRSingleton<NetworkClient>.Instance.Connect(ipaddress, numport, Globals.Username);
|
||||
SaveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Space(20);
|
||||
GUILayout.Label("Games in the same Network:");
|
||||
if (GUILayout.Button("Refresh"))
|
||||
{
|
||||
SRSingleton<NetworkClient>.Instance.SendDiscoverMessage();
|
||||
}
|
||||
if (SRSingleton<NetworkClient>.Instance.LocalGames.Count == 0)
|
||||
{
|
||||
GUILayout.Label("No games found");
|
||||
}
|
||||
foreach (var game in SRSingleton<NetworkClient>.Instance.LocalGames)
|
||||
{
|
||||
if (GUILayout.Button(game.Name))
|
||||
{
|
||||
SRSingleton<NetworkClient>.Instance.Connect(game.IP, game.Port, Globals.Username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HostGUI()
|
||||
{
|
||||
GUILayout.Label("Username: " + Globals.Username);
|
||||
if (GUILayout.Button("Change Username"))
|
||||
{
|
||||
Globals.Username = "";
|
||||
return;
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label("Port", GUILayout.Width(80));
|
||||
port = GUILayout.TextField(port);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (!int.TryParse(port, out int numport) || numport < 1000 || numport > 65000)
|
||||
{
|
||||
GUILayout.Label("Invalid port");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("Host"))
|
||||
{
|
||||
NetworkServer.Instance.StartServer(numport);
|
||||
SaveSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HelpGUI()
|
||||
{
|
||||
switch(help)
|
||||
{
|
||||
case ConnectHelp.ServerCode:
|
||||
{
|
||||
|
||||
}
|
||||
break;
|
||||
case ConnectHelp.Hosting:
|
||||
{
|
||||
GUILayout.Label("You can host a game by loading any Singleplayer save.");
|
||||
GUILayout.Label("When the game is loaded, pause it and the HostUI should appear.");
|
||||
GUILayout.Label("(Please make a backup of your Singleplayer save to avoid data loss on crashes or error)");
|
||||
|
||||
if (GUILayout.Button("Okay"))
|
||||
{
|
||||
help = ConnectHelp.None;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ErrorGUI()
|
||||
{
|
||||
switch(error)
|
||||
{
|
||||
case ConnectError.InvalidServerCode:
|
||||
{
|
||||
GUILayout.Label("There is no server with this server code.");
|
||||
|
||||
if (GUILayout.Button("Okay"))
|
||||
{
|
||||
error = ConnectError.None;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ConnectError.Kicked:
|
||||
{
|
||||
GUILayout.Label("You got kicked from the game");
|
||||
|
||||
if (GUILayout.Button("Okay"))
|
||||
{
|
||||
error = ConnectError.None;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ConnectError.Message:
|
||||
{
|
||||
GUILayout.Label("Connection Error:");
|
||||
GUILayout.Label(errorMessage);
|
||||
|
||||
if (GUILayout.Button("Okay"))
|
||||
{
|
||||
error = ConnectError.None;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ConnectError.ServerCodeTimeout:
|
||||
{
|
||||
GUILayout.Label("Connection via server code timed out.");
|
||||
GUILayout.Label("Server codes do not have a 100% success chance.");
|
||||
GUILayout.Label("You can try again, but if it keeps failing, you may have to port forward.");
|
||||
GUILayout.Label("If you can't port forward, we recommend a service like 'Hamachi' or 'Radmin'");
|
||||
GUILayout.Label("Please note: Hamachi and Radmin degrade the experience by quite a lot. Port forwarding is always better.");
|
||||
GUILayout.Label("(We can not offer support for port forwarding)");
|
||||
|
||||
if (GUILayout.Button("Okay"))
|
||||
{
|
||||
error = ConnectError.None;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void UsernameGUI()
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label("Username", GUILayout.Width(60));
|
||||
username = GUILayout.TextField(username, 30);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(username) || !new Regex(@"^[a-zA-Z_][\w]*$").IsMatch(username))
|
||||
{
|
||||
GUILayout.Label("Invalid username");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("Save Username"))
|
||||
{
|
||||
Globals.Username = username;
|
||||
SaveSettings();
|
||||
SRSingleton<NetworkMasterServer>.Instance.UpdateName(Globals.Username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
PlayerPrefs.SetString("SRMP_Username", Globals.Username);
|
||||
PlayerPrefs.SetString("SRMP_IP", ipaddress);
|
||||
PlayerPrefs.GetString("SRMP_Port", port);
|
||||
}
|
||||
|
||||
public void ConnectResponse(ConnectError connectError, string message = "")
|
||||
{
|
||||
lastCodeUse = 0f;
|
||||
error = connectError;
|
||||
errorMessage = message;
|
||||
}
|
||||
}
|
||||
15
SRMP/Networking/NetworkAccessDoor.cs
Normal file
15
SRMP/Networking/NetworkAccessDoor.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SRMultiplayer.Networking
|
||||
{
|
||||
public class NetworkAccessDoor : MonoBehaviour
|
||||
{
|
||||
public AccessDoor Door;
|
||||
public NetworkRegion Region;
|
||||
}
|
||||
}
|
||||
258
SRMP/Networking/NetworkActor.cs
Normal file
258
SRMP/Networking/NetworkActor.cs
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
using MonomiPark.SlimeRancher.Regions;
|
||||
using SRMultiplayer.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SRMultiplayer.Networking
|
||||
{
|
||||
public class NetworkActor : MonoBehaviour
|
||||
{
|
||||
public int ID { get; internal set; }
|
||||
public byte Owner;
|
||||
public ushort Ident;
|
||||
public byte RegionSet;
|
||||
public bool IsLocal { get { return Owner == Globals.LocalID; } }
|
||||
public List<NetworkPlayer> KnownPlayers = new List<NetworkPlayer>();
|
||||
|
||||
private Rigidbody m_Rigidbody;
|
||||
private RegionMember m_RegionMember;
|
||||
private float m_MovementUpdateTimer;
|
||||
private Vector3 m_ActualPosition;
|
||||
private Vector3 m_LatestPosition;
|
||||
private Vector3 m_PreviousPosition;
|
||||
private Quaternion m_ActualRotation;
|
||||
private Quaternion m_LatestRotation;
|
||||
private Quaternion m_PreviousRotation;
|
||||
private float m_LatestPositionTime;
|
||||
private float m_InterpolationPeriod = 0.1f;
|
||||
private Dictionary<SlimeEmotions.Emotion, float> m_PreviousEmotions = new Dictionary<SlimeEmotions.Emotion, float>();
|
||||
|
||||
public Reproduce Reproduce;
|
||||
public SlimeEat SlimeEat;
|
||||
public ResourceCycle ResourceCycle;
|
||||
public DestroyPlortAfterTime DestroyPlortAfterTime;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_Rigidbody = GetComponentInChildren<Rigidbody>();
|
||||
m_RegionMember = GetComponent<RegionMember>();
|
||||
Reproduce = GetComponent<Reproduce>();
|
||||
SlimeEat = GetComponent<SlimeEat>();
|
||||
ResourceCycle = GetComponent<ResourceCycle>();
|
||||
DestroyPlortAfterTime = GetComponent<DestroyPlortAfterTime>();
|
||||
|
||||
m_PreviousEmotions.Add(SlimeEmotions.Emotion.AGITATION, 0);
|
||||
m_PreviousEmotions.Add(SlimeEmotions.Emotion.FEAR, 0);
|
||||
m_PreviousEmotions.Add(SlimeEmotions.Emotion.HUNGER, 0);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if(Owner == 0)
|
||||
{
|
||||
TakeOwnership();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (IsLocal)
|
||||
{
|
||||
DropOwnership();
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if(IsLocal)
|
||||
{
|
||||
m_MovementUpdateTimer -= Time.deltaTime;
|
||||
if (m_MovementUpdateTimer <= 0)
|
||||
{
|
||||
m_MovementUpdateTimer = 0.1f;
|
||||
if (Vector3.Distance(m_ActualPosition, transform.position) > 0.1f || Vector3.Distance(m_ActualRotation.eulerAngles, transform.eulerAngles) > 0.1f)
|
||||
{
|
||||
m_LatestPosition = transform.position;
|
||||
m_LatestPositionTime = Time.time + m_InterpolationPeriod;
|
||||
m_PreviousPosition = m_LatestPosition;
|
||||
m_ActualPosition = m_LatestPosition;
|
||||
m_LatestRotation = transform.rotation;
|
||||
m_PreviousRotation = m_LatestRotation;
|
||||
m_ActualRotation = m_LatestRotation;
|
||||
|
||||
new PacketActorPosition()
|
||||
{
|
||||
ID = ID,
|
||||
Position = transform.position,
|
||||
Rotation = transform.rotation
|
||||
}.Send(Lidgren.Network.NetDeliveryMethod.Unreliable);
|
||||
}
|
||||
|
||||
if(SlimeEat != null &&
|
||||
(!Utils.CloseEnoughForMe(SlimeEat.emotions.GetCurr(SlimeEmotions.Emotion.AGITATION), m_PreviousEmotions[SlimeEmotions.Emotion.AGITATION], 0.1f) ||
|
||||
!Utils.CloseEnoughForMe(SlimeEat.emotions.GetCurr(SlimeEmotions.Emotion.FEAR), m_PreviousEmotions[SlimeEmotions.Emotion.FEAR], 0.1f) ||
|
||||
!Utils.CloseEnoughForMe(SlimeEat.emotions.GetCurr(SlimeEmotions.Emotion.HUNGER), m_PreviousEmotions[SlimeEmotions.Emotion.HUNGER], 0.1f)))
|
||||
{
|
||||
m_PreviousEmotions[SlimeEmotions.Emotion.AGITATION] = SlimeEat.emotions.GetCurr(SlimeEmotions.Emotion.AGITATION);
|
||||
m_PreviousEmotions[SlimeEmotions.Emotion.FEAR] = SlimeEat.emotions.GetCurr(SlimeEmotions.Emotion.FEAR);
|
||||
m_PreviousEmotions[SlimeEmotions.Emotion.HUNGER] = SlimeEat.emotions.GetCurr(SlimeEmotions.Emotion.HUNGER);
|
||||
|
||||
new PacketActorEmotions()
|
||||
{
|
||||
ID = ID,
|
||||
Fear = m_PreviousEmotions[SlimeEmotions.Emotion.FEAR],
|
||||
Agitation = m_PreviousEmotions[SlimeEmotions.Emotion.AGITATION],
|
||||
Hunger = m_PreviousEmotions[SlimeEmotions.Emotion.HUNGER]
|
||||
}.Send();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(m_Rigidbody != null)
|
||||
{
|
||||
m_Rigidbody.velocity = Vector3.zero;
|
||||
}
|
||||
|
||||
float t = 1.0f - ((m_LatestPositionTime - Time.time) / m_InterpolationPeriod);
|
||||
m_ActualPosition = Vector3.Lerp(m_PreviousPosition, m_LatestPosition, t);
|
||||
transform.position = m_ActualPosition;
|
||||
|
||||
m_ActualRotation = Quaternion.Slerp(m_PreviousRotation, m_LatestRotation, t);
|
||||
transform.rotation = m_ActualRotation;
|
||||
}
|
||||
}
|
||||
|
||||
public void PositionRotationUpdate(Vector3 pos, Quaternion rot, bool immediate)
|
||||
{
|
||||
if (Vector3.Distance(pos, m_ActualPosition) > 10f) // Teleport detection
|
||||
{
|
||||
immediate = true;
|
||||
}
|
||||
if (!immediate)
|
||||
{
|
||||
m_LatestPosition = pos;
|
||||
m_LatestPositionTime = Time.time + m_InterpolationPeriod;
|
||||
m_PreviousPosition = m_ActualPosition;
|
||||
|
||||
m_LatestRotation = rot;
|
||||
m_PreviousRotation = m_ActualRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_LatestPosition = pos;
|
||||
m_LatestPositionTime = Time.time + m_InterpolationPeriod;
|
||||
m_PreviousPosition = pos;
|
||||
m_ActualPosition = pos;
|
||||
transform.position = m_ActualPosition;
|
||||
|
||||
m_PreviousRotation = rot;
|
||||
m_LatestRotation = rot;
|
||||
m_ActualRotation = rot;
|
||||
transform.rotation = rot;
|
||||
}
|
||||
|
||||
if (m_RegionMember != null && m_RegionMember.hibernating)
|
||||
{
|
||||
m_RegionMember.UpdateRegionMembership(true);
|
||||
}
|
||||
}
|
||||
|
||||
public void TakeOwnership()
|
||||
{
|
||||
if (Globals.IsServer)
|
||||
{
|
||||
SetOwnership(Globals.LocalID);
|
||||
}
|
||||
|
||||
new PacketActorOwner()
|
||||
{
|
||||
ID = ID,
|
||||
Owner = Globals.LocalID
|
||||
}.Send();
|
||||
}
|
||||
|
||||
public void DropOwnership()
|
||||
{
|
||||
SetOwnership(0);
|
||||
|
||||
new PacketActorOwner()
|
||||
{
|
||||
ID = ID,
|
||||
Owner = 0
|
||||
}.Send();
|
||||
}
|
||||
|
||||
public void SetOwnership(byte id)
|
||||
{
|
||||
Owner = id;
|
||||
|
||||
if (Owner != Globals.LocalID)
|
||||
{
|
||||
if (SRSingleton<SceneContext>.Instance.Player != null)
|
||||
{
|
||||
var weapon = SRSingleton<SceneContext>.Instance.Player.GetComponentInChildren<WeaponVacuum>();
|
||||
if (weapon != null && weapon.held != null && weapon.held.GetComponent<NetworkActor>() != null && weapon.held.GetComponent<NetworkActor>().ID == ID)
|
||||
{
|
||||
weapon.DropAllVacced();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_Rigidbody != null)
|
||||
{
|
||||
m_Rigidbody.velocity = Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
if (Owner == 0 && gameObject.activeInHierarchy)
|
||||
{
|
||||
TakeOwnership();
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnDestroyEffect()
|
||||
{
|
||||
var slimeKey = GetComponentInChildren<SlimeKey>();
|
||||
if (slimeKey != null)
|
||||
{
|
||||
if (slimeKey.pickupFX != null)
|
||||
{
|
||||
SRBehaviour.SpawnAndPlayFX(slimeKey.pickupFX, slimeKey.transform.position, slimeKey.transform.rotation);
|
||||
}
|
||||
}
|
||||
var destroyOnTouching = GetComponentInChildren<DestroyOnTouching>();
|
||||
if(destroyOnTouching != null)
|
||||
{
|
||||
if (destroyOnTouching.destroyFX != null)
|
||||
{
|
||||
SRBehaviour.SpawnAndPlayFX(destroyOnTouching.destroyFX, destroyOnTouching.transform.position, destroyOnTouching.transform.rotation);
|
||||
}
|
||||
}
|
||||
var exchangeBreakOnImpact = GetComponentInChildren<ExchangeBreakOnImpact>();
|
||||
if (exchangeBreakOnImpact != null)
|
||||
{
|
||||
SRBehaviour.SpawnAndPlayFX(exchangeBreakOnImpact.breakFX, exchangeBreakOnImpact.gameObject.transform.position, exchangeBreakOnImpact.gameObject.transform.rotation);
|
||||
}
|
||||
var breakOnImpact = GetComponentInChildren<BreakOnImpactBase>();
|
||||
if (breakOnImpact != null)
|
||||
{
|
||||
SRBehaviour.SpawnAndPlayFX(breakOnImpact.breakFX, breakOnImpact.gameObject.transform.position, breakOnImpact.gameObject.transform.rotation);
|
||||
}
|
||||
var quicksilver = GetComponentInChildren<QuicksilverPlortCollector>();
|
||||
if(quicksilver != null)
|
||||
{
|
||||
if (quicksilver.destroyFX != null)
|
||||
{
|
||||
SRBehaviour.SpawnAndPlayFX(quicksilver.destroyFX, quicksilver.transform.position, Quaternion.identity);
|
||||
}
|
||||
SECTR_AudioSystem.Play(quicksilver.onCollectionCue, quicksilver.transform.position, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
48
SRMP/Networking/NetworkAmmo.cs
Normal file
48
SRMP/Networking/NetworkAmmo.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SRMultiplayer.Networking
|
||||
{
|
||||
public class NetworkAmmo : Ammo
|
||||
{
|
||||
public static Dictionary<string, NetworkAmmo> All = new Dictionary<string, NetworkAmmo>();
|
||||
|
||||
public string ID;
|
||||
public SiloStorage Silo;
|
||||
|
||||
public NetworkAmmo(SiloStorage silo, HashSet<Identifiable.Id> potentialAmmo, int numSlots, int usableSlots, Predicate<Identifiable.Id>[] slotPreds, Func<Identifiable.Id, int, int> slotMaxCountFunction) : base(potentialAmmo, numSlots, usableSlots, slotPreds, slotMaxCountFunction)
|
||||
{
|
||||
Silo = silo;
|
||||
var landplotLocation = GetInParent<LandPlotLocation>(silo.gameObject);
|
||||
if (landplotLocation != null)
|
||||
{
|
||||
ID = landplotLocation.id;
|
||||
}
|
||||
else
|
||||
{
|
||||
ID = GetInParent<GadgetSite>(silo.gameObject).id;
|
||||
}
|
||||
ID += "-" + silo.name;
|
||||
|
||||
All[ID] = this;
|
||||
}
|
||||
|
||||
private T GetInParent<T>(GameObject obj)
|
||||
{
|
||||
var cmp = obj.GetComponent<T>();
|
||||
if(cmp != null)
|
||||
{
|
||||
return cmp;
|
||||
}
|
||||
if(obj.transform.parent != null)
|
||||
{
|
||||
return GetInParent<T>(obj.transform.parent.gameObject);
|
||||
}
|
||||
return default(T);
|
||||
}
|
||||
}
|
||||
}
|
||||
264
SRMP/Networking/NetworkClient.cs
Normal file
264
SRMP/Networking/NetworkClient.cs
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
using Lidgren.Network;
|
||||
using SRMultiplayer.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace SRMultiplayer.Networking
|
||||
{
|
||||
public class NetworkClient : SRSingleton<NetworkClient>
|
||||
{
|
||||
private NetClient m_Client;
|
||||
|
||||
public enum ConnectionStatus
|
||||
{
|
||||
Disconnected,
|
||||
Connecting,
|
||||
Connected
|
||||
}
|
||||
|
||||
public struct LocalGame
|
||||
{
|
||||
public string Name;
|
||||
public string IP;
|
||||
public int Port;
|
||||
}
|
||||
|
||||
public ConnectionStatus Status;
|
||||
public NetPeerStatistics Statistics { get { return m_Client.Statistics; } }
|
||||
public int Port { get { return m_Client.Port; } }
|
||||
public List<LocalGame> LocalGames = new List<LocalGame>();
|
||||
|
||||
private void Start()
|
||||
{
|
||||
NetPeerConfiguration config = new NetPeerConfiguration("srmp");
|
||||
config.EnableMessageType(NetIncomingMessageType.NatIntroductionSuccess);
|
||||
config.EnableMessageType(NetIncomingMessageType.DiscoveryResponse);
|
||||
|
||||
m_Client = new NetClient(config);
|
||||
m_Client.Start();
|
||||
}
|
||||
|
||||
public void SendDiscoverMessage()
|
||||
{
|
||||
LocalGames.Clear();
|
||||
m_Client.DiscoverLocalPeers(6996);
|
||||
}
|
||||
|
||||
public void Connect(string ip, int port, string username)
|
||||
{
|
||||
Status = ConnectionStatus.Connecting;
|
||||
|
||||
NetOutgoingMessage hail = CreateMessage();
|
||||
hail.Write(Globals.Version);
|
||||
hail.Write(Globals.UserData.UUID.ToByteArray());
|
||||
hail.Write(username);
|
||||
hail.Write(Globals.Mods.Count);
|
||||
foreach (var mod in Globals.Mods)
|
||||
{
|
||||
hail.Write(mod);
|
||||
}
|
||||
var dlcs = SRSingleton<GameContext>.Instance.DLCDirector.Installed;
|
||||
hail.Write(dlcs.Count());
|
||||
foreach (var dlc in dlcs)
|
||||
{
|
||||
hail.Write((byte)dlc);
|
||||
}
|
||||
m_Client.Connect(ip, port, hail);
|
||||
}
|
||||
|
||||
public void Connect(IPEndPoint ip, string username)
|
||||
{
|
||||
SRMP.Log("Connect " + ip.ToString());
|
||||
if (Status != ConnectionStatus.Disconnected)
|
||||
{
|
||||
SRMP.Log("Already connecting... abort");
|
||||
return;
|
||||
}
|
||||
|
||||
NetOutgoingMessage hail = CreateMessage();
|
||||
hail.Write(Globals.Version);
|
||||
hail.Write(Globals.UserData.UUID.ToByteArray());
|
||||
hail.Write(username);
|
||||
hail.Write(Globals.Mods.Count);
|
||||
foreach (var mod in Globals.Mods)
|
||||
{
|
||||
hail.Write(mod);
|
||||
}
|
||||
var dlcs = SRSingleton<GameContext>.Instance.DLCDirector.Installed;
|
||||
hail.Write(dlcs.Count());
|
||||
foreach(var dlc in dlcs)
|
||||
{
|
||||
hail.Write((byte)dlc);
|
||||
}
|
||||
m_Client.Connect(ip, hail);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
NetIncomingMessage im;
|
||||
while ((im = m_Client?.ReadMessage()) != null)
|
||||
{
|
||||
switch (im.MessageType)
|
||||
{
|
||||
case NetIncomingMessageType.DebugMessage:
|
||||
case NetIncomingMessageType.ErrorMessage:
|
||||
case NetIncomingMessageType.WarningMessage:
|
||||
case NetIncomingMessageType.VerboseDebugMessage:
|
||||
{
|
||||
string text = im.ReadString();
|
||||
SRMP.Log("[NetworkClient] " + text);
|
||||
}
|
||||
break;
|
||||
case NetIncomingMessageType.NatIntroductionSuccess:
|
||||
{
|
||||
string token = im.ReadString();
|
||||
SRMP.Log("[NetworkClient] Nat introduction success to " + im.SenderEndPoint + " token is: " + token);
|
||||
|
||||
MultiplayerUI.Instance.ConnectResponse(MultiplayerUI.ConnectError.None);
|
||||
|
||||
Connect(im.SenderEndPoint, Globals.Username);
|
||||
}
|
||||
break;
|
||||
case NetIncomingMessageType.DiscoveryResponse:
|
||||
{
|
||||
var game = new LocalGame()
|
||||
{
|
||||
Name = im.ReadString(),
|
||||
Port = im.ReadInt32(),
|
||||
IP = im.SenderEndPoint.Address.ToString()
|
||||
};
|
||||
LocalGames.Add(game);
|
||||
Console.WriteLine("Found server at " + im.SenderEndPoint + " name: " + game.Name);
|
||||
}
|
||||
break;
|
||||
case NetIncomingMessageType.StatusChanged:
|
||||
{
|
||||
NetConnectionStatus status = (NetConnectionStatus)im.ReadByte();
|
||||
string reason = im.ReadString();
|
||||
SRMP.Log("[NetworkClient] " + NetUtility.ToHexString(im.SenderConnection.RemoteUniqueIdentifier) + " " + status + ": " + reason);
|
||||
|
||||
if (status == NetConnectionStatus.Connected)
|
||||
{
|
||||
Status = ConnectionStatus.Connected;
|
||||
NetIncomingMessage hail = im.SenderConnection.RemoteHailMessage;
|
||||
Globals.LocalID = hail.ReadByte();
|
||||
|
||||
int playerCount = hail.ReadInt32();
|
||||
for (int i = 0; i < playerCount; i++)
|
||||
{
|
||||
byte id = hail.ReadByte();
|
||||
string username = hail.ReadString();
|
||||
bool hasloaded = hail.ReadBoolean();
|
||||
|
||||
var playerObject = new GameObject($"{username} ({id})");
|
||||
var player = playerObject.AddComponent<NetworkPlayer>();
|
||||
DontDestroyOnLoad(playerObject);
|
||||
|
||||
player.ID = id;
|
||||
player.Username = username;
|
||||
player.HasLoaded = hasloaded;
|
||||
Globals.Players.Add(id, player);
|
||||
|
||||
if (id == Globals.LocalID)
|
||||
{
|
||||
Globals.LocalPlayer = player;
|
||||
}
|
||||
}
|
||||
Globals.PartyID = new Guid(hail.ReadBytes(16));
|
||||
var gameMode = (PlayerState.GameMode)hail.ReadByte();
|
||||
Globals.CurrentGameName = hail.ReadString();
|
||||
|
||||
SRSingleton<GameContext>.Instance.AutoSaveDirector.LoadNewGame("SRMultiplayerGame", Identifiable.Id.GOLD_SLIME, gameMode, () =>
|
||||
{
|
||||
Disconnect("Error loading save");
|
||||
|
||||
SceneManager.LoadScene(2);
|
||||
});
|
||||
}
|
||||
else if (status == NetConnectionStatus.Disconnected)
|
||||
{
|
||||
if (reason != "goodbye")
|
||||
{
|
||||
if(reason.Equals("kicked"))
|
||||
{
|
||||
MultiplayerUI.Instance.ConnectResponse(MultiplayerUI.ConnectError.Kicked);
|
||||
}
|
||||
else
|
||||
{
|
||||
MultiplayerUI.Instance.ConnectResponse(MultiplayerUI.ConnectError.Message, reason);
|
||||
}
|
||||
}
|
||||
Status = ConnectionStatus.Disconnected;
|
||||
if (SceneManager.GetActiveScene().buildIndex == 3)
|
||||
{
|
||||
SceneManager.LoadScene(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetIncomingMessageType.Data:
|
||||
{
|
||||
PacketType type = (PacketType)im.ReadUInt16();
|
||||
|
||||
Globals.HandlePacket = true;
|
||||
try
|
||||
{
|
||||
NetworkHandlerClient.HandlePacket(type, im);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SRMP.Log($"[NetworkClient] Could not handle packet {type}\n{ex}");
|
||||
}
|
||||
Globals.HandlePacket = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
SRMP.Log("[NetworkClient] Unhandled type: " + im.MessageType + " " + im.LengthBytes + " bytes " + im.DeliveryMethod + "|" + im.SequenceChannel);
|
||||
break;
|
||||
}
|
||||
m_Client.Recycle(im);
|
||||
}
|
||||
}
|
||||
|
||||
public void Disconnect(string message = "goodbye")
|
||||
{
|
||||
Status = ConnectionStatus.Disconnected;
|
||||
m_Client?.Disconnect(message);
|
||||
}
|
||||
|
||||
public void Send(Packet packet, NetDeliveryMethod method, int sequence)
|
||||
{
|
||||
if (m_Client == null || m_Client.ConnectionStatus != NetConnectionStatus.Connected) return;
|
||||
|
||||
var om = CreateMessage();
|
||||
packet.Serialize(om);
|
||||
|
||||
m_Client?.SendMessage(om, method, sequence);
|
||||
}
|
||||
|
||||
public void Send(NetOutgoingMessage om, NetDeliveryMethod method, int sequence)
|
||||
{
|
||||
if (m_Client == null || m_Client.ConnectionStatus != NetConnectionStatus.Connected) return;
|
||||
|
||||
m_Client?.SendMessage(om, method, sequence);
|
||||
}
|
||||
|
||||
public void SendUnconnected(NetOutgoingMessage om, IPEndPoint endPoint)
|
||||
{
|
||||
if (m_Client == null) return;
|
||||
|
||||
m_Client?.SendUnconnectedMessage(om, endPoint);
|
||||
}
|
||||
|
||||
public NetOutgoingMessage CreateMessage()
|
||||
{
|
||||
return m_Client?.CreateMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
84
SRMP/Networking/NetworkClientUI.cs
Normal file
84
SRMP/Networking/NetworkClientUI.cs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace SRMultiplayer.Networking
|
||||
{
|
||||
public class NetworkClientUI : MonoBehaviour
|
||||
{
|
||||
public List<GameObject> Panels = new List<GameObject>();
|
||||
|
||||
public GameObject UsernamePanel;
|
||||
public InputField UsernameInput;
|
||||
|
||||
public GameObject ErrorPanel;
|
||||
public Text ErrorText;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
UsernamePanel = transform.Find("Background/UsernamePanel").gameObject;
|
||||
UsernameInput = UsernamePanel.transform.Find("UsernameInput").GetComponent<InputField>();
|
||||
UsernamePanel.transform.Find("ConfirmButton").GetComponent<Button>().onClick.AddListener(OnUsernameConfirm);
|
||||
|
||||
ErrorPanel = transform.Find("Background/ErrorPanel").gameObject;
|
||||
ErrorText = ErrorPanel.transform.Find("ErrorText").GetComponent<Text>();
|
||||
ErrorPanel.transform.Find("CloseButton").GetComponent<Button>().onClick.AddListener(HideError);
|
||||
|
||||
Panels.Add(UsernamePanel);
|
||||
|
||||
UsernameInput.text = PlayerPrefs.GetString("MultiplayerUsername", "");
|
||||
if(string.IsNullOrWhiteSpace(UsernameInput.text))
|
||||
{
|
||||
ChangeUsername();
|
||||
}
|
||||
else
|
||||
{
|
||||
//Connect UI
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUsernameConfirm()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(UsernameInput.text) && UsernameInput.text.Length > 1 && UsernameInput.text.Length < 30)
|
||||
{
|
||||
PlayerPrefs.SetString("MultiplayerUsername", UsernameInput.text);
|
||||
SRMP.Log("UsernameInput: " + UsernameInput.text);
|
||||
HidePanels();
|
||||
//Connect UI
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowError("Username has to be between 1 and 30 letters long");
|
||||
}
|
||||
}
|
||||
|
||||
private void ChangeUsername()
|
||||
{
|
||||
HidePanels();
|
||||
UsernamePanel.SetActive(true);
|
||||
}
|
||||
|
||||
private void ShowError(string error)
|
||||
{
|
||||
ErrorText.text = error;
|
||||
ErrorPanel.SetActive(true);
|
||||
}
|
||||
|
||||
private void HideError()
|
||||
{
|
||||
ErrorPanel.SetActive(false);
|
||||
}
|
||||
|
||||
private void HidePanels()
|
||||
{
|
||||
foreach(var panel in Panels)
|
||||
{
|
||||
panel.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
SRMP/Networking/NetworkDirectedActorSpawner.cs
Normal file
18
SRMP/Networking/NetworkDirectedActorSpawner.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SRMultiplayer.Networking
|
||||
{
|
||||
public class NetworkDirectedActorSpawner : MonoBehaviour
|
||||
{
|
||||
public int ID;
|
||||
public DirectedActorSpawner Spawner;
|
||||
public NetworkRegion Region;
|
||||
|
||||
public bool IsLocal { get { return Region.IsLocal; } }
|
||||
}
|
||||
}
|
||||
131
SRMP/Networking/NetworkDrone.cs
Normal file
131
SRMP/Networking/NetworkDrone.cs
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
using MonomiPark.SlimeRancher.Regions;
|
||||
using SRMultiplayer.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SRMultiplayer.Networking
|
||||
{
|
||||
public class NetworkDrone : MonoBehaviour
|
||||
{
|
||||
public Drone Drone;
|
||||
public NetworkRegion Region;
|
||||
|
||||
public bool IsLocal { get { return Region.IsLocal; } }
|
||||
|
||||
private RegionMember m_RegionMember;
|
||||
private float m_MovementUpdateTimer;
|
||||
private Vector3 m_ActualPosition;
|
||||
private Vector3 m_LatestPosition;
|
||||
private Vector3 m_PreviousPosition;
|
||||
private Quaternion m_ActualRotation;
|
||||
private Quaternion m_LatestRotation;
|
||||
private Quaternion m_PreviousRotation;
|
||||
private float m_LatestPositionTime;
|
||||
private float m_InterpolationPeriod = 0.1f;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_RegionMember = GetComponent<RegionMember>();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Region.OnBecameOwner -= OnBecameOwner;
|
||||
}
|
||||
|
||||
private void OnBecameOwner()
|
||||
{
|
||||
Drone.plexer.ForceRethink();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
Region.OnBecameOwner += OnBecameOwner;
|
||||
|
||||
m_LatestPosition = transform.position;
|
||||
m_LatestPositionTime = Time.time + m_InterpolationPeriod;
|
||||
m_PreviousPosition = m_LatestPosition;
|
||||
m_ActualPosition = m_LatestPosition;
|
||||
m_LatestRotation = transform.rotation;
|
||||
m_PreviousRotation = m_LatestRotation;
|
||||
m_ActualRotation = m_LatestRotation;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (IsLocal)
|
||||
{
|
||||
m_MovementUpdateTimer -= Time.deltaTime;
|
||||
if (m_MovementUpdateTimer <= 0)
|
||||
{
|
||||
if (Vector3.Distance(m_ActualPosition, transform.position) > 0.1f || Vector3.Distance(m_ActualRotation.eulerAngles, transform.eulerAngles) > 0.1f)
|
||||
{
|
||||
m_LatestPosition = transform.position;
|
||||
m_LatestPositionTime = Time.time + m_InterpolationPeriod;
|
||||
m_PreviousPosition = m_LatestPosition;
|
||||
m_ActualPosition = m_LatestPosition;
|
||||
m_LatestRotation = transform.rotation;
|
||||
m_PreviousRotation = m_LatestRotation;
|
||||
m_ActualRotation = m_LatestRotation;
|
||||
|
||||
m_MovementUpdateTimer = 0.1f;
|
||||
new PacketDronePosition()
|
||||
{
|
||||
ID = Drone.droneModel.siteId,
|
||||
Position = transform.position,
|
||||
Rotation = transform.rotation
|
||||
}.Send(Lidgren.Network.NetDeliveryMethod.Unreliable);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float t = 1.0f - ((m_LatestPositionTime - Time.time) / m_InterpolationPeriod);
|
||||
m_ActualPosition = Vector3.Lerp(m_PreviousPosition, m_LatestPosition, t);
|
||||
transform.position = m_ActualPosition;
|
||||
|
||||
m_ActualRotation = Quaternion.Slerp(m_PreviousRotation, m_LatestRotation, t);
|
||||
transform.rotation = m_ActualRotation;
|
||||
}
|
||||
}
|
||||
|
||||
public void PositionRotationUpdate(Vector3 pos, Quaternion rot, bool immediate)
|
||||
{
|
||||
if (Vector3.Distance(pos, m_ActualPosition) > 10f) // Teleport detection
|
||||
{
|
||||
immediate = true;
|
||||
}
|
||||
if (!immediate)
|
||||
{
|
||||
m_LatestPosition = pos;
|
||||
m_LatestPositionTime = Time.time + m_InterpolationPeriod;
|
||||
m_PreviousPosition = m_ActualPosition;
|
||||
|
||||
m_LatestRotation = rot;
|
||||
m_PreviousRotation = m_ActualRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_LatestPosition = pos;
|
||||
m_LatestPositionTime = Time.time + m_InterpolationPeriod;
|
||||
m_PreviousPosition = pos;
|
||||
m_ActualPosition = pos;
|
||||
transform.position = m_ActualPosition;
|
||||
|
||||
m_PreviousRotation = rot;
|
||||
m_LatestRotation = rot;
|
||||
m_ActualRotation = rot;
|
||||
transform.rotation = rot;
|
||||
}
|
||||
|
||||
if (m_RegionMember != null && m_RegionMember.hibernating)
|
||||
{
|
||||
m_RegionMember.UpdateRegionMembership(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
SRMP/Networking/NetworkDroneAmmo.cs
Normal file
18
SRMP/Networking/NetworkDroneAmmo.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SRMultiplayer.Networking
|
||||
{
|
||||
public class NetworkDroneAmmo : DroneAmmo
|
||||
{
|
||||
public Drone Drone;
|
||||
|
||||
public NetworkDroneAmmo(Drone drone) : base()
|
||||
{
|
||||
Drone = drone;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
SRMP/Networking/NetworkExchangeAcceptor.cs
Normal file
16
SRMP/Networking/NetworkExchangeAcceptor.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SRMultiplayer.Networking
|
||||
{
|
||||
public class NetworkExchangeAcceptor : MonoBehaviour
|
||||
{
|
||||
public int ID;
|
||||
public ExchangeAcceptor Acceptor;
|
||||
public NetworkRegion Region;
|
||||
}
|
||||
}
|
||||
16
SRMP/Networking/NetworkFireColumn.cs
Normal file
16
SRMP/Networking/NetworkFireColumn.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SRMultiplayer.Networking
|
||||
{
|
||||
public class NetworkFireColumn : MonoBehaviour
|
||||
{
|
||||
public int ID;
|
||||
public FireColumn Column;
|
||||
public NetworkRegion Region;
|
||||
}
|
||||
}
|
||||
95
SRMP/Networking/NetworkGadgetSite.cs
Normal file
95
SRMP/Networking/NetworkGadgetSite.cs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
using SRMultiplayer.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SRMultiplayer.Networking
|
||||
{
|
||||
public class NetworkGadgetSite : MonoBehaviour
|
||||
{
|
||||
public GadgetSite Site;
|
||||
public NetworkRegion Region;
|
||||
|
||||
public bool IsLocal { get { return Region.IsLocal; } }
|
||||
|
||||
private HydroTurret[] m_Turrets;
|
||||
private Quaternion[] m_ActualRotations;
|
||||
private Quaternion[] m_PreviousRotations;
|
||||
private Quaternion[] m_LatestRotations;
|
||||
private float m_LatestPositionTime;
|
||||
private float m_InterpolationPeriod = 0.1f;
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Site.HasAttached() && (Site.GetAttachedId() == Gadget.Id.HYDRO_TURRET || Site.GetAttachedId() == Gadget.Id.SUPER_HYDRO_TURRET))
|
||||
{
|
||||
if(m_Turrets == null)
|
||||
{
|
||||
m_Turrets = Site.gameObject.GetComponentsInChildren<HydroTurret>(true);
|
||||
m_ActualRotations = new Quaternion[m_Turrets.Length];
|
||||
m_PreviousRotations = new Quaternion[m_Turrets.Length];
|
||||
m_LatestRotations = new Quaternion[m_Turrets.Length];
|
||||
}
|
||||
if(IsLocal)
|
||||
{
|
||||
if(AnyChange())
|
||||
{
|
||||
m_LatestPositionTime = Time.time + m_InterpolationPeriod;
|
||||
var data = new List<PacketGadgetTurrets.TurretData>();
|
||||
for (int i = 0; i < m_Turrets.Length; i++)
|
||||
{
|
||||
m_ActualRotations[i] = m_Turrets[i].toRotate.rotation;
|
||||
m_PreviousRotations[i] = m_Turrets[i].toRotate.rotation;
|
||||
m_LatestRotations[i] = m_Turrets[i].toRotate.rotation;
|
||||
data.Add(new PacketGadgetTurrets.TurretData()
|
||||
{
|
||||
Index = i,
|
||||
Rotation = m_Turrets[i].toRotate.rotation
|
||||
});
|
||||
}
|
||||
new PacketGadgetTurrets()
|
||||
{
|
||||
ID = Site.id,
|
||||
Turrets = data
|
||||
}.Send(Lidgren.Network.NetDeliveryMethod.Unreliable);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float t = 1.0f - ((m_LatestPositionTime - Time.time) / m_InterpolationPeriod);
|
||||
for (int i = 0; i < m_Turrets.Length; i++)
|
||||
{
|
||||
m_ActualRotations[i] = Quaternion.Slerp(m_PreviousRotations[i], m_LatestRotations[i], t);
|
||||
m_Turrets[i].toRotate.rotation = m_ActualRotations[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Turrets = null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool AnyChange()
|
||||
{
|
||||
for(int i = 0; i < m_Turrets.Length; i++)
|
||||
{
|
||||
if(Vector3.Distance(m_ActualRotations[i].eulerAngles, m_Turrets[i].toRotate.eulerAngles) > 0.1f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void UpdateTurretRotation(int index, Quaternion rotation)
|
||||
{
|
||||
m_LatestPositionTime = Time.time + m_InterpolationPeriod;
|
||||
m_LatestRotations[index] = rotation;
|
||||
m_PreviousRotations[index] = m_ActualRotations[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
71
SRMP/Networking/NetworkGordo.cs
Normal file
71
SRMP/Networking/NetworkGordo.cs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SRMultiplayer.Networking
|
||||
{
|
||||
public class NetworkGordo : MonoBehaviour
|
||||
{
|
||||
public string ID { get { return Gordo.id == null ? Gordo.GetComponentInParent<GadgetSite>(true).id : Gordo.id; } }
|
||||
public GordoEat Gordo;
|
||||
public NetworkRegion Region;
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Globals.Gordos.Remove(ID);
|
||||
}
|
||||
|
||||
public void Eat(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
if (Gordo.eatFX != null)
|
||||
{
|
||||
SRBehaviour.SpawnAndPlayFX(Gordo.eatFX, position, rotation);
|
||||
}
|
||||
if (Gordo.eatCue != null)
|
||||
{
|
||||
SECTR_AudioSystem.Play(Gordo.eatCue, position, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Burst()
|
||||
{
|
||||
if (gameObject.activeInHierarchy)
|
||||
{
|
||||
StartCoroutine(ReachedTarget());
|
||||
}
|
||||
else
|
||||
{
|
||||
Gordo.gameObject.SetActive(false);
|
||||
Gordo.SetEatenCount(-1);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator ReachedTarget()
|
||||
{
|
||||
Gordo.WillStartBurst();
|
||||
Gordo.GetComponent<GordoFaceAnimator>().SetTrigger("Strain");
|
||||
SECTR_AudioSystem.Play(Gordo.strainCue, Gordo.transform.position, false);
|
||||
yield return new WaitForSeconds(2f);
|
||||
SECTR_AudioSystem.Play(Gordo.burstCue, Gordo.transform.position, false);
|
||||
if (Gordo.destroyFX != null)
|
||||
{
|
||||
GameObject gameObject = SRBehaviour.SpawnAndPlayFX(Gordo.destroyFX, Gordo.transform.position + Vector3.up * 2f, Gordo.transform.rotation);
|
||||
Identifiable component = Gordo.gameObject.GetComponent<Identifiable>();
|
||||
Color[] colors = SlimeUtil.GetColors(Gordo.gameObject, (component != null) ? component.id : Identifiable.Id.NONE, true);
|
||||
RecolorSlimeMaterial[] componentsInChildren = gameObject.GetComponentsInChildren<RecolorSlimeMaterial>();
|
||||
for (int i = 0; i < componentsInChildren.Length; i++)
|
||||
{
|
||||
componentsInChildren[i].SetColors(colors[0], colors[1], colors[2]);
|
||||
}
|
||||
}
|
||||
Gordo.DidCompleteBurst();
|
||||
Gordo.gameObject.SetActive(false);
|
||||
Gordo.SetEatenCount(-1);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
2048
SRMP/Networking/NetworkHandlerClient.cs
Normal file
2048
SRMP/Networking/NetworkHandlerClient.cs
Normal file
File diff suppressed because it is too large
Load diff
1773
SRMP/Networking/NetworkHandlerServer.cs
Normal file
1773
SRMP/Networking/NetworkHandlerServer.cs
Normal file
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue