Reformat, Begin packet refactors

* Reformat solution
* Start to work on packet refactors to centralize serialization logic
* Expand gitignore
This commit is contained in:
Mooshua 2023-03-06 14:23:56 -08:00
parent e3c04d2363
commit 7d40531231
18 changed files with 1939 additions and 1634 deletions

37
.gitignore vendored
View File

@ -1,2 +1,37 @@
*.swp
*.*~
project.lock.json
.DS_Store
*.pyc
nupkg/
.vs/CommunityServerAPI/v17/.suo
# Visual Studio Code
.vscode
# Rider
.idea
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
build/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
msbuild.log
msbuild.err
msbuild.wrn
# Visual Studio 2015
.vs/

View File

@ -1,358 +1,391 @@
using BattleBitAPI.Common.Enums;
using BattleBitAPI.Common.Extentions;
using BattleBitAPI.Common.Serialization;
using BattleBitAPI.Networking;
using CommunityServerAPI.BattleBitAPI;
using System;
#region
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
namespace BattleBitAPI.Client
using BattleBitAPI.Common.Enums;
using BattleBitAPI.Networking;
using CommunityServerAPI.BattleBitAPI;
using CommunityServerAPI.BattleBitAPI.Common.Extentions;
using Stream = BattleBitAPI.Common.Serialization.Stream;
#endregion
namespace BattleBitAPI.Client;
// This class was created mainly for Unity Engine, for this reason, Task async was not implemented.
public class Client
{
// This class was created mainly for Unity Engine, for this reason, Task async was not implemented.
public class Client
{
// ---- Public Variables ----
public bool IsConnected { get; private set; }
public int GamePort { get; set; } = 30000;
public bool IsPasswordProtected { get; set; } = false;
public string ServerName { get; set; } = "";
public string Gamemode { get; set; } = "";
public string Map { get; set; } = "";
public MapSize MapSize { get; set; } = MapSize._16vs16;
public MapDayNight DayNight { get; set; } = MapDayNight.Day;
public int CurrentPlayers { get; set; } = 0;
public int InQueuePlayers { get; set; } = 0;
public int MaxPlayers { get; set; } = 16;
public string LoadingScreenText { get; set; } = "";
public string ServerRulesText { get; set; } = "";
private string mDestination;
private bool mIsConnectingFlag;
private byte[] mKeepAliveBuffer;
private long mLastPackageReceived;
private long mLastPackageSent;
private int mPort;
private uint mReadPackageSize;
private Stream mReadStream;
// ---- Private Variables ----
private TcpClient mSocket;
private string mDestination;
private int mPort;
private byte[] mKeepAliveBuffer;
private Common.Serialization.Stream mWriteStream;
private Common.Serialization.Stream mReadStream;
private uint mReadPackageSize;
private long mLastPackageReceived;
private long mLastPackageSent;
private bool mIsConnectingFlag;
// ---- Private Variables ----
private TcpClient mSocket;
private Stream mWriteStream;
// ---- Construction ----
public Client(string destination, int port)
{
this.mDestination = destination;
this.mPort = port;
// ---- Construction ----
public Client(string destination, int port)
{
mDestination = destination;
mPort = port;
this.mWriteStream = new Common.Serialization.Stream()
{
Buffer = new byte[Const.MaxNetworkPackageSize],
InPool = false,
ReadPosition = 0,
WritePosition = 0,
};
this.mReadStream = new Common.Serialization.Stream()
{
Buffer = new byte[Const.MaxNetworkPackageSize],
InPool = false,
ReadPosition = 0,
WritePosition = 0,
};
this.mKeepAliveBuffer = new byte[4]
{
0,0,0,0,
};
mWriteStream = new Stream()
{
Buffer = new byte[Const.MaxNetworkPackageSize],
InPool = false,
ReadPosition = 0,
WritePosition = 0
};
mReadStream = new Stream()
{
Buffer = new byte[Const.MaxNetworkPackageSize],
InPool = false,
ReadPosition = 0,
WritePosition = 0
};
mKeepAliveBuffer = new byte[4]
{
0, 0, 0, 0
};
this.mLastPackageReceived = Extentions.TickCount;
this.mLastPackageSent = Extentions.TickCount;
}
mLastPackageReceived = Extensions.TickCount;
mLastPackageSent = Extensions.TickCount;
}
// ---- Main Tick ----
public void Tick()
{
//Are we connecting?
if (mIsConnectingFlag)
return;
// ---- Public Variables ----
public bool IsConnected { get; private set; }
//Have we connected?
if (!this.IsConnected)
{
//Attempt to connect to server async.
this.mIsConnectingFlag = true;
public int GamePort { get; set; } = 30000;
//Dispose old client if exist.
if (this.mSocket != null)
{
try { this.mSocket.Close(); } catch { }
try { this.mSocket.Dispose(); } catch { }
this.mSocket = null;
}
public bool IsPasswordProtected { get; set; } = false;
//Create new client
this.mSocket = new TcpClient();
this.mSocket.SendBufferSize = Const.MaxNetworkPackageSize;
this.mSocket.ReceiveBufferSize = Const.MaxNetworkPackageSize;
public string ServerName { get; set; } = "";
//Attempt to connect.
try
{
var state = mSocket.BeginConnect(mDestination, mPort, (x) =>
{
public string Gamemode { get; set; } = "";
try
{
//Did we connect?
mSocket.EndConnect(x);
public string Map { get; set; } = "";
var networkStream = mSocket.GetStream();
public MapSize MapSize { get; set; } = MapSize._16vs16;
//Prepare our hail package and send it.
using (var hail = BattleBitAPI.Common.Serialization.Stream.Get())
{
hail.Write((byte)NetworkCommuncation.Hail);
hail.Write((ushort)this.GamePort);
hail.Write(this.IsPasswordProtected);
hail.Write(this.ServerName);
hail.Write(this.Gamemode);
hail.Write(this.Map);
hail.Write((byte)this.MapSize);
hail.Write((byte)this.DayNight);
hail.Write((byte)this.CurrentPlayers);
hail.Write((byte)this.InQueuePlayers);
hail.Write((byte)this.MaxPlayers);
hail.Write(this.LoadingScreenText);
hail.Write(this.ServerRulesText);
public MapDayNight DayNight { get; set; } = MapDayNight.Day;
//Send our hail package.
networkStream.Write(hail.Buffer, 0, hail.WritePosition);
networkStream.Flush();
}
public int CurrentPlayers { get; set; } = 0;
//Sadly can not use Task Async here, Unity isn't great with tasks.
var watch = Stopwatch.StartNew();
public int InQueuePlayers { get; set; } = 0;
//Read the first byte.
NetworkCommuncation response = NetworkCommuncation.None;
while (watch.ElapsedMilliseconds < Const.HailConnectTimeout)
{
if (mSocket.Available > 0)
{
var data = networkStream.ReadByte();
if (data >= 0)
{
response = (NetworkCommuncation)data;
break;
}
}
Thread.Sleep(1);
}
public int MaxPlayers { get; set; } = 16;
//Were we accepted.
if (response == NetworkCommuncation.Accepted)
{
//We are accepted.
this.mIsConnectingFlag = false;
this.IsConnected = true;
public string LoadingScreenText { get; set; } = "";
mOnConnectedToServer();
}
else
{
//Did we at least got a response?
if (response == NetworkCommuncation.None)
throw new Exception("Server did not respond to your connect request.");
public string ServerRulesText { get; set; } = "";
//Try to read our deny reason.
if (response == NetworkCommuncation.Denied && mSocket.Available > 0)
{
string errorString = null;
// ---- Main Tick ----
public void Tick()
{
//Are we connecting?
if (mIsConnectingFlag)
return;
using (var readStream = BattleBitAPI.Common.Serialization.Stream.Get())
{
readStream.WritePosition = networkStream.Read(readStream.Buffer, 0, mSocket.Available);
if (!readStream.TryReadString(out errorString))
errorString = null;
}
//Have we connected?
if (!IsConnected)
{
//Attempt to connect to server async.
mIsConnectingFlag = true;
if (errorString != null)
throw new Exception(errorString);
}
//Dispose old client if exist.
if (mSocket != null)
{
try
{
mSocket.Close();
}
catch
{
}
try
{
mSocket.Dispose();
}
catch
{
}
mSocket = null;
}
throw new Exception("Server denied our connect request with an unknown reason.");
}
}
catch (Exception e)
{
//Create new client
mSocket = new TcpClient();
mSocket.SendBufferSize = Const.MaxNetworkPackageSize;
mSocket.ReceiveBufferSize = Const.MaxNetworkPackageSize;
this.mIsConnectingFlag = false;
//Attempt to connect.
try
{
var state = mSocket.BeginConnect(mDestination, mPort, (x) =>
{
try
{
//Did we connect?
mSocket.EndConnect(x);
mLogError("Unable to connect to API server: " + e.Message);
return;
}
var networkStream = mSocket.GetStream();
}, null);
}
catch
{
this.mIsConnectingFlag = false;
}
//Prepare our hail package and send it.
using (var hail = Stream.Get())
{
hail.Write((byte)NetworkCommuncation.Hail);
hail.Write((ushort)GamePort);
hail.Write(IsPasswordProtected);
hail.Write(ServerName);
hail.Write(Gamemode);
hail.Write(Map);
hail.Write((byte)MapSize);
hail.Write((byte)DayNight);
hail.Write((byte)CurrentPlayers);
hail.Write((byte)InQueuePlayers);
hail.Write((byte)MaxPlayers);
hail.Write(LoadingScreenText);
hail.Write(ServerRulesText);
//We haven't connected yet.
return;
}
//Send our hail package.
networkStream.Write(hail.Buffer, 0, hail.WritePosition);
networkStream.Flush();
}
//We are connected at this point.
//Sadly can not use Task Async here, Unity isn't great with tasks.
var watch = Stopwatch.StartNew();
try
{
//Are we still connected on socket level?
if (!mSocket.Connected)
{
mClose("Connection was terminated.");
return;
}
//Read the first byte.
var response = NetworkCommuncation.None;
while (watch.ElapsedMilliseconds < Const.HailConnectTimeout)
{
if (mSocket.Available > 0)
{
var data = networkStream.ReadByte();
if (data >= 0)
{
response = (NetworkCommuncation)data;
break;
}
}
Thread.Sleep(1);
}
var networkStream = mSocket.GetStream();
//Were we accepted.
if (response == NetworkCommuncation.Accepted)
{
//We are accepted.
mIsConnectingFlag = false;
IsConnected = true;
//Read network packages.
while (mSocket.Available > 0)
{
this.mLastPackageReceived = Extentions.TickCount;
mOnConnectedToServer();
}
else
{
//Did we at least got a response?
if (response == NetworkCommuncation.None)
throw new Exception("Server did not respond to your connect request.");
//Do we know the package size?
if (this.mReadPackageSize == 0)
{
const int sizeToRead = 4;
int leftSizeToRead = sizeToRead - this.mReadStream.WritePosition;
//Try to read our deny reason.
if (response == NetworkCommuncation.Denied && mSocket.Available > 0)
{
string errorString = null;
int read = networkStream.Read(this.mReadStream.Buffer, this.mReadStream.WritePosition, leftSizeToRead);
if (read <= 0)
throw new Exception("Connection was terminated.");
using (var readStream = Stream.Get())
{
readStream.WritePosition = networkStream.Read(readStream.Buffer, 0, mSocket.Available);
if (!readStream.TryReadString(out errorString))
errorString = null;
}
this.mReadStream.WritePosition += read;
if (errorString != null)
throw new Exception(errorString);
}
//Did we receive the package?
if (this.mReadStream.WritePosition >= 4)
{
//Read the package size
this.mReadPackageSize = this.mReadStream.ReadUInt32();
throw new Exception("Server denied our connect request with an unknown reason.");
}
}
catch (Exception e)
{
mIsConnectingFlag = false;
if (this.mReadPackageSize > Const.MaxNetworkPackageSize)
throw new Exception("Incoming package was larger than 'Conts.MaxNetworkPackageSize'");
mLogError("Unable to connect to API server: " + e.Message);
return;
}
}, null);
}
catch
{
mIsConnectingFlag = false;
}
//Is this keep alive package?
if (this.mReadPackageSize == 0)
{
Console.WriteLine("Keep alive was received.");
}
//We haven't connected yet.
return;
}
//Reset the stream.
this.mReadStream.Reset();
}
}
else
{
int sizeToRead = (int)mReadPackageSize;
int leftSizeToRead = sizeToRead - this.mReadStream.WritePosition;
//We are connected at this point.
int read = networkStream.Read(this.mReadStream.Buffer, this.mReadStream.WritePosition, leftSizeToRead);
if (read <= 0)
throw new Exception("Connection was terminated.");
try
{
//Are we still connected on socket level?
if (!mSocket.Connected)
{
mClose("Connection was terminated.");
return;
}
this.mReadStream.WritePosition += read;
var networkStream = mSocket.GetStream();
//Do we have the package?
if (this.mReadStream.WritePosition >= mReadPackageSize)
{
this.mReadPackageSize = 0;
//Read network packages.
while (mSocket.Available > 0)
{
mLastPackageReceived = Extensions.TickCount;
mExecutePackage(this.mReadStream);
//Do we know the package size?
if (mReadPackageSize == 0)
{
const int sizeToRead = 4;
var leftSizeToRead = sizeToRead - mReadStream.WritePosition;
//Reset
this.mReadStream.Reset();
}
}
}
var read = networkStream.Read(mReadStream.Buffer, mReadStream.WritePosition, leftSizeToRead);
if (read <= 0)
throw new Exception("Connection was terminated.");
//Send the network packages.
if (this.mWriteStream.WritePosition > 0)
{
lock (this.mWriteStream)
{
if (this.mWriteStream.WritePosition > 0)
{
networkStream.Write(this.mWriteStream.Buffer, 0, this.mWriteStream.WritePosition);
this.mWriteStream.WritePosition = 0;
mReadStream.WritePosition += read;
this.mLastPackageSent = Extentions.TickCount;
}
}
}
//Did we receive the package?
if (mReadStream.WritePosition >= 4)
{
//Read the package size
mReadPackageSize = mReadStream.ReadUInt32();
//Are we timed out?
if ((Extentions.TickCount - this.mLastPackageReceived) > Const.NetworkTimeout)
throw new Exception("server timedout.");
if (mReadPackageSize > Const.MaxNetworkPackageSize)
throw new Exception("Incoming package was larger than 'Conts.MaxNetworkPackageSize'");
//Send keep alive if needed
if ((Extentions.TickCount - this.mLastPackageSent) > Const.NetworkKeepAlive)
{
//Send keep alive.
networkStream.Write(this.mKeepAliveBuffer, 0, 4);
//Is this keep alive package?
if (mReadPackageSize == 0)
Console.WriteLine("Keep alive was received.");
this.mLastPackageSent = Extentions.TickCount;
//Reset the stream.
mReadStream.Reset();
}
}
else
{
var sizeToRead = (int)mReadPackageSize;
var leftSizeToRead = sizeToRead - mReadStream.WritePosition;
Console.WriteLine("Keep alive was sent.");
}
}
catch (Exception e)
{
mClose(e.Message);
}
}
var read = networkStream.Read(mReadStream.Buffer, mReadStream.WritePosition, leftSizeToRead);
if (read <= 0)
throw new Exception("Connection was terminated.");
// ---- Internal ----
private void mExecutePackage(Common.Serialization.Stream stream)
{
var communcation = (NetworkCommuncation)stream.ReadInt8();
switch (communcation)
{
mReadStream.WritePosition += read;
}
}
//Do we have the package?
if (mReadStream.WritePosition >= mReadPackageSize)
{
mReadPackageSize = 0;
// ---- Callbacks ----
private void mOnConnectedToServer()
{
mExecutePackage(mReadStream);
}
private void mOnDisconnectedFromServer(string reason)
{
}
//Reset
mReadStream.Reset();
}
}
}
//Send the network packages.
if (mWriteStream.WritePosition > 0)
lock (mWriteStream)
{
if (mWriteStream.WritePosition > 0)
{
networkStream.Write(mWriteStream.Buffer, 0, mWriteStream.WritePosition);
mWriteStream.WritePosition = 0;
mLastPackageSent = Extensions.TickCount;
}
}
//Are we timed out?
if (Extensions.TickCount - mLastPackageReceived > Const.NetworkTimeout)
throw new Exception("server timedout.");
//Send keep alive if needed
if (Extensions.TickCount - mLastPackageSent > Const.NetworkKeepAlive)
{
//Send keep alive.
networkStream.Write(mKeepAliveBuffer, 0, 4);
mLastPackageSent = Extensions.TickCount;
Console.WriteLine("Keep alive was sent.");
}
}
catch (Exception e)
{
mClose(e.Message);
}
}
// ---- Internal ----
private void mExecutePackage(Stream stream)
{
var communcation = (NetworkCommuncation)stream.ReadInt8();
switch (communcation)
{
}
}
// ---- Callbacks ----
private void mOnConnectedToServer()
{
}
private void mOnDisconnectedFromServer(string reason)
{
}
// ---- Private ----
private void mLogError(string str)
{
// ---- Private ----
private void mLogError(string str)
{
}
}
private void mClose(string reason)
{
if (this.IsConnected)
{
this.IsConnected = false;
private void mClose(string reason)
{
if (IsConnected)
{
IsConnected = false;
//Dispose old client if exist.
if (this.mSocket != null)
{
try { this.mSocket.Close(); } catch { }
try { this.mSocket.Dispose(); } catch { }
this.mSocket = null;
}
//Dispose old client if exist.
if (mSocket != null)
{
try
{
mSocket.Close();
}
catch
{
}
try
{
mSocket.Dispose();
}
catch
{
}
mSocket = null;
}
mOnDisconnectedFromServer(reason);
}
}
}
mOnDisconnectedFromServer(reason);
}
}
}

View File

@ -1,40 +1,40 @@
namespace CommunityServerAPI.BattleBitAPI
namespace CommunityServerAPI.BattleBitAPI;
public static class Const
{
public static class Const
{
// ---- Networking ----
/// <summary>
/// Maximum data size for a single package. 4MB is default.
/// </summary>
public const int MaxNetworkPackageSize = 1024 * 1024 * 4;//4mb
/// <summary>
/// How long should server/client wait until connection is determined as timed out when no packages is being sent for long time.
/// </summary>
public const int NetworkTimeout = 60 * 1000;//60 seconds
/// <summary>
/// How frequently client/server will send keep alive to each other when no message is being sent to each other for a while.
/// </summary>
public const int NetworkKeepAlive = 15 * 1000;//15 seconds
/// <summary>
/// How long server/client will wait other side to send their hail/initial package. In miliseconds.
/// </summary>
public const int HailConnectTimeout = 2 * 1000;
// ---- Networking ----
/// <summary>
/// Maximum data size for a single package. 4MB is default.
/// </summary>
public const int MaxNetworkPackageSize = 1024 * 1024 * 4; //4mb
/// <summary>
/// How long should server/client wait until connection is determined as timed out when no packages
/// is being sent for long time.
/// </summary>
public const int NetworkTimeout = 60 * 1000; //60 seconds
/// <summary>
/// How frequently client/server will send keep alive to each other when no message is being sent
/// to each other for a while.
/// </summary>
public const int NetworkKeepAlive = 15 * 1000; //15 seconds
/// <summary>
/// How long server/client will wait other side to send their hail/initial package. In miliseconds.
/// </summary>
public const int HailConnectTimeout = 2 * 1000;
// ---- Server Fields ----
public const int MinServerNameLength = 5;
public const int MaxServerNameLength = 400;
// ---- Server Fields ----
public const int MinServerNameLength = 5;
public const int MaxServerNameLength = 400;
public const int MinGamemodeNameLength = 2;
public const int MaxGamemodeNameLength = 12;
public const int MinGamemodeNameLength = 2;
public const int MaxGamemodeNameLength = 12;
public const int MinMapNameLength = 2;
public const int MaxMapNameLength = 36;
public const int MinMapNameLength = 2;
public const int MaxMapNameLength = 36;
public const int MinLoadingScreenTextLength = 0;
public const int MaxLoadingScreenTextLength = 1024 * 8;
public const int MinLoadingScreenTextLength = 0;
public const int MaxLoadingScreenTextLength = 1024 * 8;
public const int MinServerRulesTextLength = 0;
public const int MaxServerRulesTextLength = 1024 * 8;
}
}
public const int MinServerRulesTextLength = 0;
public const int MaxServerRulesTextLength = 1024 * 8;
}

View File

@ -1,8 +1,7 @@
namespace BattleBitAPI.Common.Enums
namespace BattleBitAPI.Common.Enums;
public enum MapDayNight : byte
{
public enum MapDayNight : byte
{
Day = 0,
Night = 1,
}
}
Day = 0,
Night = 1
}

View File

@ -1,10 +1,9 @@
namespace BattleBitAPI.Common.Enums
namespace BattleBitAPI.Common.Enums;
public enum MapSize : byte
{
public enum MapSize : byte
{
_16vs16 = 0,
_32vs32 = 1,
_64vs64 = 2,
_127vs127 = 3,
}
}
_16vs16 = 0,
_32vs32 = 1,
_64vs64 = 2,
_127vs127 = 3
}

View File

@ -0,0 +1,50 @@
#region
using System.Net;
using System.Net.Sockets;
#endregion
namespace CommunityServerAPI.BattleBitAPI.Common.Extentions;
public static class Extensions
{
public static long TickCount
{
get
{
#if NETCOREAPP
return Environment.TickCount64;
#else
return (long)Environment.TickCount;
#endif
}
}
public static unsafe uint ToUInt(this IPAddress address)
{
#if NETCOREAPP
return BitConverter.ToUInt32(address.GetAddressBytes());
#else
return BitConverter.ToUInt32(address.GetAddressBytes(), 0);
#endif
}
public static void SafeClose(this TcpClient client)
{
try
{
client.Close();
}
catch
{
}
try
{
client.Dispose();
}
catch
{
}
}
}

View File

@ -1,82 +0,0 @@
using System.Net;
using System.Net.Sockets;
namespace BattleBitAPI.Common.Extentions
{
public static class Extentions
{
public static long TickCount
{
get
{
#if NETCOREAPP
return System.Environment.TickCount64;
#else
return (long)Environment.TickCount;
#endif
}
}
public unsafe static uint ToUInt(this IPAddress address)
{
#if NETCOREAPP
return BitConverter.ToUInt32(address.GetAddressBytes());
#else
return BitConverter.ToUInt32(address.GetAddressBytes(), 0);
#endif
}
public static void SafeClose(this TcpClient client)
{
try { client.Close(); } catch { }
try { client.Dispose(); } catch { }
}
public static async Task<int> Read(this NetworkStream networkStream, Serialization.Stream outputStream, int size, CancellationToken token = default)
{
int read = 0;
int readUntil = outputStream.WritePosition + size;
//Ensure we have space.
outputStream.EnsureWriteBufferSize(size);
//Continue reading until we have the package.
while (outputStream.WritePosition < readUntil)
{
int sizeToRead = readUntil - outputStream.WritePosition;
int received = await networkStream.ReadAsync(outputStream.Buffer, outputStream.WritePosition, sizeToRead, token);
if (received <= 0)
throw new Exception("NetworkStream was closed.");
read += received;
outputStream.WritePosition += received;
}
return read;
}
public static async Task<bool> TryRead(this NetworkStream networkStream, Serialization.Stream outputStream, int size, CancellationToken token = default)
{
try
{
int readUntil = outputStream.WritePosition + size;
//Ensure we have space.
outputStream.EnsureWriteBufferSize(size);
//Continue reading until we have the package.
while (outputStream.WritePosition < readUntil)
{
int sizeToRead = readUntil - outputStream.WritePosition;
int received = await networkStream.ReadAsync(outputStream.Buffer, outputStream.WritePosition, sizeToRead, token);
if (received <= 0)
throw new Exception("NetworkStream was closed.");
outputStream.WritePosition += received;
}
return true;
}
catch
{
return false;
}
}
}
}

View File

@ -0,0 +1,85 @@
#region
using System.Net.Sockets;
using CommunityServerAPI.BattleBitAPI.Packets;
using Stream = BattleBitAPI.Common.Serialization.Stream;
#endregion
namespace CommunityServerAPI.BattleBitAPI.Common.Extentions;
public static class NetworkStreamExtensions
{
public static async Task<int> Read(this NetworkStream networkStream, Stream outputStream, int size, CancellationToken token = default)
{
var read = 0;
var readUntil = outputStream.WritePosition + size;
//Ensure we have space.
outputStream.EnsureWriteBufferSize(size);
//Continue reading until we have the package.
while (outputStream.WritePosition < readUntil)
{
var sizeToRead = readUntil - outputStream.WritePosition;
var received = await networkStream.ReadAsync(outputStream.Buffer, outputStream.WritePosition, sizeToRead, token);
if (received <= 0)
throw new Exception("NetworkStream was closed.");
read += received;
outputStream.WritePosition += received;
}
return read;
}
/// <summary>
/// Serialize the provided packet and send it down the network stream.
/// Returns false if an error occured, true otherwise.
/// </summary>
/// <param name="self"></param>
/// <param name="packet"></param>
/// <returns></returns>
public static async Task<bool> TryWritePacket(this NetworkStream self, BasePacket packet)
{
using (var stream = Stream.Get())
{
if (!packet.TryWrite(stream))
return false;
self.Write(stream.Buffer, 0, stream.WritePosition);
self.Flush();
}
return true;
}
public static async Task<bool> TryRead(this NetworkStream networkStream, Stream outputStream, int size, CancellationToken token = default)
{
try
{
var readUntil = outputStream.WritePosition + size;
//Ensure we have space.
outputStream.EnsureWriteBufferSize(size);
//Continue reading until we have the package.
while (outputStream.WritePosition < readUntil)
{
var sizeToRead = readUntil - outputStream.WritePosition;
var received = await networkStream.ReadAsync(outputStream.Buffer, outputStream.WritePosition, sizeToRead, token);
if (received <= 0)
throw new Exception("NetworkStream was closed.");
outputStream.WritePosition += received;
}
return true;
}
catch
{
return false;
}
}
}

View File

@ -1,8 +1,8 @@
namespace BattleBitAPI.Common.Serialization
namespace BattleBitAPI.Common.Serialization;
public interface IStreamSerializable
{
public interface IStreamSerializable
{
void Read(Stream ser);
void Write(Stream ser);
}
}
void Read(Stream ser);
void Write(Stream ser);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,70 +1,79 @@
using System;
using System.Collections.Generic;
namespace BattleBitAPI.Common.Threading;
namespace BattleBitAPI.Common.Threading
public class ThreadSafe<T>
{
public class ThreadSafe<T>
{
private System.Threading.ReaderWriterLockSlim mLock;
public T Value;
private ReaderWriterLockSlim mLock;
public T Value;
public ThreadSafe(T value)
{
this.Value = value;
this.mLock = new System.Threading.ReaderWriterLockSlim(System.Threading.LockRecursionPolicy.SupportsRecursion);
}
public ThreadSafe(T value)
{
Value = value;
mLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
}
public SafeWriteHandle GetWriteHandle() => new SafeWriteHandle(this.mLock);
public SafeReadHandle GetReadHandle() => new SafeReadHandle(this.mLock);
public SafeWriteHandle GetWriteHandle()
{
return new(mLock);
}
/// <summary>
/// Swaps current value with new value and returns old one.
/// </summary>
/// <param name="newValue"></param>
/// <returns>Old value</returns>
public T SwapValue(T newValue)
{
using (new SafeWriteHandle(this.mLock))
{
var oldValue = this.Value;
this.Value = newValue;
return oldValue;
}
}
}
public class SafeWriteHandle : System.IDisposable
{
private System.Threading.ReaderWriterLockSlim mLock;
private bool mDisposed;
public SafeWriteHandle(System.Threading.ReaderWriterLockSlim mLock)
{
this.mLock = mLock;
mLock.EnterWriteLock();
}
public void Dispose()
{
if (mDisposed)
return;
mDisposed = true;
mLock.ExitWriteLock();
}
}
public class SafeReadHandle : System.IDisposable
{
private System.Threading.ReaderWriterLockSlim mLock;
private bool mDisposed;
public SafeReadHandle(System.Threading.ReaderWriterLockSlim mLock)
{
this.mLock = mLock;
mLock.EnterReadLock();
}
public void Dispose()
{
if (mDisposed)
return;
mDisposed = true;
public SafeReadHandle GetReadHandle()
{
return new(mLock);
}
mLock.ExitReadLock();
}
}
/// <summary>
/// Swaps current value with new value and returns old one.
/// </summary>
/// <param name="newValue"></param>
/// <returns>Old value</returns>
public T SwapValue(T newValue)
{
using (new SafeWriteHandle(mLock))
{
var oldValue = Value;
Value = newValue;
return oldValue;
}
}
}
public class SafeWriteHandle : IDisposable
{
private bool mDisposed;
private ReaderWriterLockSlim mLock;
public SafeWriteHandle(ReaderWriterLockSlim mLock)
{
this.mLock = mLock;
mLock.EnterWriteLock();
}
public void Dispose()
{
if (mDisposed)
return;
mDisposed = true;
mLock.ExitWriteLock();
}
}
public class SafeReadHandle : IDisposable
{
private bool mDisposed;
private ReaderWriterLockSlim mLock;
public SafeReadHandle(ReaderWriterLockSlim mLock)
{
this.mLock = mLock;
mLock.EnterReadLock();
}
public void Dispose()
{
if (mDisposed)
return;
mDisposed = true;
mLock.ExitReadLock();
}
}

View File

@ -1,13 +1,11 @@
namespace BattleBitAPI.Networking
namespace BattleBitAPI.Networking;
public enum NetworkCommuncation : byte
{
public enum NetworkCommuncation : byte
{
//Do not use
None = 0,
//Do not use
None = 0,
Hail = 1,
Accepted = 2,
Denied = 3,
}
}
Hail = 1,
Accepted = 2,
Denied = 3
}

View File

@ -0,0 +1,18 @@
#region
using Stream = BattleBitAPI.Common.Serialization.Stream;
#endregion
namespace CommunityServerAPI.BattleBitAPI.Packets;
public abstract class BasePacket
{
/// <summary>
/// Attempt to write this packet to the provided stream.
/// Return true if the write was successful.
/// </summary>
/// <param name="destination"></param>
/// <returns></returns>
public abstract bool TryWrite(Stream destination);
}

View File

@ -0,0 +1,46 @@
#region
using BattleBitAPI.Common.Enums;
using BattleBitAPI.Networking;
using Stream = BattleBitAPI.Common.Serialization.Stream;
#endregion
namespace CommunityServerAPI.BattleBitAPI.Packets;
public class HailPacket : BasePacket
{
public byte CurrentPlayers;
public MapDayNight DayNight;
public string Gamemode;
public ushort GamePort;
public byte InQueuePlayers;
public bool IsPasswordProtected;
public string LoadingScreenText;
public string Map;
public MapSize MapSize;
public byte MaxPlayers;
public string ServerName;
public string ServerRulesText;
public override bool TryWrite(Stream destination)
{
destination.Write((byte)NetworkCommuncation.Hail);
destination.Write((ushort)GamePort);
destination.Write(IsPasswordProtected);
destination.Write(ServerName);
destination.Write(Gamemode);
destination.Write(Map);
destination.Write((byte)MapSize);
destination.Write((byte)DayNight);
destination.Write((byte)CurrentPlayers);
destination.Write((byte)InQueuePlayers);
destination.Write((byte)MaxPlayers);
destination.Write(LoadingScreenText);
destination.Write(ServerRulesText);
return true;
}
}

