Initial
parent
2674eb2a10
commit
840218f119
|
|
@ -2,6 +2,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
@ -14,8 +17,21 @@ namespace Lobbies
|
||||||
TcpLobbbyClient tcpClient = new TcpLobbbyClient();
|
TcpLobbbyClient tcpClient = new TcpLobbbyClient();
|
||||||
private readonly ConcurrentQueue<LobbyClientEvent> events = new ConcurrentQueue<LobbyClientEvent>();
|
private readonly ConcurrentQueue<LobbyClientEvent> events = new ConcurrentQueue<LobbyClientEvent>();
|
||||||
BufferRental bufferRental = new BufferRental(4096);
|
BufferRental bufferRental = new BufferRental(4096);
|
||||||
|
|
||||||
|
AutoResetEvent waitForExternalIp = new AutoResetEvent(false);
|
||||||
|
|
||||||
|
private string? host;
|
||||||
|
private int port;
|
||||||
|
private int connectionId;
|
||||||
|
|
||||||
|
public string? externalIp;
|
||||||
|
public int externalPort;
|
||||||
|
|
||||||
public void Connect(string host, int port, CancellationToken cancellationToken)
|
public void Connect(string host, int port, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
this.host = host;
|
||||||
|
this.port = port;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
@ -30,9 +46,6 @@ namespace Lobbies
|
||||||
tcpClient.Disconnected -= TcpClient_Disconnected;
|
tcpClient.Disconnected -= TcpClient_Disconnected;
|
||||||
tcpClient.Disconnected += TcpClient_Disconnected;
|
tcpClient.Disconnected += TcpClient_Disconnected;
|
||||||
|
|
||||||
tcpClient.Connected -= TcpClient_Connected;
|
|
||||||
tcpClient.Connected += TcpClient_Connected;
|
|
||||||
|
|
||||||
_ = Task.Run(() => tcpClient.Connect(host, port));
|
_ = Task.Run(() => tcpClient.Connect(host, port));
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|
@ -93,19 +106,25 @@ namespace Lobbies
|
||||||
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RequestLobbyNatPunch(Guid lobbyId, string? password, string? clientIp, int clientPort)
|
|
||||||
{
|
|
||||||
var lobbyRequestNatPunch = new LobbyRequestNatPunch()
|
|
||||||
{
|
|
||||||
LobbyId = lobbyId,
|
|
||||||
PasswordHash = string.IsNullOrEmpty(password) ? null : SHA256.HashData(Encoding.UTF8.GetBytes(password)),
|
|
||||||
ClientIp = clientIp,
|
|
||||||
ClientPort = clientPort
|
|
||||||
};
|
|
||||||
|
|
||||||
byte[] messageData = bufferRental.Rent();
|
public delegate void SendUdpMessageCallback(IPEndPoint remoteEndpoint, byte[] messageBuffer, int messageLength);
|
||||||
var len = lobbyRequestNatPunch.Serialize(messageData);
|
public void RequestLobbyNatPunch(Guid lobbyId, string? password, SendUdpMessageCallback sendUdpCallback)
|
||||||
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
QueryExternalIpAndPort(sendUdpCallback);
|
||||||
|
var lobbyRequestNatPunch = new LobbyRequestNatPunch()
|
||||||
|
{
|
||||||
|
LobbyId = lobbyId,
|
||||||
|
PasswordHash = string.IsNullOrEmpty(password) ? null : SHA256.HashData(Encoding.UTF8.GetBytes(password)),
|
||||||
|
ClientIp = externalIp,
|
||||||
|
ClientPort = externalPort
|
||||||
|
};
|
||||||
|
|
||||||
|
byte[] messageData = bufferRental.Rent();
|
||||||
|
var len = lobbyRequestNatPunch.Serialize(messageData);
|
||||||
|
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateLobby(string name, int gameMode, int maxPlayerCount, int playerCount, string? password, string? ip, int port)
|
public void UpdateLobby(string name, int gameMode, int maxPlayerCount, int playerCount, string? password, string? ip, int port)
|
||||||
|
|
@ -156,23 +175,75 @@ namespace Lobbies
|
||||||
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NotifyLobbyNatPunchDone(int natPunchId)
|
public void NotifyLobbyNatPunchDone(int natPunchId, string externalIp, int externalPort)
|
||||||
{
|
{
|
||||||
var lobbyNatPunchDone = new LobbyNatPunchDone() { NatPunchId = natPunchId };
|
var lobbyNatPunchDone = new LobbyNatPunchDone() { NatPunchId = natPunchId, ExternalIp = externalIp, ExternalPort = externalPort };
|
||||||
|
|
||||||
byte[] messageData = bufferRental.Rent();
|
byte[] messageData = bufferRental.Rent();
|
||||||
var len = lobbyNatPunchDone.Serialize(messageData);
|
var len = lobbyNatPunchDone.Serialize(messageData);
|
||||||
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
public static IPAddress[] GetIPsByName(string hostName, bool ip4Wanted, bool ip6Wanted)
|
||||||
{
|
{
|
||||||
tcpClient.Stop();
|
// Check if the hostname is already an IPAddress
|
||||||
|
IPAddress? outIpAddress;
|
||||||
|
if (IPAddress.TryParse(hostName, out outIpAddress) == true)
|
||||||
|
return new IPAddress[] { outIpAddress };
|
||||||
|
//<----------
|
||||||
|
|
||||||
|
IPAddress[] addresslist = Dns.GetHostAddresses(hostName);
|
||||||
|
|
||||||
|
if (addresslist == null || addresslist.Length == 0)
|
||||||
|
return new IPAddress[0];
|
||||||
|
//<----------
|
||||||
|
|
||||||
|
if (ip4Wanted && ip6Wanted)
|
||||||
|
return addresslist;
|
||||||
|
//<----------
|
||||||
|
|
||||||
|
if (ip4Wanted)
|
||||||
|
return addresslist.Where(o => o.AddressFamily == AddressFamily.InterNetwork).ToArray();
|
||||||
|
//<----------
|
||||||
|
|
||||||
|
if (ip6Wanted)
|
||||||
|
return addresslist.Where(o => o.AddressFamily == AddressFamily.InterNetworkV6).ToArray();
|
||||||
|
//<----------
|
||||||
|
|
||||||
|
return new IPAddress[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TcpClient_Connected()
|
public void QueryExternalIpAndPort(SendUdpMessageCallback sendUdpCallback)
|
||||||
{
|
{
|
||||||
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.Connected, EventData = null });
|
byte[] messageData = bufferRental.Rent();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
waitForExternalIp.Reset();
|
||||||
|
var queryExternalPortAndIp = new QueryExternalPortAndIp() { LobbyClientId = connectionId };
|
||||||
|
var len = queryExternalPortAndIp.Serialize(messageData);
|
||||||
|
var ip = GetIPsByName(host!, true, false).First();
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
sendUdpCallback(new IPEndPoint(ip, port), messageData, len);
|
||||||
|
}
|
||||||
|
while (!waitForExternalIp.WaitOne(100));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
bufferRental.Return(messageData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
waitForExternalIp.Set();
|
||||||
|
tcpClient.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TcpClient_Disconnected(bool clean, string error)
|
private void TcpClient_Disconnected(bool clean, string error)
|
||||||
|
|
@ -183,38 +254,24 @@ namespace Lobbies
|
||||||
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.Disconnected, EventData = new LobbyClientDisconnectReason { WasError = false, ErrorMessage = string.Empty } });
|
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.Disconnected, EventData = new LobbyClientDisconnectReason { WasError = false, ErrorMessage = string.Empty } });
|
||||||
}
|
}
|
||||||
|
|
||||||
private int PeekTypeId(ReadOnlySpan<byte> buffer)
|
|
||||||
{
|
|
||||||
int typeId = 0;
|
|
||||||
int shift = 0;
|
|
||||||
int offset = 0;
|
|
||||||
byte b;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
{
|
|
||||||
// Check for a corrupted stream. Read a max of 5 bytes.
|
|
||||||
// In a future version, add a DataFormatException.
|
|
||||||
if (shift == 5 * 7) // 5 bytes max per Int32, shift += 7
|
|
||||||
throw new FormatException("Format_Bad7BitInt32");
|
|
||||||
|
|
||||||
// ReadByte handles end of stream cases for us.
|
|
||||||
b = buffer[offset++];
|
|
||||||
typeId |= (b & 0x7F) << shift;
|
|
||||||
shift += 7;
|
|
||||||
}
|
|
||||||
} while ((b & 0x80) != 0);
|
|
||||||
|
|
||||||
return typeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TcpClient_DataReceived(int dataLength, Memory<byte> data)
|
private void TcpClient_DataReceived(int dataLength, Memory<byte> data)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (dataLength > 0)
|
if (dataLength > 0)
|
||||||
{
|
{
|
||||||
switch (PeekTypeId(data.Span))
|
switch (LobbyMessageIdentifier.ReadLobbyMessageIdentifier(data.Span))
|
||||||
{
|
{
|
||||||
|
case LobbyClientConnectionInfo.TypeId:
|
||||||
|
{
|
||||||
|
var lobbyClientConnectionInfo = LobbyClientConnectionInfo.Deserialize(data.Span);
|
||||||
|
if (lobbyClientConnectionInfo != null)
|
||||||
|
{
|
||||||
|
connectionId = lobbyClientConnectionInfo.Id;
|
||||||
|
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.Connected, EventData = null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case LobbyInfo.TypeId:
|
case LobbyInfo.TypeId:
|
||||||
{
|
{
|
||||||
var lobbyInfo = LobbyInfo.Deserialize(data.Span);
|
var lobbyInfo = LobbyInfo.Deserialize(data.Span);
|
||||||
|
|
@ -260,6 +317,19 @@ namespace Lobbies
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case SeenExternalIpAndPort.TypeId:
|
||||||
|
{
|
||||||
|
var seenExternalIpAndPort = SeenExternalIpAndPort.Deserialize(data.Span);
|
||||||
|
if (seenExternalIpAndPort != null)
|
||||||
|
{
|
||||||
|
externalIp = seenExternalIpAndPort.Ip;
|
||||||
|
externalPort = seenExternalIpAndPort.Port;
|
||||||
|
waitForExternalIp.Set();
|
||||||
|
|
||||||
|
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.ExternalIpAndPort, EventData = seenExternalIpAndPort });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -268,7 +338,8 @@ namespace Lobbies
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
tcpClient.Dispose();
|
waitForExternalIp.Dispose();
|
||||||
|
tcpClient.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,4 @@
|
||||||
using System;
|
namespace Lobbies
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Lobbies
|
|
||||||
{
|
{
|
||||||
public class LobbyClientDisconnectReason
|
public class LobbyClientDisconnectReason
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,64 @@
|
||||||
using System;
|
namespace Lobbies
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Lobbies
|
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains a lobby client event
|
||||||
|
/// </summary>
|
||||||
public class LobbyClientEvent
|
public class LobbyClientEvent
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Identifier type of the event
|
||||||
|
/// </summary>
|
||||||
public LobbyClientEventTypes EventType { get; internal set; }
|
public LobbyClientEventTypes EventType { get; internal set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Optional data object, contains additional information for the event. See <see cref="LobbyClientEventTypes"/> for possible values.
|
||||||
|
/// </summary>
|
||||||
public object? EventData { get; internal set; }
|
public object? EventData { get; internal set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The list of events that can occur.
|
||||||
|
/// </summary>
|
||||||
public enum LobbyClientEventTypes
|
public enum LobbyClientEventTypes
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Client connected to lobby server. EventData is null.
|
||||||
|
/// </summary>
|
||||||
Connected,
|
Connected,
|
||||||
|
/// <summary>
|
||||||
|
/// Client disconnected from lobby server graceful. EventData is <see cref="LobbyClientDisconnectReason"/>.
|
||||||
|
/// </summary>
|
||||||
Disconnected,
|
Disconnected,
|
||||||
|
/// <summary>
|
||||||
|
/// Connection lost to lobby server. EventData is LobbyClientDisconnectReason.
|
||||||
|
/// </summary>
|
||||||
Failed,
|
Failed,
|
||||||
|
/// <summary>
|
||||||
|
/// A lobby was added to the lobby list for the observed game id. EventData is <see cref="LobbyAdd"/> with the lobby data.
|
||||||
|
/// </summary>
|
||||||
LobbyAdd,
|
LobbyAdd,
|
||||||
|
/// <summary>
|
||||||
|
/// A lobby was changed for the observed game id. EventData is <see cref="LobbyUpdate"/> with the lobby data.
|
||||||
|
/// </summary>
|
||||||
LobbyUpdate,
|
LobbyUpdate,
|
||||||
|
/// <summary>
|
||||||
|
/// A lobby was removed from the lobby list for the observed game id. EventData is <see cref="LobbyDelete"/> with the lobby id.
|
||||||
|
/// </summary>
|
||||||
LobbyDelete,
|
LobbyDelete,
|
||||||
|
/// <summary>
|
||||||
|
/// The game host shared his self known address. EventData is <see cref="LobbyHostInfo"/> with the games hosts self known address, most likely internal.
|
||||||
|
/// </summary>
|
||||||
LobbyHostInfo,
|
LobbyHostInfo,
|
||||||
|
/// <summary>
|
||||||
|
/// A nat punch was requested by another LobbyClient to be performed by the host LobbyClient. EventData is <see cref="LobbyRequestNatPunch"/> with the clients seen external address to nat punch to.
|
||||||
|
/// </summary>
|
||||||
LobbyRequestNatPunch,
|
LobbyRequestNatPunch,
|
||||||
|
/// <summary>
|
||||||
|
/// Game host has finished the nat punch. EventData is <see cref="LobbyRequestNatPunch"/> with the games hosts external address.
|
||||||
|
/// </summary>
|
||||||
LobbyNatPunchDone,
|
LobbyNatPunchDone,
|
||||||
|
/// <summary>
|
||||||
|
/// Response to a query of our external ip and port seen by the lobby server. EventData is <see cref="SeenExternalIpAndPort"/> with the clients seen external address.
|
||||||
|
/// </summary>
|
||||||
|
ExternalIpAndPort
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,67 @@
|
||||||
using System;
|
using System.Net.Sockets;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||||
|
|
||||||
namespace LobbyClientTest
|
namespace LobbyClientTest
|
||||||
{
|
{
|
||||||
internal class FakeGameHost
|
internal class FakeGameHost : IDisposable
|
||||||
{
|
{
|
||||||
private Socket _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
public bool isHost = false;
|
||||||
private const int bufSize = 8 * 1024;
|
UdpClient? udpClient;
|
||||||
private State state = new State();
|
bool running = true;
|
||||||
private EndPoint epFrom = new IPEndPoint(IPAddress.Any, 0);
|
|
||||||
private AsyncCallback recv = null;
|
|
||||||
public bool isHost;
|
|
||||||
public class State
|
|
||||||
{
|
|
||||||
public byte[] buffer = new byte[bufSize];
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Server(int port)
|
public int Server(int port)
|
||||||
{
|
{
|
||||||
_socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true);
|
udpClient = new UdpClient(port);
|
||||||
_socket.ExclusiveAddressUse = false;
|
|
||||||
_socket.Bind(new IPEndPoint(IPAddress.Any, port));
|
_ = Task.Run(() =>
|
||||||
Receive();
|
{
|
||||||
return ((IPEndPoint)_socket.LocalEndPoint!).Port;
|
Receive();
|
||||||
|
});
|
||||||
|
|
||||||
|
return ((IPEndPoint)udpClient.Client.LocalEndPoint!).Port;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Send(EndPoint ep, string text)
|
public void Send(IPEndPoint ep, string text)
|
||||||
{
|
{
|
||||||
byte[] data = Encoding.ASCII.GetBytes(text);
|
byte[] data = Encoding.ASCII.GetBytes(text);
|
||||||
_socket.SendTo(data, 0, data.Length, SocketFlags.None, ep);
|
udpClient?.Send(data, data.Length, ep);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(IPEndPoint ep, byte[] message, int length)
|
||||||
|
{
|
||||||
|
udpClient?.Send(message, length, ep);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Receive()
|
private void Receive()
|
||||||
{
|
{
|
||||||
_socket.BeginReceiveFrom(state.buffer, 0, bufSize, SocketFlags.None, ref epFrom, recv = (ar) =>
|
try
|
||||||
{
|
{
|
||||||
State so = (State)ar.AsyncState;
|
IPEndPoint remoteEp = new IPEndPoint(IPAddress.Any, 0);
|
||||||
int bytes = _socket.EndReceiveFrom(ar, ref epFrom);
|
|
||||||
_socket.BeginReceiveFrom(so.buffer, 0, bufSize, SocketFlags.None, ref epFrom, recv, so);
|
while (running)
|
||||||
Console.WriteLine($"Game {(isHost ? "host" : "client")} received: {epFrom.ToString()}: {bytes}, {Encoding.ASCII.GetString(so.buffer, 0, bytes)}");
|
{
|
||||||
if (isHost)
|
var data = udpClient!.Receive(ref remoteEp);
|
||||||
Send(epFrom, "Hello from Game Server!");
|
Console.WriteLine($"Game {(isHost ? "host" : "client")} received: {remoteEp.ToString()}: {data.Length}, {Encoding.ASCII.GetString(data, 0, data.Length)}");
|
||||||
}, state);
|
if (isHost)
|
||||||
|
Send(remoteEp, "Hello from Game Server!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
running = false;
|
||||||
|
udpClient?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,22 @@ using LobbyClientTest;
|
||||||
using LobbyServerDto;
|
using LobbyServerDto;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
Console.WriteLine("Starting lobby client!");
|
Console.WriteLine("Starting lobby client v0.7!");
|
||||||
var lobbyClient = new LobbyClient();
|
var lobbyClient = new LobbyClient();
|
||||||
var cancellationTokenSource = new CancellationTokenSource();
|
var cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
List<LobbyInfo> openLobbies = new List<LobbyInfo>();
|
List<LobbyInfo> openLobbies = new List<LobbyInfo>();
|
||||||
|
|
||||||
lobbyClient.Connect("localhost", 8088, cancellationTokenSource.Token);
|
lobbyClient.Connect("lobby.incobyte.de" /*"localhost"*/, 8088, cancellationTokenSource.Token);
|
||||||
|
|
||||||
FakeGameHost fakeGameHost = new FakeGameHost();
|
FakeGameHost fakeGameHost = new FakeGameHost();
|
||||||
int myPort = fakeGameHost.Server(0);
|
int myPort = fakeGameHost.Server(0);
|
||||||
|
string? myExternalIp = null;
|
||||||
|
int myExternalPort = -1;
|
||||||
IPEndPoint? hostInfo = null;
|
IPEndPoint? hostInfo = null;
|
||||||
|
|
||||||
bool running = true;
|
bool running = true;
|
||||||
|
bool connected = false;
|
||||||
|
|
||||||
_ = Task.Run(() =>
|
_ = Task.Run(() =>
|
||||||
{
|
{
|
||||||
|
|
@ -28,6 +31,7 @@ _ = Task.Run(() =>
|
||||||
{
|
{
|
||||||
case LobbyClientEventTypes.Connected:
|
case LobbyClientEventTypes.Connected:
|
||||||
{
|
{
|
||||||
|
connected = true;
|
||||||
var p = Console.GetCursorPosition();
|
var p = Console.GetCursorPosition();
|
||||||
Console.SetCursorPosition(0, p.Top);
|
Console.SetCursorPosition(0, p.Top);
|
||||||
Console.WriteLine("Lobby client connected!");
|
Console.WriteLine("Lobby client connected!");
|
||||||
|
|
@ -76,24 +80,56 @@ _ = Task.Run(() =>
|
||||||
Console.SetCursorPosition(0, p.Top);
|
Console.SetCursorPosition(0, p.Top);
|
||||||
Console.WriteLine($"Host info for lobby {lobbyHostInfo!.LobbyId} is {lobbyHostInfo.HostIp}:{lobbyHostInfo.HostPort}!");
|
Console.WriteLine($"Host info for lobby {lobbyHostInfo!.LobbyId} is {lobbyHostInfo.HostIp}:{lobbyHostInfo.HostPort}!");
|
||||||
hostInfo = new IPEndPoint(IPAddress.Parse(lobbyHostInfo.HostIp!), lobbyHostInfo.HostPort);
|
hostInfo = new IPEndPoint(IPAddress.Parse(lobbyHostInfo.HostIp!), lobbyHostInfo.HostPort);
|
||||||
Console.WriteLine($"Requesting nat punch!");
|
Console.WriteLine($"Requesting nat punch to me!");
|
||||||
lobbyClient.RequestLobbyNatPunch(lobbyHostInfo.LobbyId, null, null, myPort);
|
lobbyClient.RequestLobbyNatPunch(lobbyHostInfo.LobbyId, null, (remoteEndpoint, messageBuffer, messageLength) => {
|
||||||
|
fakeGameHost.Send(remoteEndpoint, messageBuffer, messageLength);
|
||||||
|
});
|
||||||
Console.Write(">");
|
Console.Write(">");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case LobbyClientEventTypes.ExternalIpAndPort:
|
||||||
|
{
|
||||||
|
var seenExternalIpAndPort = lobbyEvent.EventData as SeenExternalIpAndPort;
|
||||||
|
if (seenExternalIpAndPort != null)
|
||||||
|
{
|
||||||
|
myExternalIp = seenExternalIpAndPort.Ip;
|
||||||
|
myExternalPort = seenExternalIpAndPort.Port;
|
||||||
|
|
||||||
|
var p = Console.GetCursorPosition();
|
||||||
|
Console.SetCursorPosition(0, p.Top);
|
||||||
|
Console.WriteLine($"Received my external ip {seenExternalIpAndPort!.Ip}:{seenExternalIpAndPort.Port}");
|
||||||
|
Console.Write(">");
|
||||||
|
|
||||||
|
connected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case LobbyClientEventTypes.LobbyRequestNatPunch:
|
case LobbyClientEventTypes.LobbyRequestNatPunch:
|
||||||
{
|
{
|
||||||
var lobbyRequestNatPunch = lobbyEvent.EventData as LobbyRequestNatPunch;
|
var lobbyRequestNatPunch = lobbyEvent.EventData as LobbyRequestNatPunch;
|
||||||
var p = Console.GetCursorPosition();
|
var p = Console.GetCursorPosition();
|
||||||
Console.SetCursorPosition(0, p.Top);
|
Console.SetCursorPosition(0, p.Top);
|
||||||
Console.WriteLine($"Nat punch requested to {lobbyRequestNatPunch!.ClientIp}:{lobbyRequestNatPunch.ClientPort}!");
|
Console.WriteLine($"Nat punch requested to {lobbyRequestNatPunch!.ClientIp}:{lobbyRequestNatPunch.ClientPort}!");
|
||||||
//NatPuncher.NatPunch(new IPEndPoint(IPAddress.Any, myPort), new IPEndPoint(IPAddress.Parse(lobbyRequestNatPunch.ClientIp!), lobbyRequestNatPunch.ClientPort));
|
|
||||||
var ep = new IPEndPoint(IPAddress.Parse(lobbyRequestNatPunch.ClientIp!), lobbyRequestNatPunch.ClientPort);
|
Task.Run(() =>
|
||||||
for (int z = 0; z < 16; z++)
|
|
||||||
{
|
{
|
||||||
fakeGameHost.Send(ep, "Nat Falcon Punch!");
|
lobbyClient.QueryExternalIpAndPort((remoteEndpoint, messageData, messageLength) => {
|
||||||
}
|
fakeGameHost.Send(remoteEndpoint, messageData, messageLength);
|
||||||
lobbyClient.NotifyLobbyNatPunchDone(lobbyRequestNatPunch.NatPunchId);
|
});
|
||||||
|
|
||||||
|
var ep = new IPEndPoint(IPAddress.Parse(lobbyRequestNatPunch.ClientIp!), lobbyRequestNatPunch.ClientPort);
|
||||||
|
for (int z = 0; z < 32; z++)
|
||||||
|
{
|
||||||
|
fakeGameHost.Send(ep, "Nat Falcon Punch!");
|
||||||
|
}
|
||||||
|
|
||||||
|
lobbyClient.NotifyLobbyNatPunchDone(lobbyRequestNatPunch.NatPunchId, lobbyClient.externalIp!, lobbyClient.externalPort);
|
||||||
|
var p = Console.GetCursorPosition();
|
||||||
|
Console.SetCursorPosition(0, p.Top);
|
||||||
|
Console.WriteLine($"Nat punch done!");
|
||||||
|
Console.Write(">");
|
||||||
|
});
|
||||||
|
|
||||||
Console.Write(">");
|
Console.Write(">");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -104,7 +140,8 @@ _ = Task.Run(() =>
|
||||||
Console.SetCursorPosition(0, p.Top);
|
Console.SetCursorPosition(0, p.Top);
|
||||||
Console.WriteLine($"Nat punch request done!");
|
Console.WriteLine($"Nat punch request done!");
|
||||||
Console.WriteLine($"Connecting game client!");
|
Console.WriteLine($"Connecting game client!");
|
||||||
fakeGameHost.Send(hostInfo!, "Hello from Game Client!");
|
|
||||||
|
fakeGameHost.Send(new IPEndPoint(IPAddress.Parse(lobbyNatPunchDone!.ExternalIp!), lobbyNatPunchDone.ExternalPort), "Hello from Game Client!");
|
||||||
Console.Write(">");
|
Console.Write(">");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -133,9 +170,16 @@ while (running)
|
||||||
{
|
{
|
||||||
case "host":
|
case "host":
|
||||||
{
|
{
|
||||||
Console.WriteLine("Hosting game ...");
|
if (!connected)
|
||||||
lobbyClient.HostLobby(GameGuids.NFS, "Hallo, Welt!", 1, 8, null, null, myPort);
|
{
|
||||||
fakeGameHost.isHost = true;
|
Console.WriteLine("Not connected yet!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("Hosting game ...");
|
||||||
|
lobbyClient.HostLobby(GameGuids.NFS, "Hallo, Welt!", 1, 8, null, "127.0.0.1", myPort);
|
||||||
|
fakeGameHost.isHost = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "host stop":
|
case "host stop":
|
||||||
|
|
@ -147,15 +191,22 @@ while (running)
|
||||||
break;
|
break;
|
||||||
case "join":
|
case "join":
|
||||||
{
|
{
|
||||||
Console.WriteLine("Trying to join first lobby ...");
|
if (!connected)
|
||||||
var firstLobby = openLobbies.FirstOrDefault();
|
|
||||||
if (firstLobby != null)
|
|
||||||
{
|
{
|
||||||
lobbyClient.RequestLobbyHostInfo(firstLobby.Id, null);
|
Console.WriteLine("Not connected yet!");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Console.WriteLine("Seeing no open lobby!");
|
Console.WriteLine("Trying to join first lobby ...");
|
||||||
|
var firstLobby = openLobbies.FirstOrDefault();
|
||||||
|
if (firstLobby != null)
|
||||||
|
{
|
||||||
|
lobbyClient.RequestLobbyHostInfo(firstLobby.Id, null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("Seeing no open lobby!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System.Collections.Concurrent;
|
||||||
|
|
||||||
using var closing = new AutoResetEvent(false);
|
using var closing = new AutoResetEvent(false);
|
||||||
using var tcpServer = new TcpServer();
|
using var tcpServer = new TcpServer();
|
||||||
|
using var udpServer = new UdpCandidateServer();
|
||||||
|
|
||||||
ConcurrentDictionary<Guid, Lobby> lobbiesById = new ConcurrentDictionary<Guid, Lobby>();
|
ConcurrentDictionary<Guid, Lobby> lobbiesById = new ConcurrentDictionary<Guid, Lobby>();
|
||||||
ConcurrentDictionary<Guid, List<Lobby>> lobbiesByGameId = new ConcurrentDictionary<Guid, List<Lobby>>();
|
ConcurrentDictionary<Guid, List<Lobby>> lobbiesByGameId = new ConcurrentDictionary<Guid, List<Lobby>>();
|
||||||
|
|
@ -14,35 +15,23 @@ ConcurrentDictionary<int, Guid> clientWatchingGameIdLobbies = new ConcurrentDict
|
||||||
ConcurrentDictionary<Guid, List<int>> clientsWatchingGameId = new ConcurrentDictionary<Guid, List<int>>();
|
ConcurrentDictionary<Guid, List<int>> clientsWatchingGameId = new ConcurrentDictionary<Guid, List<int>>();
|
||||||
BufferRental bufferRental = new BufferRental(4096);
|
BufferRental bufferRental = new BufferRental(4096);
|
||||||
|
|
||||||
int PeekTypeId(ReadOnlySpan<byte> buffer)
|
udpServer.QueryIpAndPort += (clientId, ip, port) =>
|
||||||
{
|
{
|
||||||
int typeId = 0;
|
var messageData = bufferRental.Rent();
|
||||||
int shift = 0;
|
var seenExternalIpAndPort = new SeenExternalIpAndPort() { Ip = ip, Port = port };
|
||||||
int offset = 0;
|
var messageDataLength = seenExternalIpAndPort.Serialize(messageData);
|
||||||
byte b;
|
_ = Task.Run(async () =>
|
||||||
do
|
|
||||||
{
|
{
|
||||||
{
|
await tcpServer.Send(clientId, messageData, 0, messageDataLength);
|
||||||
// Check for a corrupted stream. Read a max of 5 bytes.
|
bufferRental.Return(messageData);
|
||||||
// In a future version, add a DataFormatException.
|
});
|
||||||
if (shift == 5 * 7) // 5 bytes max per Int32, shift += 7
|
};
|
||||||
throw new FormatException("Format_Bad7BitInt32");
|
|
||||||
|
|
||||||
// ReadByte handles end of stream cases for us.
|
|
||||||
b = buffer[offset++];
|
|
||||||
typeId |= (b & 0x7F) << shift;
|
|
||||||
shift += 7;
|
|
||||||
}
|
|
||||||
} while ((b & 0x80) != 0);
|
|
||||||
|
|
||||||
return typeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
tcpServer.DataReceived += (clientId, dataLength, data) =>
|
tcpServer.DataReceived += (clientId, dataLength, data) =>
|
||||||
{
|
{
|
||||||
if (dataLength > 0)
|
if (dataLength > 0)
|
||||||
{
|
{
|
||||||
switch (PeekTypeId(data.Span))
|
switch (LobbyMessageIdentifier.ReadLobbyMessageIdentifier(data.Span))
|
||||||
{
|
{
|
||||||
case LobbyCreate.TypeId:
|
case LobbyCreate.TypeId:
|
||||||
{
|
{
|
||||||
|
|
@ -433,8 +422,9 @@ Console.CancelKeyPress += (sender, args) =>
|
||||||
args.Cancel = true;
|
args.Cancel = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
Console.WriteLine($"{DateTime.Now}: Application started");
|
Console.WriteLine($"{DateTime.Now}: Application started v0.7");
|
||||||
|
|
||||||
|
udpServer.Start(8088);
|
||||||
tcpServer.Start(8088);
|
tcpServer.Start(8088);
|
||||||
|
|
||||||
closing.WaitOne();
|
closing.WaitOne();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using LobbyServerDto;
|
||||||
|
|
||||||
namespace LobbyServer
|
namespace LobbyServer
|
||||||
{
|
{
|
||||||
|
|
@ -131,6 +131,11 @@ namespace LobbyServer
|
||||||
bool offsetSizeInt = false;
|
bool offsetSizeInt = false;
|
||||||
int currentReadOffset = 0;
|
int currentReadOffset = 0;
|
||||||
|
|
||||||
|
var lobbyClientConnectionInfo = new LobbyClientConnectionInfo { Id = myId };
|
||||||
|
byte[] sendBuffer = new byte[128];
|
||||||
|
int sendLen = lobbyClientConnectionInfo.Serialize(sendBuffer);
|
||||||
|
await Send(myId, sendBuffer, 0, sendLen);
|
||||||
|
|
||||||
while (running)
|
while (running)
|
||||||
{
|
{
|
||||||
int copyOffset = 0;
|
int copyOffset = 0;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Net;
|
||||||
|
using LobbyServerDto;
|
||||||
|
|
||||||
|
namespace LobbyServer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Small udp server to receive udp packets and report their remote ip and port to a event delegate
|
||||||
|
/// </summary>
|
||||||
|
internal class UdpCandidateServer : IDisposable
|
||||||
|
{
|
||||||
|
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
private bool running = false;
|
||||||
|
private bool isDisposed = false;
|
||||||
|
|
||||||
|
public delegate void QueryIpAndPortReceivedEventArgs(int clientId, string ip, int port);
|
||||||
|
/// <summary>
|
||||||
|
/// If a valid request for a ip and port query comes in call this event with the seen remote ip and port for a lobby server client id
|
||||||
|
/// </summary>
|
||||||
|
public event QueryIpAndPortReceivedEventArgs? QueryIpAndPort;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Listen to requests and fire events
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="port">The port to listen on</param>
|
||||||
|
private async void Listen(int port)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cancellationTokenSource.Token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
using var serverSocket = new UdpClient(port, AddressFamily.InterNetwork);
|
||||||
|
Console.WriteLine($"{DateTime.Now}: [UdpCandidateServer] started");
|
||||||
|
while (running)
|
||||||
|
{
|
||||||
|
var receiveResult = await serverSocket.ReceiveAsync(cancellationTokenSource.Token);
|
||||||
|
if(receiveResult.Buffer.Length > 0)
|
||||||
|
{
|
||||||
|
Memory<byte> receivedData = new Memory<byte>(receiveResult.Buffer);
|
||||||
|
switch (LobbyMessageIdentifier.ReadLobbyMessageIdentifier(receivedData.Span))
|
||||||
|
{
|
||||||
|
case QueryExternalPortAndIp.TypeId:
|
||||||
|
var queryExternalPortAndIp = QueryExternalPortAndIp.Deserialize(receivedData.Span);
|
||||||
|
QueryIpAndPort?.Invoke(queryExternalPortAndIp.LobbyClientId, receiveResult.RemoteEndPoint.Address.ToString(), receiveResult.RemoteEndPoint.Port);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch when (cancellationTokenSource.IsCancellationRequested) //Cancel requested
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{DateTime.Now}: [UdpCandidateServer] canceled");
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{DateTime.Now}: [UdpCandidateServer] exception: {e}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{DateTime.Now}: [UdpCandidateServer] stopped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start udp listener
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="port">The port to listen on</param>
|
||||||
|
public void Start(int port)
|
||||||
|
{
|
||||||
|
running = true;
|
||||||
|
_ = Task.Run(() => Listen(port));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stop udp listener
|
||||||
|
/// </summary>
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
running = false;
|
||||||
|
cancellationTokenSource.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!isDisposed)
|
||||||
|
{
|
||||||
|
cancellationTokenSource.Dispose();
|
||||||
|
isDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
namespace LobbyServerDto
|
namespace LobbyServerDto
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to subscribe to a game id by guid to observe it's list of open lobbies
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>A client can only be subscribed to one game id, sending multiple observe requests with different game ids will overwrite the previous</remarks>
|
||||||
[LobbyMessage]
|
[LobbyMessage]
|
||||||
public partial class LobbiesObserve
|
public partial class LobbiesObserve
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The game id to subscribe to
|
||||||
|
/// </summary>
|
||||||
public Guid GameId { get; set; }
|
public Guid GameId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
namespace LobbyServerDto
|
namespace LobbyServerDto
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Stop observing and receiving updates on the lobby list
|
||||||
|
/// </summary>
|
||||||
[LobbyMessage]
|
[LobbyMessage]
|
||||||
public partial class LobbiesStopObserve
|
public partial class LobbiesStopObserve
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
namespace LobbyServerDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Send to lobby client after connection is established
|
||||||
|
/// </summary>
|
||||||
|
[LobbyMessage]
|
||||||
|
public partial class LobbyClientConnectionInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Connection id on the lobby server
|
||||||
|
/// </summary>
|
||||||
|
public int Id { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,19 +2,47 @@
|
||||||
|
|
||||||
namespace LobbyServerDto
|
namespace LobbyServerDto
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create a lobby in the lobby list. The lobby will stay until either closed by the host or the host disconnects
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>A host can always have only one open lobby, if another lobby is already opened by the client it will be closed</remarks>
|
||||||
[LobbyMessage]
|
[LobbyMessage]
|
||||||
public partial class LobbyCreate
|
public partial class LobbyCreate
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Game Id for which game this lobby is for
|
||||||
|
/// </summary>
|
||||||
public Guid GameId { get; set; }
|
public Guid GameId { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Display name of the lobby
|
||||||
|
/// </summary>
|
||||||
[MaxLength(64)]
|
[MaxLength(64)]
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// Game mode of lobby
|
||||||
|
/// </summary>
|
||||||
public int GameMode { get; set; }
|
public int GameMode { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// How many players are in the lobby
|
||||||
|
/// </summary>
|
||||||
public int PlayerCount { get; set; }
|
public int PlayerCount { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum players allowed in the lobby
|
||||||
|
/// </summary>
|
||||||
public int MaxPlayerCount { get; set; }
|
public int MaxPlayerCount { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The hash of a password to protect the lobby. Only users with the password can request host information/nat punch.
|
||||||
|
/// </summary>
|
||||||
[MaxLength(26)]
|
[MaxLength(26)]
|
||||||
public byte[]? PasswordHash { get; set; }
|
public byte[]? PasswordHash { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The hosts ip. Used the the host information send to clients on their request.
|
||||||
|
/// </summary>
|
||||||
[MaxLength(32)]
|
[MaxLength(32)]
|
||||||
public string? HostIp { get; set; }
|
public string? HostIp { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The hosts port. Used the the host information send to clients on their request.
|
||||||
|
/// </summary>
|
||||||
public int HostPort { get; set; }
|
public int HostPort { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,19 @@
|
||||||
namespace LobbyServerDto
|
namespace LobbyServerDto
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// If send from host closes and deletes a lobby.
|
||||||
|
/// If received by client, the lobby with the Id parameter has been closed
|
||||||
|
/// </summary>
|
||||||
[LobbyMessage]
|
[LobbyMessage]
|
||||||
public partial class LobbyDelete
|
public partial class LobbyDelete
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// If received by client, this contains the lobby id. Does not need to be set if the host sends this request.
|
||||||
|
/// </summary>
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// If received by client, this contains the game id. Does not need to be set if the host sends this request.
|
||||||
|
/// </summary>
|
||||||
public Guid GameId { get; set; }
|
public Guid GameId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,12 +2,24 @@
|
||||||
|
|
||||||
namespace LobbyServerDto
|
namespace LobbyServerDto
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Send to client upon request, this contains the connection info supplied by the host.
|
||||||
|
/// </summary>
|
||||||
[LobbyMessage]
|
[LobbyMessage]
|
||||||
public partial class LobbyHostInfo
|
public partial class LobbyHostInfo
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Lobby id this information is for
|
||||||
|
/// </summary>
|
||||||
public Guid LobbyId { get; set; }
|
public Guid LobbyId { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The hosts ip, this could be an internal address
|
||||||
|
/// </summary>
|
||||||
[MaxLength(32)]
|
[MaxLength(32)]
|
||||||
public string? HostIp { get; set; }
|
public string? HostIp { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The hosts port
|
||||||
|
/// </summary>
|
||||||
public int HostPort { get; set; }
|
public int HostPort { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,34 @@
|
||||||
namespace LobbyServerDto
|
namespace LobbyServerDto
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Send when a lobby is created or updates, this contains the lobby information
|
||||||
|
/// </summary>
|
||||||
[LobbyMessage]
|
[LobbyMessage]
|
||||||
public partial class LobbyInfo
|
public partial class LobbyInfo
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Id of the lobby
|
||||||
|
/// </summary>
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Displayname of the lobby
|
||||||
|
/// </summary>
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Game mode of the lobby
|
||||||
|
/// </summary>
|
||||||
public int GameMode { get; set; }
|
public int GameMode { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Count of currently joined players in the lobby
|
||||||
|
/// </summary>
|
||||||
public int PlayerCount { get; set; }
|
public int PlayerCount { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Max players allowed in the lobby
|
||||||
|
/// </summary>
|
||||||
public int MaxPlayerCount { get; set; }
|
public int MaxPlayerCount { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// True if the lobby requires a password to gain access
|
||||||
|
/// </summary>
|
||||||
public bool PasswordProtected { get; set; }
|
public bool PasswordProtected { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
namespace LobbyServerDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to read the message identifer from a received object
|
||||||
|
/// </summary>
|
||||||
|
public static class LobbyMessageIdentifier
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Read the message identifiere from the stream
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer"></param>
|
||||||
|
/// <returns>The message identifier or -1 if invalid</returns>
|
||||||
|
public static int ReadLobbyMessageIdentifier(ReadOnlySpan<byte> buffer)
|
||||||
|
{
|
||||||
|
if(buffer.Length == 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int typeId = 0;
|
||||||
|
int shift = 0;
|
||||||
|
int offset = 0;
|
||||||
|
byte currentByte;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (offset < buffer.Length)
|
||||||
|
{
|
||||||
|
// Check for a corrupted stream. Read a max of 5 bytes.
|
||||||
|
// In a future version, add a DataFormatException.
|
||||||
|
if (shift == 5 * 7) // 5 bytes max per Int32, shift += 7
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// ReadByte handles end of stream cases for us.
|
||||||
|
currentByte = buffer[offset++];
|
||||||
|
typeId |= (currentByte & 0x7F) << shift;
|
||||||
|
shift += 7;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
} while ((currentByte & 0x80) != 0);
|
||||||
|
|
||||||
|
return typeId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,10 +2,29 @@
|
||||||
|
|
||||||
namespace LobbyServerDto
|
namespace LobbyServerDto
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Send from host to lobby server and releyed to the client by lobby server if the nat punch is finished.
|
||||||
|
/// </summary>
|
||||||
[LobbyMessage]
|
[LobbyMessage]
|
||||||
public partial class LobbyNatPunchDone
|
public partial class LobbyNatPunchDone
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The lobby this nat punch was for
|
||||||
|
/// </summary>
|
||||||
public Guid LobbyId { get; set; }
|
public Guid LobbyId { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Id of the nat punch that the lobby server requested from us in <see cref="LobbyRequestNatPunch"/>
|
||||||
|
/// </summary>
|
||||||
public int NatPunchId { get; set; }
|
public int NatPunchId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The external ip of the host
|
||||||
|
/// </summary>
|
||||||
|
[MaxLength(32)]
|
||||||
|
public string? ExternalIp { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The external port of the host
|
||||||
|
/// </summary>
|
||||||
|
public int ExternalPort { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,10 +2,19 @@
|
||||||
|
|
||||||
namespace LobbyServerDto
|
namespace LobbyServerDto
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Request the host address for a lobby
|
||||||
|
/// </summary>
|
||||||
[LobbyMessage]
|
[LobbyMessage]
|
||||||
public partial class LobbyRequestHostInfo
|
public partial class LobbyRequestHostInfo
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Id of the lobby
|
||||||
|
/// </summary>
|
||||||
public Guid LobbyId { get; set; }
|
public Guid LobbyId { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Hash of the password if the lobby requires one
|
||||||
|
/// </summary>
|
||||||
[MaxLength(26)]
|
[MaxLength(26)]
|
||||||
public byte[]? PasswordHash { get; set; }
|
public byte[]? PasswordHash { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,33 @@
|
||||||
|
|
||||||
namespace LobbyServerDto
|
namespace LobbyServerDto
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Request a nat punch from the game host
|
||||||
|
/// </summary>
|
||||||
[LobbyMessage]
|
[LobbyMessage]
|
||||||
public partial class LobbyRequestNatPunch
|
public partial class LobbyRequestNatPunch
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Id of the lobby
|
||||||
|
/// </summary>
|
||||||
public Guid LobbyId { get; set; }
|
public Guid LobbyId { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Password hash if the lobby is password protected
|
||||||
|
/// </summary>
|
||||||
[MaxLength(26)]
|
[MaxLength(26)]
|
||||||
public byte[]? PasswordHash { get; set; }
|
public byte[]? PasswordHash { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Our external ip
|
||||||
|
/// </summary>
|
||||||
|
|
||||||
public string? ClientIp { get; set; }
|
public string? ClientIp { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Our external port
|
||||||
|
/// </summary>
|
||||||
public int ClientPort { get; set; }
|
public int ClientPort { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Set by the lobby server, this will be the NatPunchId send to the host
|
||||||
|
/// </summary>
|
||||||
public int NatPunchId { get; set; }
|
public int NatPunchId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,16 +2,40 @@
|
||||||
|
|
||||||
namespace LobbyServerDto
|
namespace LobbyServerDto
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// If the host updates values of the lobby, it can send this request to update the lobby information on the lobby server
|
||||||
|
/// </summary>
|
||||||
[LobbyMessage]
|
[LobbyMessage]
|
||||||
public partial class LobbyUpdate
|
public partial class LobbyUpdate
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The displayname of the lobby
|
||||||
|
/// </summary>
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
/// <summary>
|
||||||
|
/// The game mode of the lobby
|
||||||
|
/// </summary>
|
||||||
public int GameMode { get; set; }
|
public int GameMode { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Current count of players in the lobby
|
||||||
|
/// </summary>
|
||||||
public int PlayerCount { get; set; }
|
public int PlayerCount { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Max players in the lobby
|
||||||
|
/// </summary>
|
||||||
public int MaxPlayerCount { get; set; }
|
public int MaxPlayerCount { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The hash of the password required to enter this lobby
|
||||||
|
/// </summary>
|
||||||
public byte[]? PasswordHash { get; set; }
|
public byte[]? PasswordHash { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The hosts ip
|
||||||
|
/// </summary>
|
||||||
[MaxLength(32)]
|
[MaxLength(32)]
|
||||||
public string? HostIp { get; set; }
|
public string? HostIp { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The hosts port
|
||||||
|
/// </summary>
|
||||||
public int HostPort { get; set; }
|
public int HostPort { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,14 @@
|
||||||
namespace LobbyServerDto
|
namespace LobbyServerDto
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Send if a client requests information about a password protected lobby with an invalid password hash
|
||||||
|
/// </summary>
|
||||||
[LobbyMessage]
|
[LobbyMessage]
|
||||||
public partial class LobbyWrongPassword
|
public partial class LobbyWrongPassword
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Id of the lobby
|
||||||
|
/// </summary>
|
||||||
public Guid LobbyId { get; set; }
|
public Guid LobbyId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
using System.Net;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
|
|
||||||
|
|
||||||
namespace LobbyServerDto
|
|
||||||
{
|
|
||||||
public class NatPuncher
|
|
||||||
{
|
|
||||||
public static void NatPunch(IPEndPoint localEndpoint, IPEndPoint remoteEndpoint)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using Socket socket = new Socket(SocketType.Dgram, ProtocolType.Udp);
|
|
||||||
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true);
|
|
||||||
socket.ExclusiveAddressUse = false;
|
|
||||||
socket.Bind(localEndpoint);
|
|
||||||
|
|
||||||
for (int i = 0; i < 16; i++)
|
|
||||||
{
|
|
||||||
socket.SendTo(new byte[] { }, remoteEndpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
namespace LobbyServerDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Send from a client to the lobby server via udp to from the game port to retrieve the observerd external ip and port
|
||||||
|
/// </summary>
|
||||||
|
[LobbyMessage]
|
||||||
|
public partial class QueryExternalPortAndIp
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Lobby client id
|
||||||
|
/// </summary>
|
||||||
|
public int LobbyClientId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace LobbyServerDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The ip and port seen by the lobby udp server by an <see cref="QueryExternalPortAndIp"/> request.
|
||||||
|
/// </summary>
|
||||||
|
[LobbyMessage]
|
||||||
|
public partial class SeenExternalIpAndPort
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The seen ip of the client
|
||||||
|
/// </summary>
|
||||||
|
[MaxLength(32)]
|
||||||
|
public string? Ip { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The seen port of the client
|
||||||
|
/// </summary>
|
||||||
|
public int Port { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
|
|
||||||
namespace LobbyServerDto
|
namespace LobbyServerDto
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// List of accepted game ips
|
||||||
|
/// </summary>
|
||||||
public static class GameGuids
|
public static class GameGuids
|
||||||
{
|
{
|
||||||
public static Guid NFS = new Guid("0572706f-0fd9-46a9-8ea6-5a31a5363442");
|
public static Guid NFS = new Guid("0572706f-0fd9-46a9-8ea6-5a31a5363442");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue