first commit

This commit is contained in:
techiesplash 2023-01-15 13:26:28 -08:00
commit 90b9eae405
43 changed files with 2245 additions and 0 deletions

View file

@ -0,0 +1,50 @@
namespace Matchmaker.Server.BaseServer;
public class ClientAttributes
{
private const uint MaxAttributes = 64;
private class Attribute
{
// ReSharper disable once FieldCanBeMadeReadOnly.Local
public string AttributeName;
public string AttributeValue;
public Attribute(string name, string value)
{
AttributeName = name;
AttributeValue = value;
}
}
private readonly List<Attribute> _attributes = new();
public void SetAttribute(string name, string value)
{
// Check if the attribute is already set
foreach (var attr in _attributes.Where(attr => attr.AttributeName.Equals(name)))
{
attr.AttributeValue = value;
return;
}
// If it is not
if (_attributes.Count < MaxAttributes)
{
_attributes.Add(new Attribute(name, value));
}
else
{
Console.WriteLine("Failed to add attribute: Reached limit!");
}
}
public string? GetAttribute(string name)
{
return _attributes.Where(attr => attr.AttributeName.Equals(name)).Select(attr => attr.AttributeValue).FirstOrDefault();
}
public void Clear()
{
_attributes.Clear();
}
}

306
Matchmaker/BaseServer/Client.cs Executable file
View file

@ -0,0 +1,306 @@
using System.Net;
using System.Net.Sockets;
namespace Matchmaker.Server.BaseServer;
public class Client
{
public ServerSend.StatusType LastStatus;
public readonly ClientAttributes Attributes = new();
public readonly TCP? Tcp;
public readonly UDP Udp;
public readonly Server ServerParent;
public Client(int clientId, Server server)
{
Tcp = new TCP(clientId, server);
Udp = new UDP(clientId, server);
ServerParent = server;
}
public bool IsConnected
{
get
{
try
{
if (Tcp?.Socket != null && Tcp.Socket.Client != null && Tcp.Socket.Client.Connected)
{
/* pear to the documentation on Poll:
* When passing SelectMode.SelectRead as a parameter to the Poll method it will return
* -either- true if Socket.Listen(Int32) has been called and a connection is pending;
* -or- true if data is available for reading;
* -or- true if the connection has been closed, reset, or terminated;
* otherwise, returns false
*/
// Detect if client disconnected
if (Tcp.Socket.Client.Poll(0, SelectMode.SelectRead))
{
byte[] buff = new byte[1];
if (Tcp.Socket.Client.Receive(buff, SocketFlags.Peek) == 0)
{
// Client disconnected
return false;
}
else
{
return true;
}
}
return true;
}
else
{
return false;
}
}
catch
{
return false;
}
}
}
// ReSharper disable once InconsistentNaming
public class TCP
{
private readonly Server _serv;
public TcpClient? Socket;
private readonly int _id;
private NetworkStream? _stream;
private Packet? _receivedData;
private byte[]? _receiveBuffer;
public TCP(int id, Server server)
{
_serv = server;
_id = id;
}
public void Connect(TcpClient? socket)
{
Terminal.LogDebug("Connecting TCP...");
Socket = socket;
if (Socket != null)
{
Socket.ReceiveBufferSize = Constants.DataBufferSize;
Socket.SendBufferSize = Constants.DataBufferSize;
_stream = Socket.GetStream();
}
_receivedData = new Packet();
_receiveBuffer = new byte[Constants.DataBufferSize];
_stream?.BeginRead(_receiveBuffer, 0, Constants.DataBufferSize, ReceiveCallback, null);
ServerSend.Welcome(_serv, _id, "Welcome to the server!");
}
public void SendData(Packet packet)
{
try
{
if (Socket != null)
{
_stream?.BeginWrite(packet.ToArray(), 0, packet.Length(), null, null);
}
}
catch (Exception ex)
{
Terminal.LogError($"Error sending data to player {_id} via TCP: {ex}");
}
}
private void ReceiveCallback(IAsyncResult result)
{
try
{
if (Socket?.Client.RemoteEndPoint is not IPEndPoint remoteIpEndPoint)
{
Terminal.LogDebug($"[{_serv.DisplayName}]: RemoteIpEndPoint is NULL!");
_serv.Clients[_id].Attributes.Clear();
_serv.Clients[_id].Disconnect();
return;
}
if (_stream != null)
{
var byteLength = _stream.EndRead(result);
if (byteLength <= 0)
{
Terminal.LogDebug("Client has left. Disconnecting...");
_serv.DisconnectClient(_id);
return;
}
var data = new byte[byteLength];
if (_receiveBuffer != null) Array.Copy(_receiveBuffer, data, byteLength);
_receivedData?.Reset(HandleData(data));
}
if (_receiveBuffer != null)
{
_stream?.BeginRead(_receiveBuffer, 0, Constants.DataBufferSize, ReceiveCallback, null);
}
}
catch (Exception ex)
{
Terminal.LogError($"[{_serv.DisplayName}] Error receiving TCP data: {ex}");
_serv.DisconnectClient(_id);
}
}
private bool HandleData(IEnumerable<byte> data)
{
var packetLength = 0;
_receivedData?.SetBytes(data);
if (_receivedData?.UnreadLength() >= 4)
{
packetLength = _receivedData.ReadInt();
if (packetLength <= 0)
{
return true;
}
}
while (packetLength > 0 && packetLength <= _receivedData?.UnreadLength())
{
var packetBytes = _receivedData.ReadBytes(packetLength);
ThreadManager.ExecuteOnMainThread(() =>
{
try
{
var packet = new Packet(packetBytes);
var packetId = packet.ReadInt();
if ((packetId != int.MaxValue - 3) && Config.Sync)
{
ServerSend.Status(_serv, _id, ServerSend.StatusType.RECEIVED);
}
Terminal.LogDebug($"[{_serv.DisplayName}] Received TCP Packet with ID: " + packetId);
var x = _serv.Packets?.PacketHandlers.ContainsKey(packetId);
if (x != null && x != false)
{
_serv.Packets?.PacketHandlers[packetId].DynamicInvoke(_serv, _id, packet);
}
else
{
Terminal.LogError(
$"[{_serv.DisplayName}] Received unregistered TCP packet with ID: {packetId}");
}
}
catch (Exception e)
{
Terminal.LogError($"[{_serv.DisplayName}] Error processing packet: {e}");
throw;
}
});
packetLength = 0;
if (_receivedData.UnreadLength() < 4) continue;
packetLength = _receivedData.ReadInt();
if (packetLength <= 0)
{
return true;
}
}
return packetLength <= 1;
}
public void Disconnect()
{
Socket?.Close();
_stream = null;
_receivedData = null;
_receiveBuffer = null;
Socket = null;
}
}
// ReSharper disable once InconsistentNaming
public class UDP
{
public IPEndPoint? EndPoint;
private readonly Server _serv;
private readonly int _id;
public UDP(int id, Server server)
{
_serv = server;
_id = id;
}
public void Connect(IPEndPoint endPoint)
{
EndPoint = endPoint;
}
public void SendData(Packet packet)
{
if (EndPoint != null) _serv.SendUdpData(EndPoint, packet);
}
public void HandleData(Packet packetData)
{
var packetLength = packetData.ReadInt();
var packetBytes = packetData.ReadBytes(packetLength);
ThreadManager.ExecuteOnMainThread(() =>
{
try
{
using var packet = new Packet(packetBytes);
var packetId = packet.ReadInt();
if ((packetId != int.MaxValue - 3) && Config.Sync)
{
ServerSend.Status(_serv, _id, ServerSend.StatusType.RECEIVED);
}
Terminal.LogDebug($"[{_serv.DisplayName}] Received UDP Packet with ID: " + packetId);
var x = _serv.Packets?.PacketHandlers.ContainsKey(packetId);
if (x != null && x != false)
{
_serv.Packets?.PacketHandlers[packetId].DynamicInvoke(_serv, _id, packet);
}
else
{
Terminal.LogError(
$"[{_serv.DisplayName}] Received unregistered UDP packet with ID: {packetId}");
}
}
catch (Exception e)
{
Terminal.LogError($"[{_serv.DisplayName}] Error processing packet: {e}");
throw;
}
});
}
public void Disconnect()
{
EndPoint = null;
}
}
public void Disconnect()
{
Terminal.LogInfo(
$"[{ServerParent.DisplayName}] Disconnecting Client ({Tcp?.Socket?.Client.RemoteEndPoint})...");
Tcp?.Disconnect();
Udp.Disconnect();
Attributes.Clear();
}
}