View File

@ -1,226 +1,241 @@
using System.Net;
#region
using System.Net;
using System.Net.Sockets;
using BattleBitAPI.Common.Enums;
using BattleBitAPI.Common.Extentions;
using BattleBitAPI.Networking;
using CommunityServerAPI.BattleBitAPI;
using CommunityServerAPI.BattleBitAPI.Common.Extentions;
namespace BattleBitAPI.Server
using Stream = BattleBitAPI.Common.Serialization.Stream;
#endregion
namespace BattleBitAPI.Server;
public class GameServer
{
public class GameServer
{
// ---- Public Variables ----
public TcpClient Socket { get; private set; }
// ---- Private Variables ----
private byte[] mKeepAliveBuffer;
private long mLastPackageReceived;
private long mLastPackageSent;
private uint mReadPackageSize;
private Stream mReadStream;
private Stream mWriteStream;
/// <summary>
/// Is game server connected to our server?
/// </summary>
public bool IsConnected { get; private set; }
public IPAddress GameIP { get; private set; }
public int GamePort { get; private set; }
public bool IsPasswordProtected { get; private set; }
public string ServerName { get; private set; }
public string Gamemode { get; private set; }
public string Map { get; private set; }
public MapSize MapSize { get; private set; }
public MapDayNight DayNight { get; private set; }
public int CurrentPlayers { get; private set; }
public int InQueuePlayers { get; private set; }
public int MaxPlayers { get; private set; }
public string LoadingScreenText { get; private set; }
public string ServerRulesText { get; private set; }
// ---- Constrction ----
public GameServer(TcpClient socket, IPAddress iP, int port, bool isPasswordProtected, string serverName, string gamemode, string map, MapSize mapSize, MapDayNight dayNight, int currentPlayers, int inQueuePlayers, int maxPlayers, string loadingScreenText, string serverRulesText)
{
IsConnected = true;
Socket = socket;
/// <summary>
/// Reason why connection was terminated.
/// </summary>
public string TerminationReason { get; private set; }
GameIP = iP;
GamePort = port;
IsPasswordProtected = isPasswordProtected;
ServerName = serverName;
Gamemode = gamemode;
Map = map;
MapSize = mapSize;
DayNight = dayNight;
CurrentPlayers = currentPlayers;
InQueuePlayers = inQueuePlayers;
MaxPlayers = maxPlayers;
LoadingScreenText = loadingScreenText;
ServerRulesText = serverRulesText;
// ---- Private Variables ----
private byte[] mKeepAliveBuffer;
private Common.Serialization.Stream mWriteStream;
private Common.Serialization.Stream mReadStream;
private uint mReadPackageSize;
private long mLastPackageReceived;
private long mLastPackageSent;
TerminationReason = string.Empty;
// ---- Constrction ----
public GameServer(TcpClient socket, IPAddress iP, int port, bool isPasswordProtected, string serverName, string gamemode, string map, MapSize mapSize, MapDayNight dayNight, int currentPlayers, int inQueuePlayers, int maxPlayers, string loadingScreenText, string serverRulesText)
{
this.IsConnected = true;
this.Socket = socket;
mWriteStream = new Stream()
{
Buffer = new byte[Const.MaxNetworkPackageSize],
InPool = false,
ReadPosition = 0,
WritePosition = 0
};
mReadStream = new Stream()
{
Buffer = new byte[Const.MaxNetworkPackageSize],
InPool = false,
ReadPosition = 0,
WritePosition = 0
};
mKeepAliveBuffer = new byte[4]
{
0, 0, 0, 0
};
this.GameIP = iP;
this.GamePort = port;
this.IsPasswordProtected = isPasswordProtected;
this.ServerName = serverName;
this.Gamemode = gamemode;
this.Map = map;
this.MapSize = mapSize;
this.DayNight = dayNight;
this.CurrentPlayers = currentPlayers;
this.InQueuePlayers = inQueuePlayers;
this.MaxPlayers = maxPlayers;
this.LoadingScreenText = loadingScreenText;
this.ServerRulesText = serverRulesText;
mLastPackageReceived = Extensions.TickCount;
mLastPackageSent = Extensions.TickCount;
}
this.TerminationReason = string.Empty;
// ---- Public Variables ----
public TcpClient Socket { get; private set; }
this.mWriteStream = new Common.Serialization.Stream()
{
Buffer = new byte[Const.MaxNetworkPackageSize],
InPool = false,
ReadPosition = 0,
WritePosition = 0,
};
this.mReadStream = new Common.Serialization.Stream()
{
Buffer = new byte[Const.MaxNetworkPackageSize],
InPool = false,
ReadPosition = 0,
WritePosition = 0,
};
this.mKeepAliveBuffer = new byte[4]
{
0,0,0,0,
};
/// <summary>
/// Is game server connected to our server?
/// </summary>
public bool IsConnected { get; private set; }
this.mLastPackageReceived = Extentions.TickCount;
this.mLastPackageSent = Extentions.TickCount;
}
public IPAddress GameIP { get; private set; }
// ---- Tick ----
public async Task Tick()
{
if (!this.IsConnected)
return;
public int GamePort { get; private set; }
try
{
//Are we still connected on socket level?
if (!Socket.Connected)
{
mClose("Connection was terminated.");
return;
}
public bool IsPasswordProtected { get; private set; }
var networkStream = Socket.GetStream();
public string ServerName { get; private set; }
//Read network packages.
while (Socket.Available > 0)
{
this.mLastPackageReceived = Extentions.TickCount;
public string Gamemode { get; private set; }
//Do we know the package size?
if (this.mReadPackageSize == 0)
{
const int sizeToRead = 4;
int leftSizeToRead = sizeToRead - this.mReadStream.WritePosition;
public string Map { get; private set; }
int read = await networkStream.ReadAsync(this.mReadStream.Buffer, this.mReadStream.WritePosition, leftSizeToRead);
if (read <= 0)
throw new Exception("Connection was terminated.");
public MapSize MapSize { get; private set; }
this.mReadStream.WritePosition += read;
public MapDayNight DayNight { get; private set; }
//Did we receive the package?
if (this.mReadStream.WritePosition >= 4)
{
//Read the package size
this.mReadPackageSize = this.mReadStream.ReadUInt32();
public int CurrentPlayers { get; private set; }
if (this.mReadPackageSize > Const.MaxNetworkPackageSize)
throw new Exception("Incoming package was larger than 'Conts.MaxNetworkPackageSize'");
public int InQueuePlayers { get; private set; }
//Is this keep alive package?
if (this.mReadPackageSize == 0)
{
Console.WriteLine("Keep alive was received.");
}
public int MaxPlayers { get; private set; }
//Reset the stream.
this.mReadStream.Reset();
}
}
else
{
int sizeToRead = (int)mReadPackageSize;
int leftSizeToRead = sizeToRead - this.mReadStream.WritePosition;
public string LoadingScreenText { get; private set; }
int read = await networkStream.ReadAsync(this.mReadStream.Buffer, this.mReadStream.WritePosition, leftSizeToRead);
if (read <= 0)
throw new Exception("Connection was terminated.");
public string ServerRulesText { get; private set; }
this.mReadStream.WritePosition += read;
/// <summary>
/// Reason why connection was terminated.
/// </summary>
public string TerminationReason { get; private set; }
//Do we have the package?
if (this.mReadStream.WritePosition >= mReadPackageSize)
{
this.mReadPackageSize = 0;
// ---- Tick ----
public async Task Tick()
{
if (!IsConnected)
return;
await mExecutePackage(this.mReadStream);
try
{
//Are we still connected on socket level?
if (!Socket.Connected)
{
mClose("Connection was terminated.");
return;
}
//Reset
this.mReadStream.Reset();
}
}
}
var networkStream = Socket.GetStream();
//Send the network packages.
if (this.mWriteStream.WritePosition > 0)
{
lock (this.mWriteStream)
{
if (this.mWriteStream.WritePosition > 0)
{
networkStream.Write(this.mWriteStream.Buffer, 0, this.mWriteStream.WritePosition);
this.mWriteStream.WritePosition = 0;
//Read network packages.
while (Socket.Available > 0)
{
mLastPackageReceived = Extensions.TickCount;
this.mLastPackageSent = Extentions.TickCount;
}
}
}
//Do we know the package size?
if (mReadPackageSize == 0)
{
const int sizeToRead = 4;
var leftSizeToRead = sizeToRead - mReadStream.WritePosition;
//Are we timed out?
if ((Extentions.TickCount - this.mLastPackageReceived) > Const.NetworkTimeout)
throw new Exception("Game server timedout.");
var read = await networkStream.ReadAsync(mReadStream.Buffer, mReadStream.WritePosition, leftSizeToRead);
if (read <= 0)
throw new Exception("Connection was terminated.");
//Send keep alive if needed
if ((Extentions.TickCount - this.mLastPackageSent) > Const.NetworkKeepAlive)
{
//Send keep alive.
networkStream.Write(this.mKeepAliveBuffer, 0, 4);
mReadStream.WritePosition += read;
this.mLastPackageSent = Extentions.TickCount;
//Did we receive the package?
if (mReadStream.WritePosition >= 4)
{
//Read the package size
mReadPackageSize = mReadStream.ReadUInt32();
Console.WriteLine("Keep alive was sent.");
}
}
catch (Exception e)
{
mClose(e.Message);
}
}
if (mReadPackageSize > Const.MaxNetworkPackageSize)
throw new Exception("Incoming package was larger than 'Conts.MaxNetworkPackageSize'");
// ---- Internal ----
private async Task mExecutePackage(Common.Serialization.Stream stream)
{
var communcation = (NetworkCommuncation)stream.ReadInt8();
switch (communcation)
{
//Is this keep alive package?
if (mReadPackageSize == 0)
Console.WriteLine("Keep alive was received.");
}
}
//Reset the stream.
mReadStream.Reset();
}
}
else
{
var sizeToRead = (int)mReadPackageSize;
var leftSizeToRead = sizeToRead - mReadStream.WritePosition;
private void mClose(string reason)
{
if (this.IsConnected)
{
this.TerminationReason = reason;
this.IsConnected = false;
var read = await networkStream.ReadAsync(mReadStream.Buffer, mReadStream.WritePosition, leftSizeToRead);
if (read <= 0)
throw new Exception("Connection was terminated.");
this.mWriteStream = null;
this.mReadStream = null;
}
}
}
}
mReadStream.WritePosition += read;
//Do we have the package?
if (mReadStream.WritePosition >= mReadPackageSize)
{
mReadPackageSize = 0;
await mExecutePackage(mReadStream);
//Reset
mReadStream.Reset();
}
}
}
//Send the network packages.
if (mWriteStream.WritePosition > 0)
lock (mWriteStream)
{
if (mWriteStream.WritePosition > 0)
{
networkStream.Write(mWriteStream.Buffer, 0, mWriteStream.WritePosition);
mWriteStream.WritePosition = 0;
mLastPackageSent = Extensions.TickCount;
}
}
//Are we timed out?
if (Extensions.TickCount - mLastPackageReceived > Const.NetworkTimeout)
throw new Exception("Game server timedout.");
//Send keep alive if needed
if (Extensions.TickCount - mLastPackageSent > Const.NetworkKeepAlive)
{
//Send keep alive.
networkStream.Write(mKeepAliveBuffer, 0, 4);
mLastPackageSent = Extensions.TickCount;
Console.WriteLine("Keep alive was sent.");
}
}
catch (Exception e)
{
mClose(e.Message);
}
}
// ---- Internal ----
private async Task mExecutePackage(Stream stream)
{
var communcation = (NetworkCommuncation)stream.ReadInt8();
switch (communcation)
{
}
}
private void mClose(string reason)
{
if (IsConnected)
{
TerminationReason = reason;
IsConnected = false;
mWriteStream = null;
mReadStream = null;
}
}
}

