using System.Net.Sockets; using System.Net; namespace NotPogo { public class TCPGameServer(ILogger logger, IConfiguration configuration) : BackgroundService { private readonly ILogger _logger = logger; private readonly IConfiguration _configuration = configuration; private int _tcpPort; public TcpListener? Listener { get; private set; } public static List Clients { get; } = []; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _tcpPort = _configuration.GetValue("GameServerPort"); Listener = new(IPAddress.Any, _tcpPort); Listener.Start(); _logger.LogInformation("Game Server Listening On Port {Port}", _tcpPort); while (!stoppingToken.IsCancellationRequested) { var client = await Listener.AcceptTcpClientAsync(stoppingToken); _logger.LogInformation("Client Connected: {RemoteEndPoint}", client.Client.RemoteEndPoint); _ = HandleClientAsync(client); } } private async Task HandleClientAsync(TcpClient client) { using (client) await using (var stream = client.GetStream()) { try { while (true) { Dictionary received; try { received = await TCPProtocol.ReadMessageAsync(stream); } catch (EndOfStreamException) { break; } _logger.LogInformation("Received:"); foreach (var kv in received) _logger.LogInformation(" {Key} = {Value}", kv.Key, kv.Value); if (!received.TryGetValue("msg.op", out var op)) continue; if (op == "logn") // Login Request { // we know this is a compatible client now so let's add it to the client list Clients.Add(client); // send logn response chain await SendAsync(stream, ("msg.op", "init"), ("plid", "1"), ("cnid", "1")); // PLID and CNID are placeholder values here await SendAsync(stream, ("msg.op", "load"), ("rmst", "start")); // indicate the backend is putting you in a room await SendAsync(stream, ("msg.op", "rm.join"), ("scrn", "standalone"), ("plid", "1")); // SCRN and PLID are placeholder values here (PLID = Room ID?) await SendAsync(stream, ("msg.op", "gm.crea")); // I don't know what this does but apparently it's required await SendAsync(stream, ("msg.op", "load"), ("rmst", "end")); // room placement complete await SendAsync(stream, ("msg.op", "say"), ("chnl", "serv"), ("text", "Welcome to Not Pogo! The best place to go for games and friends!")); // MOTD await SendAsync(stream, ("msg.op", "say"), ("chnl", "serv"), ("text", "WARNING: This Emulator Is A WIP. Some Things May Not Work As Expected Even With Proper Client Props")); // Alpha Disclaimer await SendAsync(stream, ("msg.op", "pl.rm.stte"), ("plid", "1"), ("tokb", "9999")); // PLID and TOKB are placeholder values here _logger.LogInformation("Sent 'logn' Response Sequence"); } else if (op == "gm.join") // Room Join Request { // send gm.join response chain await SendAsync(stream, ("msg.op", "gm.join"), ("plid", "1"), ("scrn", "standalone")); // yay more placeholders! await SendAsync(stream, ("msg.op", "jcfg"), ("jpot", "1234.56"), ("jmlt", "1.0"), ("cap", "4999.0"), ("intv", "10000"), ("incr", "0.01")); // more than likely jackpot config await SendAsync(stream, ("msg.op", "cnfg"), ("impd", "15")); // placeholder, idk what this is await SendAsync(stream, ("msg.op", "tokb"), ("tokb", "9999")); // token balance again for whatever reason await SendAsync(stream, ("msg.op", "gm.lnch")); // more than likely this tells the shell to launch the game await SendAsync(stream, ("msg.op", "ext.pref"), ("pref", "")); // game preferences _logger.LogInformation("Sent 'gm.join' Response Sequence (Launches Game)"); } else if (op == "ext.over") { bool winMet = (received["gm.won"] == "true"); if (winMet) _logger.LogInformation("Jackpot Won!"); // send stubbed results await SendAsync(stream, ("msg.op", "ext.rslt"), ("tokb", "999"), ("tokn.won", "0"), // Token Earnings ("spin.rslt", winMet ? "1" : "0"), ("spin.tokn", "50"), ("inst.desc", ""), // Jackpot? ("jpot", "1234.0"), ("pzpm", $"conf={Guid.NewGuid()}"), ("bmlt", "1"), ("bspo", "")); // Jackpot Confirmation Stuff - placeholder _logger.LogInformation("Results Sent"); } } } catch (Exception e) { _logger.LogError(e, "Error Handling Client"); } } Clients.Remove(client); _logger.LogInformation("Client Disconnected"); } static Task SendAsync(NetworkStream stream, params (string key, string value)[] fields) { var msg = new Dictionary(); foreach (var (key, value) in fields) msg[key] = value; return TCPProtocol.WriteMessageAsync(stream, msg); } public override Task StopAsync(CancellationToken cancellationToken) { if(!cancellationToken.IsCancellationRequested) { Clients.Clear(); Listener?.Stop(); Listener?.Dispose(); } return Task.CompletedTask; } } }