View file

@ -0,0 +1,12 @@
namespace Matchmaker.Server.BaseServer;
internal static class Constants
{
public const int TicksPerSec = 30;
public const float MsPerTick = 1000f / TicksPerSec;
public const int DataBufferSize = 4096;
}

View file

@ -0,0 +1,12 @@
namespace Matchmaker.Server.BaseServer;
internal static class GameLogic
{
public static void Update()
{
ThreadManager.UpdateMain();
}
}

398
Matchmaker/BaseServer/Packet.cs Executable file
View file

@ -0,0 +1,398 @@
using System.Numerics;
using System.Text;
namespace Matchmaker.Server.BaseServer;
public sealed class Packet : IDisposable
{
private List<byte> _buffer;
private byte[] _readableBuffer = null!;
private int _readPos;
/// <summary>Creates a new empty packet (without an ID).</summary>
public Packet()
{
_buffer = new List<byte>(); // Initialize buffer
_readPos = 0; // Set readPos to 0
}
/// <summary>Creates a new packet with a given ID. Used for sending.</summary>
/// <param name="id">The packet ID.</param>
public Packet(int id)
{
_buffer = new List<byte>(); // Initialize buffer
_readPos = 0; // Set readPos to 0
Write(id); // Write packet id to the buffer
}
/// <summary>Creates a packet from which data can be read. Used for receiving.</summary>
/// <param name="data">The bytes to add to the packet.</param>
public Packet(IEnumerable<byte> data)
{
_buffer = new List<byte>(); // Initialize buffer
_readPos = 0; // Set readPos to 0
SetBytes(data);
}
#region Functions
/// <summary>Sets the packet's content and prepares it to be read.</summary>
/// <param name="data">The bytes to add to the packet.</param>
public void SetBytes(IEnumerable<byte> data)
{
Write(data);
_readableBuffer = _buffer.ToArray();
}
/// <summary>Inserts the length of the packet's content at the start of the buffer.</summary>
public void WriteLength()
{
_buffer.InsertRange(0, BitConverter.GetBytes(_buffer.Count)); // Insert the byte length of the packet at the very beginning
}
/// <summary>Inserts the given int at the start of the buffer.</summary>
/// <param name="value">The int to insert.</param>
// ReSharper disable once UnusedMember.Global
public void InsertInt(int value)
{
_buffer.InsertRange(0, BitConverter.GetBytes(value)); // Insert the int at the start of the buffer
}
/// <summary>Gets the packet's content in array form.</summary>
public byte[] ToArray()
{
_readableBuffer = _buffer.ToArray();
return _readableBuffer;
}
/// <summary>Gets the length of the packet's content.</summary>
public int Length()
{
return _buffer.Count; // Return the length of buffer
}
/// <summary>Gets the length of the unread data contained in the packet.</summary>
public int UnreadLength()
{
return Length() - _readPos; // Return the remaining length (unread)
}
/// <summary>Resets the packet instance to allow it to be reused.</summary>
/// <param name="shouldReset">Whether or not to reset the packet.</param>
public void Reset(bool shouldReset = true)
{
if (shouldReset)
{
_buffer.Clear(); // Clear buffer
_readableBuffer = null!;
_readPos = 0; // Reset readPos
}
else
{
_readPos -= 4; // "Unread" the last read int
}
}
#endregion
#region Write Data
/// <summary>Adds a byte to the packet.</summary>
/// <param name="value">The byte to add.</param>
// ReSharper disable once UnusedMember.Global
public void Write(byte value)
{
_buffer.Add(value);
}
/// <summary>Adds an array of bytes to the packet.</summary>
/// <param name="value">The byte array to add.</param>
// ReSharper disable once MemberCanBePrivate.Global
public void Write(IEnumerable<byte> value)
{
_buffer.AddRange(value);
}
/// <summary>Adds a short to the packet.</summary>
/// <param name="value">The short to add.</param>
// ReSharper disable once UnusedMember.Global
public void Write(short value)
{
_buffer.AddRange(BitConverter.GetBytes(value));
}
/// <summary>Adds an int to the packet.</summary>
/// <param name="value">The int to add.</param>
public void Write(int value)
{
_buffer.AddRange(BitConverter.GetBytes(value));
}
/// <summary>Adds a long to the packet.</summary>
/// <param name="value">The long to add.</param>
// ReSharper disable once UnusedMember.Global
public void Write(long value)
{
_buffer.AddRange(BitConverter.GetBytes(value));
}
/// <summary>Adds a float to the packet.</summary>
/// <param name="value">The float to add.</param>
// ReSharper disable once UnusedMember.Global
public void Write(float value)
{
_buffer.AddRange(BitConverter.GetBytes(value));
}
/// <summary>Adds a bool to the packet.</summary>
/// <param name="value">The bool to add.</param>
// ReSharper disable once UnusedMember.Global
public void Write(bool value)
{
_buffer.AddRange(BitConverter.GetBytes(value));
}
/// <summary>Adds a string to the packet.</summary>
/// <param name="value">The string to add.</param>
// ReSharper disable once UnusedMember.Global
public void Write(string value)
{
Write(value.Length); // Add the length of the string to the packet
_buffer.AddRange(Encoding.ASCII.GetBytes(value)); // Add the string itself
}
/// <summary>Adds a Vector3 to the packet.</summary>
/// <param name="value">The Vector3 to add.</param>
// ReSharper disable once UnusedMember.Global
public void Write(Vector3 value)
{
Write(value.X);
Write(value.Y);
Write(value.Z);
}
/// <summary>Adds a Quaternion to the packet.</summary>
/// <param name="value">The Quaternion to add.</param>
// ReSharper disable once UnusedMember.Global
public void Write(Quaternion value)
{
Write(value.X);
Write(value.Y);
Write(value.Z);
Write(value.W);
}
#endregion
#region Read Data
/// <summary>Reads a byte from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
// ReSharper disable once UnusedMember.Global
public byte ReadByte(bool moveReadPos = true)
{
if (_buffer.Count > _readPos)
{
// If there are unread bytes
var value = _readableBuffer[_readPos]; // Get the byte at readPos' position
if (moveReadPos)
{
// If _moveReadPos is true
_readPos += 1; // Increase readPos by 1
}
return value; // Return the byte
}
// ReSharper disable once RedundantIfElseBlock
else
{
throw new Exception("Could not read value of type 'byte'!");
}
}
/// <summary>Reads an array of bytes from the packet.</summary>
/// <param name="length">The length of the byte array.</param>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
public byte[] ReadBytes(int length, bool moveReadPos = true)
{
if (_buffer.Count > _readPos)
{
// If there are unread bytes
var value = _buffer.GetRange(_readPos, length).ToArray(); // Get the bytes at readPos' position with a range of _length
if (moveReadPos)
{
// If _moveReadPos is true
_readPos += length; // Increase readPos by _length
}
return value; // Return the bytes
}
// ReSharper disable once RedundantIfElseBlock
else
{
throw new Exception("Could not read value of type 'byte[]'!");
}
}
/// <summary>Reads a short from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
// ReSharper disable once UnusedMember.Global
public short ReadShort(bool moveReadPos = true)
{
if (_buffer.Count > _readPos)
{
// If there are unread bytes
var value = BitConverter.ToInt16(_readableBuffer, _readPos); // Convert the bytes to a short
if (moveReadPos)
{
// If _moveReadPos is true and there are unread bytes
_readPos += 2; // Increase readPos by 2
}
return value; // Return the short
}
// ReSharper disable once RedundantIfElseBlock
else
{
throw new Exception("Could not read value of type 'short'!");
}
}
/// <summary>Reads an int from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
public int ReadInt(bool moveReadPos = true)
{
if (_buffer.Count > _readPos)
{
// If there are unread bytes
var value = BitConverter.ToInt32(_readableBuffer, _readPos); // Convert the bytes to an int
if (moveReadPos)
{
// If _moveReadPos is true
_readPos += 4; // Increase readPos by 4
}
return value; // Return the int
}
// ReSharper disable once RedundantIfElseBlock
else
{
throw new Exception("Could not read value of type 'int'!");
}
}
/// <summary>Reads a long from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
// ReSharper disable once UnusedMember.Global
public long ReadLong(bool moveReadPos = true)
{
if (_buffer.Count > _readPos)
{
// If there are unread bytes
var value = BitConverter.ToInt64(_readableBuffer, _readPos); // Convert the bytes to a long
if (moveReadPos)
{
// If _moveReadPos is true
_readPos += 8; // Increase readPos by 8
}
return value; // Return the long
}
// ReSharper disable once RedundantIfElseBlock
else
{
throw new Exception("Could not read value of type 'long'!");
}
}
/// <summary>Reads a float from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
public float ReadFloat(bool moveReadPos = true)
{
if (_buffer.Count > _readPos)
{
// If there are unread bytes
var value = BitConverter.ToSingle(_readableBuffer, _readPos); // Convert the bytes to a float
if (moveReadPos)
{
// If _moveReadPos is true
_readPos += 4; // Increase readPos by 4
}
return value; // Return the float
}
// ReSharper disable once RedundantIfElseBlock
else
{
throw new Exception("Could not read value of type 'float'!");
}
}
/// <summary>Reads a bool from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
// ReSharper disable once UnusedMember.Global
public bool ReadBool(bool moveReadPos = true)
{
if (_buffer.Count > _readPos)
{
// If there are unread bytes
var value = BitConverter.ToBoolean(_readableBuffer, _readPos); // Convert the bytes to a bool
if (moveReadPos)
{
// If _moveReadPos is true
_readPos += 1; // Increase readPos by 1
}
return value; // Return the bool
}
// ReSharper disable once RedundantIfElseBlock
else
{
throw new Exception("Could not read value of type 'bool'!");
}
}
/// <summary>Reads a string from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
public string ReadString(bool moveReadPos = true)
{
try
{
var length = ReadInt(); // Get the length of the string
var value = Encoding.ASCII.GetString(_readableBuffer, _readPos, length); // Convert the bytes to a string
if (moveReadPos && value.Length > 0)
{
// If _moveReadPos is true string is not empty
_readPos += length; // Increase readPos by the length of the string
}
return value; // Return the string
}
catch
{
throw new Exception("Could not read value of type 'string'!");
}
}
/// <summary>Reads a Vector3 from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
// ReSharper disable once UnusedMember.Global
public Vector3 ReadVector3(bool moveReadPos = true)
{
return new Vector3(ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos));
}
/// <summary>Reads a Quaternion from the packet.</summary>
/// <param name="moveReadPos">Whether or not to move the buffer's read position.</param>
// ReSharper disable once UnusedMember.Global
public Quaternion ReadQuaternion(bool moveReadPos = true)
{
return new Quaternion(ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos), ReadFloat(moveReadPos));
}
#endregion
private bool _disposed;
private void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
_buffer = null!;
_readableBuffer = null!;
_readPos = 0;
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
// ReSharper disable once GCSuppressFinalizeForTypeWithoutDestructor
GC.SuppressFinalize(this);
}
}

159
Matchmaker/BaseServer/Server.cs Executable file
View file

@ -0,0 +1,159 @@
using System.Net;
using System.Net.Sockets;
namespace Matchmaker.Server.BaseServer;
public class Server
{
public int MaxPlayers;
private int _port;
public readonly Dictionary<int, Client> Clients = new();
public string DisplayName = "Unnamed Server";
public class ServerPackets
{
public delegate void PacketHandler(Server server, int fromClient, Packet packet);
// ReSharper disable once FieldCanBeMadeReadOnly.Global
public Dictionary<int, PacketHandler> PacketHandlers;
public ServerPackets(Dictionary<int, PacketHandler> packetHandlers)
{
PacketHandlers = packetHandlers;
PacketHandlers.Add(int.MaxValue, ServerHandle.WelcomeReceived);
PacketHandlers.Add(int.MaxValue - 1, ServerHandle.SetClientAttribute);
PacketHandlers.Add(int.MaxValue - 2, ServerHandle.GetClientAttribute);
}
}
public ServerPackets? Packets;
/*
public Dictionary<int, Delegate> packetHandlers = new Dictionary<int, Delegate>()
{
{ (int)ClientPackets.welcomeReceived, ServerHandle.WelcomeReceived },
{ (int)ClientPackets.setClientAttributes, ServerHandle.SetClientAttribute },
{ (int)ClientPackets.getClientAttributes, ServerHandle.GetClientAttribute }
};
*/
private TcpListener _tcpListener = null!;
private UdpClient _udpListener = null!;
// ReSharper disable once UnusedMethodReturnValue.Global
public bool Start(ServerPackets packets, int maxPlayers, int port, string displayName)
{
try
{
DisplayName = displayName;
Packets = packets;
MaxPlayers = maxPlayers;
_port = port;
InitializeServerData();
_tcpListener = new TcpListener(IPAddress.Any, _port);
_tcpListener.Start();
_tcpListener.BeginAcceptTcpClient(TcpConnectCallback, null);
_udpListener = new UdpClient(_port);
_udpListener.BeginReceive(UdpReceiveCallback, null);
Terminal.LogSuccess($"[{DisplayName}] Server started on port {_port}.");
return true;
}
catch (Exception ex)
{
Terminal.LogError($"[{DisplayName}] Failed to start server: {ex}");
return false;
}
}
public void DisconnectClient(int id)
{
Clients[id].Disconnect();
}
private void TcpConnectCallback(IAsyncResult result)
{
var client = _tcpListener.EndAcceptTcpClient(result);
_tcpListener.BeginAcceptTcpClient(TcpConnectCallback, null);
Terminal.LogInfo($"[{DisplayName}] Incoming connection from {client.Client.RemoteEndPoint}...");
for (var i = 1; i <= MaxPlayers; i++)
{
if (Clients[i].Tcp!.Socket != null) continue;
Clients[i].Tcp!.Connect(client);
return;
}
Terminal.LogWarn($"[{DisplayName}] {client.Client.RemoteEndPoint} failed to connect: Server full!");
}
private void UdpReceiveCallback(IAsyncResult result)
{
try
{
var clientEndPoint = new IPEndPoint(IPAddress.Any, 0);
var data = _udpListener.EndReceive(result, ref clientEndPoint!);
_udpListener.BeginReceive(UdpReceiveCallback, null);
if (data.Length < 4)
{
return;
}
using var packet = new Packet(data);
var clientId = packet.ReadInt();
if (clientId == 0)
{
return;
}
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (Clients[clientId].Udp.EndPoint == null)
{
Clients[clientId].Udp.Connect(clientEndPoint);
return;
}
if (Clients[clientId].Udp.EndPoint?.ToString() == clientEndPoint.ToString())
{
Clients[clientId].Udp.HandleData(packet);
}
}
catch (Exception ex)
{
Terminal.LogError($"[{DisplayName}] Error receiving UDP data: {ex}");
}
}
public void SendUdpData(IPEndPoint clientEndPoint, Packet packet)
{
try
{
_udpListener.BeginSend(packet.ToArray(), packet.Length(), clientEndPoint, null, null);
}
catch (Exception ex)
{
Terminal.LogError($"[{DisplayName}] Error sending data to {clientEndPoint} via UDP: {ex}");
}
}
private void InitializeServerData()
{
for (var i = 1; i <= MaxPlayers; i++)
{
Clients.Add(i, new Client(i, this));
}
Terminal.LogSuccess($"[{DisplayName}] Server data is now initialized.");
}
}