View File

@ -1,359 +1,377 @@
using System.Net;
#region
using System.Net;
using System.Net.Sockets;
using BattleBitAPI.Common.Enums;
using BattleBitAPI.Common.Extentions;
using BattleBitAPI.Common.Serialization;
using BattleBitAPI.Networking;
using CommunityServerAPI.BattleBitAPI;
using CommunityServerAPI.BattleBitAPI.Common.Extentions;
namespace BattleBitAPI.Server
using Stream = BattleBitAPI.Common.Serialization.Stream;
#endregion
namespace BattleBitAPI.Server;
public class ServerListener : IDisposable
{
public class ServerListener : IDisposable
{
// --- Public ---
public bool IsListening { get; private set; }
public bool IsDisposed { get; private set; }
public int ListeningPort { get; private set; }
// --- Private ---
private TcpListener mSocket;
// --- Events ---
/// <summary>
/// Fired when an attempt made to connect to the server.
/// Connection will be allowed if function returns true, otherwise will be blocked.
/// Default, any connection attempt will be accepted.
/// </summary>
public Func<IPAddress, Task<bool>> OnGameServerConnecting { get; set; }
// --- Construction ---
public ServerListener()
{
}
/// <summary>
/// Fired when a game server connects.
/// </summary>
public Func<GameServer, Task> OnGameServerConnected { get; set; }
/// <summary>
/// Fired when a game server disconnects. Check (server.TerminationReason) to see the reason.
/// </summary>
public Func<GameServer, Task> OnGameServerDisconnected { get; set; }
// --- Public ---
public bool IsListening { get; private set; }
// --- Private ---
private TcpListener mSocket;
public bool IsDisposed { get; private set; }
// --- Construction ---
public ServerListener() { }
public int ListeningPort { get; private set; }
// --- Starting ---
public void Start(IPAddress bindIP, int port)
{
if (this.IsDisposed)
throw new ObjectDisposedException(this.GetType().FullName);
if (bindIP == null)
throw new ArgumentNullException(nameof(bindIP));
if (IsListening)
throw new Exception("Server is already listening.");
// --- Events ---
/// <summary>
/// Fired when an attempt made to connect to the server.
/// Connection will be allowed if function returns true, otherwise will be blocked.
/// Default, any connection attempt will be accepted.
/// </summary>
public Func<IPAddress, Task<bool>> OnGameServerConnecting { get; set; }
this.mSocket = new TcpListener(bindIP, port);
this.mSocket.Start();
/// <summary>
/// Fired when a game server connects.
/// </summary>
public Func<GameServer, Task> OnGameServerConnected { get; set; }
this.ListeningPort = port;
this.IsListening = true;
/// <summary>
/// Fired when a game server disconnects. Check (server.TerminationReason) to see the reason.
/// </summary>
public Func<GameServer, Task> OnGameServerDisconnected { get; set; }
mMainLoop();
}
public void Start(int port)
{
Start(IPAddress.Loopback, port);
}
// --- Disposing ---
public void Dispose()
{
//Already disposed?
if (IsDisposed)
return;
IsDisposed = true;
// --- Stopping ---
public void Stop()
{
if (this.IsDisposed)
throw new ObjectDisposedException(this.GetType().FullName);
if (!IsListening)
throw new Exception("Already not running.");
if (IsListening)
Stop();
}
try
{
mSocket.Stop();
}
catch { }
// --- Starting ---
public void Start(IPAddress bindIP, int port)
{
if (IsDisposed)
throw new ObjectDisposedException(GetType().FullName);
if (bindIP == null)
throw new ArgumentNullException(nameof(bindIP));
if (IsListening)
throw new Exception("Server is already listening.");
this.mSocket = null;
this.ListeningPort = 0;
this.IsListening = true;
}
mSocket = new TcpListener(bindIP, port);
mSocket.Start();
// --- Main Loop ---
private async Task mMainLoop()
{
while (IsListening)
{
var client = await mSocket.AcceptTcpClientAsync();
mInternalOnClientConnecting(client);
}
}
private async Task mInternalOnClientConnecting(TcpClient client)
{
var ip = (client.Client.RemoteEndPoint as IPEndPoint).Address;
ListeningPort = port;
IsListening = true;
bool allow = true;
if (OnGameServerConnecting != null)
allow = await OnGameServerConnecting(ip);
mMainLoop();
}
if (!allow)
{
//Connection is not allowed from this IP.
client.SafeClose();
return;
}
public void Start(int port)
{
Start(IPAddress.Loopback, port);
}
GameServer server = null;
try
{
using (CancellationTokenSource source = new CancellationTokenSource(Const.HailConnectTimeout))
{
using (var readStream = Common.Serialization.Stream.Get())
{
var networkStream = client.GetStream();
// --- Stopping ---
public void Stop()
{
if (IsDisposed)
throw new ObjectDisposedException(GetType().FullName);
if (!IsListening)
throw new Exception("Already not running.");
//Read package type
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 1, source.Token))
throw new Exception("Unable to read the package type");
NetworkCommuncation type = (NetworkCommuncation)readStream.ReadInt8();
if (type != NetworkCommuncation.Hail)
throw new Exception("Incoming package wasn't hail.");
}
try
{
mSocket.Stop();
}
catch
{
}
//Read port
int gamePort;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 2, source.Token))
throw new Exception("Unable to read the Port");
gamePort = readStream.ReadUInt16();
}
mSocket = null;
ListeningPort = 0;
IsListening = true;
}
//Read is Port protected
bool isPasswordProtected;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 1, source.Token))
throw new Exception("Unable to read the IsPasswordProtected");
isPasswordProtected = readStream.ReadBool();
}
// --- Main Loop ---
private async Task mMainLoop()
{
while (IsListening)
{
var client = await mSocket.AcceptTcpClientAsync();
mInternalOnClientConnecting(client);
}
}
//Read the server name
string serverName;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 2, source.Token))
throw new Exception("Unable to read the ServerName Size");
private async Task mInternalOnClientConnecting(TcpClient client)
{
var ip = (client.Client.RemoteEndPoint as IPEndPoint).Address;
int stringSize = readStream.ReadUInt16();
if (stringSize < Const.MinServerNameLength || stringSize > Const.MaxServerNameLength)
throw new Exception("Invalid server name size");
var allow = true;
if (OnGameServerConnecting != null)
allow = await OnGameServerConnecting(ip);
readStream.Reset();
if (!await networkStream.TryRead(readStream, stringSize, source.Token))
throw new Exception("Unable to read the ServerName");
if (!allow)
{
//Connection is not allowed from this IP.
client.SafeClose();
return;
}
serverName = readStream.ReadString(stringSize);
}
GameServer server = null;
try
{
using (var source = new CancellationTokenSource(Const.HailConnectTimeout))
{
using (var readStream = Stream.Get())
{
var networkStream = client.GetStream();
//Read the gamemode
string gameMode;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 2, source.Token))
throw new Exception("Unable to read the gamemode Size");
//Read package type
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 1, source.Token))
throw new Exception("Unable to read the package type");
var type = (NetworkCommuncation)readStream.ReadInt8();
if (type != NetworkCommuncation.Hail)
throw new Exception("Incoming package wasn't hail.");
}
int stringSize = readStream.ReadUInt16();
if (stringSize < Const.MinGamemodeNameLength || stringSize > Const.MaxGamemodeNameLength)
throw new Exception("Invalid gamemode size");
//Read port
int gamePort;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 2, source.Token))
throw new Exception("Unable to read the Port");
gamePort = readStream.ReadUInt16();
}
readStream.Reset();
if (!await networkStream.TryRead(readStream, stringSize, source.Token))
throw new Exception("Unable to read the gamemode");
//Read is Port protected
bool isPasswordProtected;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 1, source.Token))
throw new Exception("Unable to read the IsPasswordProtected");
isPasswordProtected = readStream.ReadBool();
}
gameMode = readStream.ReadString(stringSize);
}
//Read the server name
string serverName;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 2, source.Token))
throw new Exception("Unable to read the ServerName Size");
//Read the gamemap
string gamemap;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 2, source.Token))
throw new Exception("Unable to read the map size");
int stringSize = readStream.ReadUInt16();
if (stringSize < Const.MinServerNameLength || stringSize > Const.MaxServerNameLength)
throw new Exception("Invalid server name size");
int stringSize = readStream.ReadUInt16();
if (stringSize < Const.MinMapNameLength || stringSize > Const.MaxMapNameLength)
throw new Exception("Invalid map size");
readStream.Reset();
if (!await networkStream.TryRead(readStream, stringSize, source.Token))
throw new Exception("Unable to read the ServerName");
readStream.Reset();
if (!await networkStream.TryRead(readStream, stringSize, source.Token))
throw new Exception("Unable to read the map");
serverName = readStream.ReadString(stringSize);
}
gamemap = readStream.ReadString(stringSize);
}
//Read the gamemode
string gameMode;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 2, source.Token))
throw new Exception("Unable to read the gamemode Size");
//Read the mapSize
MapSize size;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 1, source.Token))
throw new Exception("Unable to read the MapSize");
size = (MapSize)readStream.ReadInt8();
}
int stringSize = readStream.ReadUInt16();
if (stringSize < Const.MinGamemodeNameLength || stringSize > Const.MaxGamemodeNameLength)
throw new Exception("Invalid gamemode size");
//Read the day night
MapDayNight dayNight;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 1, source.Token))
throw new Exception("Unable to read the MapDayNight");
dayNight = (MapDayNight)readStream.ReadInt8();
}
readStream.Reset();
if (!await networkStream.TryRead(readStream, stringSize, source.Token))
throw new Exception("Unable to read the gamemode");
//Current Players
int currentPlayers;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 1, source.Token))
throw new Exception("Unable to read the Current Players");
currentPlayers = readStream.ReadInt8();
}
gameMode = readStream.ReadString(stringSize);
}
//Queue Players
int queuePlayers;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 1, source.Token))
throw new Exception("Unable to read the Queue Players");
queuePlayers = readStream.ReadInt8();
}
//Read the gamemap
string gamemap;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 2, source.Token))
throw new Exception("Unable to read the map size");
//Max Players
int maxPlayers;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 1, source.Token))
throw new Exception("Unable to read the Max Players");
maxPlayers = readStream.ReadInt8();
}
int stringSize = readStream.ReadUInt16();
if (stringSize < Const.MinMapNameLength || stringSize > Const.MaxMapNameLength)
throw new Exception("Invalid map size");
//Read Loading Screen Text
string loadingScreenText;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 2, source.Token))
throw new Exception("Unable to read the Loading Screen Text Size");
readStream.Reset();
if (!await networkStream.TryRead(readStream, stringSize, source.Token))
throw new Exception("Unable to read the map");
int stringSize = readStream.ReadUInt16();
if (stringSize < Const.MinLoadingScreenTextLength || stringSize > Const.MaxLoadingScreenTextLength)
throw new Exception("Invalid server Loading Screen Text Size");
gamemap = readStream.ReadString(stringSize);
}
if (stringSize > 0)
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, stringSize, source.Token))
throw new Exception("Unable to read the Loading Screen Text");
//Read the mapSize
MapSize size;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 1, source.Token))
throw new Exception("Unable to read the MapSize");
size = (MapSize)readStream.ReadInt8();
}
loadingScreenText = readStream.ReadString(stringSize);
}
else
{
loadingScreenText = string.Empty;
}
}
//Read the day night
MapDayNight dayNight;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 1, source.Token))
throw new Exception("Unable to read the MapDayNight");
dayNight = (MapDayNight)readStream.ReadInt8();
}
//Read Server Rules Text
string serverRulesText;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 2, source.Token))
throw new Exception("Unable to read the Server Rules Text Size");
//Current Players
int currentPlayers;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 1, source.Token))
throw new Exception("Unable to read the Current Players");
currentPlayers = readStream.ReadInt8();
}
int stringSize = readStream.ReadUInt16();
if (stringSize < Const.MinServerRulesTextLength || stringSize > Const.MaxServerRulesTextLength)
throw new Exception("Invalid server Server Rules Text Size");
//Queue Players
int queuePlayers;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 1, source.Token))
throw new Exception("Unable to read the Queue Players");
queuePlayers = readStream.ReadInt8();
}
if (stringSize > 0)
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, stringSize, source.Token))
throw new Exception("Unable to read the Server Rules Text");
//Max Players
int maxPlayers;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 1, source.Token))
throw new Exception("Unable to read the Max Players");
maxPlayers = readStream.ReadInt8();
}
serverRulesText = readStream.ReadString(stringSize);
}
else
{
serverRulesText = string.Empty;
}
}
//Read Loading Screen Text
string loadingScreenText;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 2, source.Token))
throw new Exception("Unable to read the Loading Screen Text Size");
server = new GameServer(client, ip, gamePort, isPasswordProtected, serverName, gameMode, gamemap, size, dayNight, currentPlayers, queuePlayers, maxPlayers, loadingScreenText, serverRulesText);
int stringSize = readStream.ReadUInt16();
if (stringSize < Const.MinLoadingScreenTextLength || stringSize > Const.MaxLoadingScreenTextLength)
throw new Exception("Invalid server Loading Screen Text Size");
//Send accepted notification.
networkStream.WriteByte((byte)NetworkCommuncation.Accepted);
}
}
}
catch (Exception e)
{
try
{
Console.WriteLine(e.Message);
if (stringSize > 0)
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, stringSize, source.Token))
throw new Exception("Unable to read the Loading Screen Text");
var networkStream = client.GetStream();
using (var pck = BattleBitAPI.Common.Serialization.Stream.Get())
{
pck.Write((byte)NetworkCommuncation.Denied);
pck.Write(e.Message);
loadingScreenText = readStream.ReadString(stringSize);
}
else
{
loadingScreenText = string.Empty;
}
}
//Send denied notification.
networkStream.Write(pck.Buffer, 0, pck.WritePosition);
}
await networkStream.FlushAsync();
}
catch { }
//Read Server Rules Text
string serverRulesText;
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, 2, source.Token))
throw new Exception("Unable to read the Server Rules Text Size");
int stringSize = readStream.ReadUInt16();
if (stringSize < Const.MinServerRulesTextLength || stringSize > Const.MaxServerRulesTextLength)
throw new Exception("Invalid server Server Rules Text Size");
if (stringSize > 0)
{
readStream.Reset();
if (!await networkStream.TryRead(readStream, stringSize, source.Token))
throw new Exception("Unable to read the Server Rules Text");
serverRulesText = readStream.ReadString(stringSize);
}
else
{
serverRulesText = string.Empty;
}
}
server = new GameServer(client, ip, gamePort, isPasswordProtected, serverName, gameMode, gamemap, size, dayNight, currentPlayers, queuePlayers, maxPlayers, loadingScreenText, serverRulesText);
//Send accepted notification.
networkStream.WriteByte((byte)NetworkCommuncation.Accepted);
}
}
}
catch (Exception e)
{
try
{
Console.WriteLine(e.Message);
var networkStream = client.GetStream();
using (var pck = Stream.Get())
{
pck.Write((byte)NetworkCommuncation.Denied);
pck.Write(e.Message);
//Send denied notification.
networkStream.Write(pck.Buffer, 0, pck.WritePosition);
}
await networkStream.FlushAsync();
}
catch
{
}
client.SafeClose();
return;
}
client.SafeClose();
return;
}
//Call the callback.
if (OnGameServerConnected != null)
await OnGameServerConnected.Invoke(server);
//Call the callback.
if (OnGameServerConnected != null)
await OnGameServerConnected.Invoke(server);
//Set the buffer sizes.
client.ReceiveBufferSize = Const.MaxNetworkPackageSize;
client.SendBufferSize = Const.MaxNetworkPackageSize;
//Set the buffer sizes.
client.ReceiveBufferSize = Const.MaxNetworkPackageSize;
client.SendBufferSize = Const.MaxNetworkPackageSize;
//Join to main server loop.
await mHandleGameServer(server);
}
private async Task mHandleGameServer(GameServer server)
{
while (server.IsConnected)
{
await server.Tick();
await Task.Delay(1);
}
//Join to main server loop.
await mHandleGameServer(server);
}
if (OnGameServerDisconnected != null)
await OnGameServerDisconnected.Invoke(server);
}
private async Task mHandleGameServer(GameServer server)
{
while (server.IsConnected)
{
await server.Tick();
await Task.Delay(1);
}
// --- Disposing ---
public void Dispose()
{
//Already disposed?
if (this.IsDisposed)
return;
this.IsDisposed = true;
if (IsListening)
Stop();
}
}
}
if (OnGameServerDisconnected != null)
await OnGameServerDisconnected.Invoke(server);
}
}

