276 lines
11 KiB
C#
276 lines
11 KiB
C#
using LobbyServerDto;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Lobbies
|
|
{
|
|
public class LobbyClient : IDisposable
|
|
{
|
|
TcpLobbbyClient tcpClient = new TcpLobbbyClient();
|
|
private readonly ConcurrentQueue<LobbyClientEvent> events = new ConcurrentQueue<LobbyClientEvent>();
|
|
BufferRental bufferRental = new BufferRental(4096);
|
|
public void Connect(string host, int port, CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
using var cts = cancellationToken.Register(() =>
|
|
{
|
|
tcpClient.Stop();
|
|
});
|
|
|
|
tcpClient.DataReceived -= TcpClient_DataReceived;
|
|
tcpClient.DataReceived += TcpClient_DataReceived;
|
|
|
|
tcpClient.Disconnected -= TcpClient_Disconnected;
|
|
tcpClient.Disconnected += TcpClient_Disconnected;
|
|
|
|
tcpClient.Connected -= TcpClient_Connected;
|
|
tcpClient.Connected += TcpClient_Connected;
|
|
|
|
_ = Task.Run(() => tcpClient.Connect(host, port));
|
|
}
|
|
catch
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
public IEnumerable<LobbyClientEvent> ReadEvents(int maxEvents)
|
|
{
|
|
if (events.Count > 0)
|
|
{
|
|
maxEvents = Math.Min(maxEvents, events.Count);
|
|
|
|
while (maxEvents > 0)
|
|
{
|
|
if(events.TryDequeue(out var _event))
|
|
{
|
|
yield return _event;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void HostLobby(Guid gameId, string name, int gameMode, int maxPlayerCount,string? password, string? ip, int port)
|
|
{
|
|
var lobbyCreate = new LobbyCreate()
|
|
{
|
|
GameId = gameId,
|
|
Name = name,
|
|
GameMode = gameMode,
|
|
MaxPlayerCount = maxPlayerCount,
|
|
PlayerCount = 0,
|
|
PasswordHash = string.IsNullOrEmpty(password) ? null : SHA256.HashData(Encoding.UTF8.GetBytes(password)),
|
|
HostIp = ip,
|
|
HostPort = port
|
|
};
|
|
|
|
byte[] messageData = bufferRental.Rent();
|
|
var len = lobbyCreate.Serialize(messageData);
|
|
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
|
}
|
|
|
|
public void RequestLobbyHostInfo(Guid lobbyId, string? password)
|
|
{
|
|
var lobbyCreate = new LobbyRequestHostInfo()
|
|
{
|
|
LobbyId = lobbyId,
|
|
PasswordHash = string.IsNullOrEmpty(password) ? null : SHA256.HashData(Encoding.UTF8.GetBytes(password)),
|
|
};
|
|
|
|
byte[] messageData = bufferRental.Rent();
|
|
var len = lobbyCreate.Serialize(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();
|
|
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)
|
|
{
|
|
var lobbyUpdate = new LobbyUpdate()
|
|
{
|
|
Name = name,
|
|
GameMode = gameMode,
|
|
MaxPlayerCount = maxPlayerCount,
|
|
PlayerCount = playerCount,
|
|
PasswordHash = string.IsNullOrEmpty(password) ? null : SHA256.HashData(Encoding.UTF8.GetBytes(password)),
|
|
HostIp = ip,
|
|
HostPort = port
|
|
};
|
|
|
|
byte[] messageData = bufferRental.Rent();
|
|
var len = lobbyUpdate.Serialize(messageData);
|
|
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
|
}
|
|
|
|
public void CloseLobby()
|
|
{
|
|
var lobbyDelete = new LobbyDelete()
|
|
{
|
|
|
|
};
|
|
|
|
byte[] messageData = bufferRental.Rent();
|
|
var len = lobbyDelete.Serialize(messageData);
|
|
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
|
}
|
|
|
|
public void ObserveLobbies(Guid gameId)
|
|
{
|
|
var lobbiesObserve = new LobbiesObserve() { GameId = gameId };
|
|
|
|
byte[] messageData = bufferRental.Rent();
|
|
var len = lobbiesObserve.Serialize(messageData);
|
|
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
|
}
|
|
|
|
public void StopObservingLobbies()
|
|
{
|
|
var lobbiesStopObserve = new LobbiesStopObserve() { };
|
|
|
|
byte[] messageData = bufferRental.Rent();
|
|
var len = lobbiesStopObserve.Serialize(messageData);
|
|
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
|
}
|
|
|
|
public void NotifyLobbyNatPunchDone(int natPunchId)
|
|
{
|
|
var lobbyNatPunchDone = new LobbyNatPunchDone() { NatPunchId = natPunchId };
|
|
|
|
byte[] messageData = bufferRental.Rent();
|
|
var len = lobbyNatPunchDone.Serialize(messageData);
|
|
_ = Task.Run(async () => { await tcpClient.Send(messageData, 0, len); bufferRental.Return(messageData); });
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
tcpClient.Stop();
|
|
}
|
|
|
|
private void TcpClient_Connected()
|
|
{
|
|
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.Connected, EventData = null });
|
|
}
|
|
|
|
private void TcpClient_Disconnected(bool clean, string error)
|
|
{
|
|
if(!clean)
|
|
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.Failed, EventData = new LobbyClientDisconnectReason { WasError = true, ErrorMessage = error } });
|
|
else
|
|
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)
|
|
{
|
|
try
|
|
{
|
|
if (dataLength > 0)
|
|
{
|
|
switch (PeekTypeId(data.Span))
|
|
{
|
|
case LobbyInfo.TypeId:
|
|
{
|
|
var lobbyInfo = LobbyInfo.Deserialize(data.Span);
|
|
if (lobbyInfo != null)
|
|
{
|
|
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.LobbyUpdate, EventData = lobbyInfo });
|
|
}
|
|
}
|
|
break;
|
|
case LobbyDelete.TypeId:
|
|
{
|
|
var lobbyDelete = LobbyDelete.Deserialize(data.Span);
|
|
if (lobbyDelete != null)
|
|
{
|
|
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.LobbyDelete, EventData = lobbyDelete });
|
|
}
|
|
}
|
|
break;
|
|
case LobbyHostInfo.TypeId:
|
|
{
|
|
var lobbyHostInfo = LobbyHostInfo.Deserialize(data.Span);
|
|
if (lobbyHostInfo != null)
|
|
{
|
|
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.LobbyHostInfo, EventData = lobbyHostInfo });
|
|
}
|
|
}
|
|
break;
|
|
case LobbyRequestNatPunch.TypeId:
|
|
{
|
|
var lobbyRequestNatPunch = LobbyRequestNatPunch.Deserialize(data.Span);
|
|
if (lobbyRequestNatPunch != null)
|
|
{
|
|
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.LobbyRequestNatPunch, EventData = lobbyRequestNatPunch });
|
|
}
|
|
}
|
|
break;
|
|
case LobbyNatPunchDone.TypeId:
|
|
{
|
|
var lobbyNatPunchDone = LobbyNatPunchDone.Deserialize(data.Span);
|
|
if (lobbyNatPunchDone != null)
|
|
{
|
|
events.Enqueue(new LobbyClientEvent { EventType = LobbyClientEventTypes.LobbyNatPunchDone, EventData = lobbyNatPunchDone });
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
tcpClient.Dispose();
|
|
}
|
|
|
|
}
|
|
}
|