View file

@ -0,0 +1,156 @@
using System.Net;
// ReSharper disable once CheckNamespace
namespace Matchmaker.Server.BaseServer;
internal static class ServerHandle
{
/// <summary>
/// Gets the message received in response to the Welcome message
/// </summary>
/// <param name="server"></param>
/// <param name="fromClient"></param>
/// <param name="packet"></param>
public static void WelcomeReceived(Server server, int fromClient, Packet packet)
{
try
{
var clientIdCheck = packet.ReadInt();
var clientAPIVersion = packet.ReadString();
var clientGameVersion = packet.ReadString();
var clientGameId = packet.ReadString();
if (clientAPIVersion != Config.MatchmakerAPIVersion)
{
Terminal.LogError(
$"[{server.DisplayName}] Client API is not the same version as the Server! \n Server API version: {Config.MatchmakerAPIVersion}. \n Client API version: {clientAPIVersion}");
server.Clients[fromClient].Disconnect();
return;
}
if (clientGameId != Config.GameId)
{
Terminal.LogError($"Client Game ID and Server Game ID do not match: \n Server Game ID: {Config.GameId}. \n Client Game ID: {clientGameId}.");
server.Clients[fromClient].Disconnect();
return;
}
if (clientGameVersion != Config.GameVersion)
{
Terminal.LogError($"Client Game Version and Server Game Version do not match: \n Server Game Ver: {Config.GameVersion}. \n Client Game Ver: {clientGameVersion}.");
server.Clients[fromClient].Disconnect();
return;
}
var tcp = server.Clients[fromClient].Tcp;
if (tcp == null)
{
Terminal.LogWarn("TCP is null!");
}
if (tcp is { Socket: { } })
Terminal.LogSuccess(
$"[{server.DisplayName}] {tcp.Socket.Client.RemoteEndPoint} connected successfully and is now player {fromClient}.");
if (fromClient != clientIdCheck)
{
Terminal.LogWarn(
$"[{server.DisplayName}] Player (ID: {fromClient}) has assumed the wrong client ID ({clientIdCheck})!");
}
var remoteIpEndPoint = server.Clients[fromClient].Tcp?.Socket?.Client.RemoteEndPoint as IPEndPoint;
if (remoteIpEndPoint == null)
{
Terminal.LogDebug($"[{server.DisplayName}] WelcomeReceived: RemoteIpEndPoint is NULL!");
server.Clients[fromClient].Attributes.Clear();
server.Clients[fromClient].Disconnect();
return;
}
var uuidList = new List<string>();
for (var i = 1; i < server.Clients.Count; i++)
{
if (server.Clients[i].IsConnected)
{
var x = server.Clients[i].Attributes.GetAttribute("_auto_uuid");
if (x != null)
{
uuidList.Add(x);
}
}
}
server.Clients[fromClient].Attributes.SetAttribute("_auto_ip", remoteIpEndPoint.Address.ToString());
server.Clients[fromClient].Attributes.SetAttribute("_auto_port", remoteIpEndPoint.Port.ToString());
server.Clients[fromClient].Attributes
.SetAttribute("_auto_uuid", new UUID(Config.UuidLength, uuidList).GetValue());
server.Clients[fromClient].Attributes
.SetAttribute("_auto_game_version", clientGameVersion);
Terminal.LogInfo(
$"[{server.DisplayName}] Automatically assigned _auto_ip for client {fromClient}. _auto_ip = {server.Clients[fromClient].Attributes.GetAttribute("_auto_ip")}");
Terminal.LogInfo(
$"[{server.DisplayName}] Automatically assigned _auto_port for client {fromClient}. _auto_port = {server.Clients[fromClient].Attributes.GetAttribute("_auto_port")}");
Terminal.LogInfo(
$"[{server.DisplayName}] Automatically assigned _auto_uuid for client {fromClient}. _auto_uuid = {server.Clients[fromClient].Attributes.GetAttribute("_auto_uuid")}");
Terminal.LogInfo(
$"[{server.DisplayName}] Automatically assigned _auto_game_version for client {fromClient} based on received Client data. _auto_game_version = {server.Clients[fromClient].Attributes.GetAttribute("_auto_game_version")}");
}
catch (Exception ex)
{
Terminal.LogError($"[{server.DisplayName}] Error in WelcomeReceived packet handler: {ex}");
ServerSend.Status(server, fromClient, ServerSend.StatusType.FAIL);
server.Clients[fromClient].Disconnect();
}
}
public static void SetClientAttribute(Server server, int fromClient, Packet packet)
{
var clientIdCheck = packet.ReadInt();
var name = packet.ReadString();
var value = packet.ReadString();
var tcp = server.Clients[fromClient].Tcp;
if (tcp is { Socket: { } })
Terminal.LogInfo(
$"[{server.DisplayName}] {tcp.Socket.Client.RemoteEndPoint} is changing Client Attribute {name} to {value}.");
if (fromClient != clientIdCheck)
{
Terminal.LogWarn(
$"[{server.DisplayName}] Player (ID: {fromClient}) has assumed the wrong client ID ({clientIdCheck})!");
return;
}
server.Clients[fromClient].Attributes.SetAttribute(name, value);
}
public static void GetClientAttribute(Server server, int fromClient, Packet packet)
{
var clientIdCheck = packet.ReadInt();
var requestedId = packet.ReadInt();
var name = packet.ReadString();
try
{
var tcp = server.Clients[fromClient].Tcp;
if (tcp is { Socket: { } })
Terminal.LogInfo(
$"[{server.DisplayName}] {tcp.Socket.Client.RemoteEndPoint} requests Client Attribute {name}.");
if (fromClient != clientIdCheck)
{
Terminal.LogWarn(
$"[{server.DisplayName}] Player (ID: {fromClient}) has assumed the wrong client ID ({clientIdCheck})!");
}
var value = server.Clients[requestedId].Attributes.GetAttribute(name);
ServerSend.GetClientAttributesReceived(server, fromClient, requestedId, name, value ?? "");
}
catch (Exception ex)
{
Terminal.LogError($"[{server.DisplayName}] Error in GetClientAttribute: {ex}");
ServerSend.GetClientAttributesReceived(server, fromClient, requestedId, name, "ERR_HANDLED_EXCEPTION");
}
}
}