View File

@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
</Project>

View File

@ -1,47 +1,54 @@
using BattleBitAPI.Client;
using BattleBitAPI.Server;
#region
using System.Net;
class Program
using BattleBitAPI.Client;
using BattleBitAPI.Server;
#endregion
internal class Program
{
static void Main(string[] args)
{
if (Console.ReadLine().Contains("h"))
{
ServerListener server = new ServerListener();
server.OnGameServerConnecting += OnClientConnecting;
server.OnGameServerConnected += OnGameServerConnected;
server.OnGameServerDisconnected += OnGameServerDisconnected;
server.Start(29294);
private static void Main(string[] args)
{
if (Console.ReadLine().Contains("h"))
{
var server = new ServerListener();
server.OnGameServerConnecting += OnClientConnecting;
server.OnGameServerConnected += OnGameServerConnected;
server.OnGameServerDisconnected += OnGameServerDisconnected;
server.Start(29294);
Thread.Sleep(-1);
}
else
{
Client c = new Client("127.0.0.1", 29294);
c.ServerName = "Test Server";
c.Gamemode = "TDP";
c.Map = "DustyDew";
Thread.Sleep(-1);
}
else
{
var c = new Client("127.0.0.1", 29294);
c.ServerName = "Test Server";
c.Gamemode = "TDP";
c.Map = "DustyDew";
while (true)
{
c.Tick();
Thread.Sleep(1);
}
}
}
while (true)
{
c.Tick();
Thread.Sleep(1);
}
}
}
private static async Task<bool> OnClientConnecting(IPAddress ip)
{
Console.WriteLine(ip + " is connecting.");
return true;
}
private static async Task OnGameServerConnected(GameServer server)
{
Console.WriteLine("Server " + server.ServerName + " was connected.");
}
private static async Task OnGameServerDisconnected(GameServer server)
{
Console.WriteLine("Server " + server.ServerName + " was disconnected. (" + server.TerminationReason + ")");
}
private static async Task<bool> OnClientConnecting(IPAddress ip)
{
Console.WriteLine(ip + " is connecting.");
return true;
}
private static async Task OnGameServerConnected(GameServer server)
{
Console.WriteLine("Server " + server.ServerName + " was connected.");
}
private static async Task OnGameServerDisconnected(GameServer server)
{
Console.WriteLine("Server " + server.ServerName + " was disconnected. (" + server.TerminationReason + ")");
}
}