using System.Net; using System.Net.WebSockets; using Microsoft.Extensions.Logging; using Tiger.Communication.Messages; using Tiger.Communication.Messages.Types; using Tiger.Game.Habbos; using Tiger.Networking.Game.Sessions; using Tiger.Storage; using Tiger.Utils; namespace Tiger.Networking.Game; public class WebSocketServer : IWebSocketServer { private readonly HttpListener _httpListener = new(); private readonly IGameSessionManager _gameSessionManager; private readonly IMessageHandler _messageHandler; private readonly IRepository _habboRepository; private readonly ILogger _logger; public WebSocketServer(IGameSessionManager gameSessionManager, IMessageHandler messageHandler, IRepository habboRepository, ILogger logger) { _gameSessionManager = gameSessionManager; _messageHandler = messageHandler; _habboRepository = habboRepository; _logger = logger; } public async Task Start(string uriPrefix) { _httpListener.Prefixes.Add(uriPrefix); _httpListener.Start(); _logger.LogInformation("WebSocket server running on {uriPrefix}...", uriPrefix); while (true) { var context = await _httpListener.GetContextAsync(); if (context.Request.IsWebSocketRequest) { ProcessRequestAsync(context); } else { context.Response.StatusCode = 400; context.Response.Close(); } } } private async void ProcessRequestAsync(HttpListenerContext context) { WebSocket? webSocket = null; try { var webSocketContext = await context.AcceptWebSocketAsync(null); webSocket = webSocketContext.WebSocket; var session = _gameSessionManager.AddSession(webSocket); await ReceiveMessageAsync(session.SessionId); } catch (Exception e) { Console.WriteLine($"WebSocket Session Error: {e.Message}"); } finally { if (webSocket != null) { _gameSessionManager.RemoveSession(webSocket); webSocket.Dispose(); } } } private async Task ReceiveMessageAsync(string sessionId) { var buffer = new byte[1024 * 4]; GameSession? gameSession; while ((gameSession = _gameSessionManager.GetSession(sessionId)) is { WebSocket.State: WebSocketState.Open }) { var result = await gameSession.WebSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); var offset = 0; while (result.MessageType == WebSocketMessageType.Binary && offset + 4 <= result.Count) { var length = ByteUtils.GetInt32(new[] { buffer[offset], buffer[offset + 1], buffer[offset + 2], buffer[offset + 3] }); offset += 4; if (offset + length > result.Count) break; var packet = new byte[result.Count]; Array.Copy(buffer, offset, packet, 0, length); offset += length; _messageHandler.TryHandleAsync(gameSession, new ClientMessage(packet)); } if (result.MessageType == WebSocketMessageType.Close) { await gameSession.WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); } } if (gameSession is { Habbo: not null }) { gameSession.Habbo.Online = false; await _habboRepository.SaveAsync(gameSession.Habbo); _logger.LogInformation("{User} logged out", gameSession.Habbo.Username); } } }