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 events = new ConcurrentQueue(); 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 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 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 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(); } } }