View file

@ -0,0 +1,277 @@
namespace Matchmaker.Server.BaseServer;
public static class ServerSend
{
public enum StatusType
{
OK,
RECEIVED,
FAIL
}
/// <summary>
/// Sends TCP data to a Client
/// </summary>
/// <param name="server">Server the Client is in</param>
/// <param name="toClient">ID for the recipient</param>
/// <param name="packet">Data to send</param>
public static void SendTcpData(Server server, int toClient, Packet packet)
{
packet.WriteLength();
var tcp = server.Clients[toClient].Tcp;
tcp?.SendData(packet);
}
/// <summary>
/// Sends UDP data to a Client
/// </summary>
/// <param name="server">Server the Client is in</param>
/// <param name="toClient">ID for the recipient</param>
/// <param name="packet">Data to send</param>
// ReSharper disable once UnusedMember.Global
public static void SendUdpData(Server server, int toClient, Packet packet)
{
packet.WriteLength();
var udp = server.Clients[toClient].Udp;
udp.SendData(packet);
}
/// <summary>
/// Sends TCP data to all Clients
/// </summary>
/// <param name="server">Server the Client is in</param>
/// <param name="packet">Data to send</param>
// ReSharper disable once UnusedMember.Global
public static void SendTcpDataToAll(Server server, Packet packet)
{
packet.WriteLength();
for (var i = 1; i <= server.MaxPlayers; i++)
{
var tcp = server.Clients[i].Tcp;
tcp?.SendData(packet);
}
}
/// <summary>
/// Sends TCP data to all Clients except one
/// </summary>
/// <param name="server">Server the Client is in</param>
/// /// <param name="exceptClient">What Client to exclude</param>
/// <param name="packet">Data to send</param>
// ReSharper disable once UnusedMember.Global
public static void SendTcpDataToAll(Server server, int exceptClient, Packet packet)
{
packet.WriteLength();
for (var i = 1; i <= server.MaxPlayers; i++)
{
if (i == exceptClient) continue;
var tcp = server.Clients[i].Tcp;
tcp?.SendData(packet);
}
}
/// <summary>
/// Sends UDP data to all Clients
/// </summary>
/// <param name="server">Server the Client is in</param>
/// <param name="packet">Data to send</param>
// ReSharper disable once UnusedMember.Global
public static void SendUdpDataToAll(Server server, Packet packet)
{
packet.WriteLength();
for (var i = 1; i <= server.MaxPlayers; i++)
{
var udp = server.Clients[i].Udp;
udp.SendData(packet);
}
}
/// <summary>
/// Sends UDP data to all Clients except one
/// </summary>
/// <param name="server">Server the Client is in</param>
/// <param name="exceptClient">What Client to exclude</param>
/// <param name="packet">Data to send</param>
// ReSharper disable once UnusedMember.Global
public static void SendUdpDataToAll(Server server, int exceptClient, Packet packet)
{
packet.WriteLength();
for (var i = 1; i <= server.MaxPlayers; i++)
{
if (i == exceptClient) continue;
var udp = server.Clients[i].Udp;
udp.SendData(packet);
}
}
/// <summary>
/// Sends TCP data to a Client
/// </summary>
/// <param name="server">Server the Client is in</param>
/// <param name="toClient">ID for the recipient</param>
/// <param name="packet">Data to send</param>
public static void SendTcpDataNoSync(Server server, int toClient, Packet packet)
{
packet.WriteLength();
var tcp = server.Clients[toClient].Tcp;
tcp?.SendData(packet);
}
/// <summary>
/// Sends UDP data to a Client
/// </summary>
/// <param name="server">Server the Client is in</param>
/// <param name="toClient">ID for the recipient</param>
/// <param name="packet">Data to send</param>
// ReSharper disable once UnusedMember.Global
public static void SendUdpDataNoSync(Server server, int toClient, Packet packet)
{
packet.WriteLength();
var udp = server.Clients[toClient].Udp;
udp.SendData(packet);
}
/// <summary>
/// Sends TCP data to all Clients
/// </summary>
/// <param name="server">Server the Client is in</param>
/// <param name="packet">Data to send</param>
// ReSharper disable once UnusedMember.Global
public static void SendTcpDataToAllNoSync(Server server, Packet packet)
{
packet.WriteLength();
for (var i = 1; i <= server.MaxPlayers; i++)
{
var tcp = server.Clients[i].Tcp;
tcp?.SendData(packet);
}
}
/// <summary>
/// Sends TCP data to all Clients except one
/// </summary>
/// <param name="server">Server the Client is in</param>
/// /// <param name="exceptClient">What Client to exclude</param>
/// <param name="packet">Data to send</param>
// ReSharper disable once UnusedMember.Global
public static void SendTcpDataToAllNoSync(Server server, int exceptClient, Packet packet)
{
packet.WriteLength();
for (var i = 1; i <= server.MaxPlayers; i++)
{
if (i == exceptClient) continue;
var tcp = server.Clients[i].Tcp;
tcp?.SendData(packet);
}
}
/// <summary>
/// Sends UDP data to all Clients
/// </summary>
/// <param name="server">Server the Client is in</param>
/// <param name="packet">Data to send</param>
// ReSharper disable once UnusedMember.Global
public static void SendUdpDataToAllNoSync(Server server, Packet packet)
{
packet.WriteLength();
for (var i = 1; i <= server.MaxPlayers; i++)
{
var udp = server.Clients[i].Udp;
udp.SendData(packet);
}
}
/// <summary>
/// Sends UDP data to all Clients except one
/// </summary>
/// <param name="server">Server the Client is in</param>
/// <param name="exceptClient">What Client to exclude</param>
/// <param name="packet">Data to send</param>
// ReSharper disable once UnusedMember.Global
public static void SendUdpDataToAllNoSync(Server server, int exceptClient, Packet packet)
{
packet.WriteLength();
for (var i = 1; i <= server.MaxPlayers; i++)
{
if (i == exceptClient) continue;
var udp = server.Clients[i].Udp;
udp.SendData(packet);
}
}
#region Built-in Packets
/// <summary>
/// Welcome message for Client
/// </summary>
/// <param name="server">Server the Client is in</param>
/// <param name="toClient">ID for the recipient</param>
/// <param name="msg">Message to send</param>
public static void Welcome(Server server, int toClient, string msg)
{
Terminal.LogDebug($"[{server.DisplayName}] Sending 'Welcome' message.");
using var packet = new Packet(int.MaxValue);
packet.Write(msg);
packet.Write(toClient);
packet.Write(Config.MatchmakerAPIVersion);
packet.Write(Config.GameId);
packet.Write(Config.GameVersion);
SendTcpData(server, toClient, packet);
}
/// <summary>
/// Disconnect a Client
/// </summary>
/// <param name="server">What server the Client is in</param>
/// <param name="toClient">What Client to kick</param>
public static void DisconnectClient(Server server, int toClient)
{
using var packet = new Packet(int.MaxValue-1);
SendTcpData(server, toClient, packet);
}
/// <summary>
/// Send an Attribute to the Client
/// </summary>
/// <param name="server">What server the Client is in</param>
/// <param name="toClient">ID for the recipient</param>
/// <param name="requestedId">What Client the Attribute is from</param>
/// <param name="name">The Attribute name</param>
/// <param name="value">The Attribute value</param>
public static void GetClientAttributesReceived(BaseServer.Server server, int toClient, int requestedId, string name, string value)
{
using var packet = new Packet(int.MaxValue-2);
packet.Write(requestedId);
packet.Write(name);
packet.Write(value);
ServerSend.SendTcpData(server, toClient, packet);
}
/// <summary>
/// Respond to the client with a status
/// </summary>
/// <param name="server">What server the Client is in</param>
/// <param name="toClient">ID for the recipient</param>
/// <param name="status">What status</param>
public static void Status(BaseServer.Server server, int toClient, StatusType status)
{
Terminal.LogDebug($"[{server.DisplayName}] Sending status {status}...");
using var packet = new Packet(int.MaxValue-3);
packet.Write((int)status);
ServerSend.SendUdpDataNoSync(server, toClient, packet);
}
#endregion
}

View file

@ -0,0 +1,98 @@
/*
* MIT License
*
* Copyright (c) 2022 Vincent Dowling
*
* 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.
*/
namespace Matchmaker.Server.BaseServer;
public static class Terminal
{
// ReSharper disable once UnusedMember.Global
public static void Log(string str)
{
Console.ForegroundColor = ConsoleColor.White;
Console.Write(Config.LogText);
Console.ForegroundColor = ConsoleColor.White;
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
Console.ForegroundColor = ConsoleColor.White;
}
// ReSharper disable once UnusedMember.Global
public static void LogError(string str)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.Write(Config.LogErrorText);
Console.ForegroundColor = ConsoleColor.White;
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
Console.ForegroundColor = ConsoleColor.White;
}
// ReSharper disable once UnusedMember.Global
public static void LogDebug(string str)
{
if (Config.ShowTerminalDebug)
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.Write(Config.LogDebugText);
Console.ForegroundColor = ConsoleColor.White;
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
Console.ForegroundColor = ConsoleColor.White;
}
}
// ReSharper disable once UnusedMember.Global
public static void LogWarn(string str)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write(Config.LogWarningText);
Console.ForegroundColor = ConsoleColor.White;
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
Console.ForegroundColor = ConsoleColor.White;
}
// ReSharper disable once UnusedMember.Global
public static void LogSuccess(string str)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.Write(Config.LogSuccessText);
Console.ForegroundColor = ConsoleColor.White;
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
Console.ForegroundColor = ConsoleColor.White;
}
// ReSharper disable once UnusedMember.Global
public static void LogInfo(string str)
{
Console.ForegroundColor = ConsoleColor.Blue;
Console.Write(Config.LogInfoText);
Console.ForegroundColor = ConsoleColor.White;
Console.Write("({0:t}) " + str + "\n", DateTime.Now);
Console.ForegroundColor = ConsoleColor.White;
}
}

View file

@ -0,0 +1,93 @@
namespace Matchmaker.Server.BaseServer;
internal static class ThreadManager
{
private static readonly List<Action> ExecuteOnMainThreadList = new();
private static readonly List<Action> ExecuteCopiedOnMainThread = new();
private static bool _actionToExecuteOnMainThread;
/// <summary>
/// Controls whether or not the Main Thread should run. Set to FALSE to shut down Main Thread.
/// </summary>
private static bool _isRunning;
private static Thread? _thread;
/// <summary>Sets an action to be executed on the main thread.</summary>
/// <param name="action">The action to be executed on the main thread.</param>
public static void ExecuteOnMainThread(Action action)
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (action == null)
{
Terminal.LogWarn("No action to execute on main thread!");
return;
}
lock (ExecuteOnMainThreadList)
{
ExecuteOnMainThreadList.Add(action);
_actionToExecuteOnMainThread = true;
}
}
/// <summary>
/// Start the Main Thread so Packets can be acted upon
/// </summary>
public static void Start()
{
_isRunning = true;
_thread = new Thread(MainThread);
_thread.Start();
}
/// <summary>
/// Stop the Main Thread
/// </summary>
public static void Stop()
{
_isRunning = false;
}
/// <summary>
/// Executes all code meant to run on the main thread. NOTE: Call this ONLY from the main thread.
/// </summary>
public static void UpdateMain()
{
if (!_actionToExecuteOnMainThread) return;
ExecuteCopiedOnMainThread.Clear();
lock (ExecuteOnMainThreadList)
{
ExecuteCopiedOnMainThread.AddRange(ExecuteOnMainThreadList);
ExecuteOnMainThreadList.Clear();
_actionToExecuteOnMainThread = false;
}
foreach (var t in ExecuteCopiedOnMainThread)
{
t();
}
}
private static void MainThread()
{
var nextLoop = DateTime.Now;
while (_isRunning)
{
while (nextLoop < DateTime.Now)
{
GameLogic.Update();
nextLoop = nextLoop.AddMilliseconds(Constants.MsPerTick);
if (nextLoop > DateTime.Now)
{
Thread.Sleep(nextLoop - DateTime.Now);
}
}
}
}
}

69
Matchmaker/BaseServer/UUID.cs Executable file
View file

@ -0,0 +1,69 @@
namespace Matchmaker.Server.BaseServer;
/// <summary>
/// A UUID Object
/// </summary>
public class UUID
{
private string value = "";
/// <summary>
/// Generates a random string of specified length
/// </summary>
/// <param name="length">How long a random string to make</param>
/// <returns>A random string</returns>
public string GetRandomString(int length)
{
// Creating object of random class
var rand = new Random();
var str = "";
for (var i = 0; i < length; i++)
{
// Generating a random number.
var randValue = rand.Next(0, 26);
// Generating random character by converting
// the random number into character.
var letter = Convert.ToChar(randValue + 65);
// Appending the letter to string.
str = str + letter;
}
return str;
}
/// <summary>
/// Get the UUID value
/// </summary>
/// <returns>The UUID's value</returns>
public string GetValue()
{
return value;
}
public UUID(int length, List<string> usedIDs)
{
var tempValue = "";
var loop = true;
while (loop)
{
tempValue = GetRandomString(length);
loop = false;
foreach (var e in usedIDs)
{
if (e.Equals(tempValue))
{
loop = true;
}
}
}
value = tempValue;
}
}