// FreelancerListServer.cs using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace FreelancerListServer { public class FreelancerListServer : IDisposable { private readonly UdpClient _udpClient; private readonly ConcurrentDictionary _clientStates; private readonly ConcurrentDictionary _servers; private readonly ConcurrentDictionary _clientTempIds; private readonly ConcurrentDictionary _clientIdleValues; private readonly ConcurrentDictionary _echoSentFlags; private readonly CancellationTokenSource _cancellationTokenSource; private readonly Logger _logger; private readonly LiteDbManager _dbManager; private readonly string _localServerIp; private readonly string _publicServerIp; public FreelancerListServer(int port, string bindIp = "0.0.0.0") { _udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse(bindIp), port)); _clientStates = new ConcurrentDictionary(); _servers = new ConcurrentDictionary(); _clientTempIds = new ConcurrentDictionary(); _clientIdleValues = new ConcurrentDictionary(); _echoSentFlags = new ConcurrentDictionary(); _cancellationTokenSource = new CancellationTokenSource(); _logger = new Logger(); _dbManager = new LiteDbManager(); // Lade Konfigurationswerte _localServerIp = _dbManager.GetConfiguration("LocalServerIp", "192.168.200.10"); _publicServerIp = _dbManager.GetConfiguration("PublicServerIp", "195.201.106.232"); // Migration ausführen, falls INI-Dateien existieren if (File.Exists("servers.ini") || File.Exists("monitoring.ini")) { _dbManager.MigrateFromIni("servers.ini", "monitoring.ini"); } LoadKnownServers(); _logger.Log($"UDP Server initialized on {bindIp}:{port}"); } public async Task StartAsync() { _logger.Log($"UDP Server listening on port {_udpClient.Client.LocalEndPoint}..."); _ = Task.Run(() => SendKeepAlivePacketsAsync(_cancellationTokenSource.Token)); _ = Task.Run(async () => { while (!_cancellationTokenSource.Token.IsCancellationRequested) { try { await CheckPlayerCountAsync(_cancellationTokenSource.Token); } catch (Exception ex) { _logger.Log($"Critical error in CheckPlayerCountAsync, continuing: {ex.Message}"); await Task.Delay(1000, _cancellationTokenSource.Token); } } }, _cancellationTokenSource.Token); await ReceivePacketsAsync(); } private void LoadKnownServers() { var knownServers = _dbManager.GetServers(); var currentServerIds = new HashSet(_servers.Keys); // Neue und aktualisierte Server hinzufügen foreach (var section in knownServers) { var serverData = section.Value; if (serverData.TryGetValue("GameId", out var gameId) && !string.IsNullOrEmpty(gameId)) { var serverInfo = new ServerInfo { GameId = gameId, Name = serverData.GetValueOrDefault("Name", "Unknown"), IP = serverData.GetValueOrDefault("IP", "0.0.0.0"), GamePort = int.TryParse(serverData.GetValueOrDefault("GamePort", "2300"), out var port) ? port : 2300, MaxPlayers = serverData.GetValueOrDefault("MaxPlayers", "0"), Pass = serverData.GetValueOrDefault("Pass", "0"), PvP = serverData.GetValueOrDefault("PvP", "0"), GameOpts = serverData.GetValueOrDefault("GameOpts", ""), Active = false, LastSeen = DateTime.MinValue }; if (_servers.TryGetValue(gameId, out var existingServer)) { // Bestehenden Server aktualisieren existingServer.Name = serverInfo.Name; existingServer.IP = serverInfo.IP; existingServer.GamePort = serverInfo.GamePort; existingServer.MaxPlayers = serverInfo.MaxPlayers; existingServer.Pass = serverInfo.Pass; existingServer.PvP = serverInfo.PvP; existingServer.GameOpts = serverInfo.GameOpts; _logger.Log($"Updated server from DB: GameId={gameId}, Name={serverInfo.Name}, IP={serverInfo.IP}, GamePort={serverInfo.GamePort}"); } else { // Neuen Server hinzufügen _servers[gameId] = serverInfo; _logger.Log($"Loaded new server from DB: GameId={gameId}, Name={serverInfo.Name}, IP={serverInfo.IP}, GamePort={serverInfo.GamePort}, GameOpts={serverInfo.GameOpts}"); } currentServerIds.Remove(gameId); } } // Entferne Server, die nicht mehr in der Datenbank existieren foreach (var serverId in currentServerIds) { if (_servers.TryRemove(serverId, out var removedServer)) { _logger.Log($"Removed server no longer in DB: GameId={serverId}"); } } } private async Task SendKeepAlivePacketsAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { try { var activeServers = _servers.Values.Where(s => s.Active && s.LastSeen.AddSeconds(60) >= DateTime.Now).ToList(); _logger.Log($"Found {activeServers.Count} active servers."); foreach (var server in activeServers) { if (_clientStates.TryGetValue(server.ClientPort, out int comState) && comState == 6) { byte[] keepAlivePacket = new byte[] { 0x3f, 0x00, 0x05, 0x06 }; string targetIp = server.IP == _publicServerIp ? _localServerIp : server.IP; await _udpClient.SendAsync(keepAlivePacket, keepAlivePacket.Length, targetIp, server.ClientPort); _logger.Log($"Sent keep-alive to {targetIp}:{server.ClientPort}: {BitConverter.ToString(keepAlivePacket).Replace("-", "")}"); } else { server.Active = false; _logger.Log($"Marked server {server.GameId} as inactive due to comState {comState}"); } } } catch (Exception ex) { _logger.Log($"Error in SendKeepAlivePacketsAsync: {ex.Message}"); } await Task.Delay(5000, cancellationToken); } } private async Task CheckPlayerCountAsync(CancellationToken cancellationToken) { byte[] infoPacket = new byte[] { 0x00, 0x02, 0xFF, 0xFF, 0x02 }; // INFO-Paket try { // Serverliste aus der Datenbank neu laden LoadKnownServers(); var allServers = _servers.Values.ToList(); _logger.Log($"Checking player count for {allServers.Count} servers."); foreach (var server in allServers) { if (cancellationToken.IsCancellationRequested) { _logger.Log("CheckPlayerCountAsync cancelled."); return; } // NAT-Logik: Verwende lokale IP, wenn Server-IP die öffentliche IP ist string targetIp = server.IP == _publicServerIp ? _localServerIp : server.IP; int targetPort = server.GamePort; try { using var udpClient = new UdpClient(); udpClient.Client.ReceiveTimeout = 2000; await udpClient.SendAsync(infoPacket, infoPacket.Length, targetIp, targetPort); _logger.Log($"Sent player count request to {targetIp}:{targetPort} (original IP: {server.IP}): {BitConverter.ToString(infoPacket).Replace("-", "")}"); var receiveTask = udpClient.ReceiveAsync(); var timeoutTask = Task.Delay(2000, cancellationToken); var completedTask = await Task.WhenAny(receiveTask, timeoutTask); if (completedTask == timeoutTask) { _logger.Log($"Timeout querying player count for {server.GameId} ({server.IP}:{server.GamePort}, used {targetIp}:{targetPort})"); _dbManager.UpdateMonitoring(server.GameId, "unknown", "0", "0", "unknown", "Server offline"); continue; } var result = await receiveTask; byte[] data = result.Buffer; if (data.Length < 80) { _logger.Log($"Invalid response from {targetIp}:{targetPort} (original IP: {server.IP}), length: {data.Length}, hex: {BitConverter.ToString(data).Replace("-", "")}"); _dbManager.UpdateMonitoring(server.GameId, "unknown", "0", "0", "unknown", "Invalid response"); continue; } string session = BitConverter.ToUInt32(data, 0x10) == 1 ? "client-server" : "unknown"; uint maxPlayers = BitConverter.ToUInt32(data, 0x14); uint players = BitConverter.ToUInt32(data, 0x18); players = players - 1; string instanceGuid = $"{BitConverter.ToUInt32(data, 0x3C):x8}-" + $"{BitConverter.ToUInt16(data, 0x40):x4}-" + $"{BitConverter.ToUInt16(data, 0x42):x4}-" + $"{data[0x44]:x2}{data[0x45]:x2}-" + $"{data[0x46]:x2}{data[0x47]:x2}{data[0x48]:x2}{data[0x49]:x2}{data[0x4A]:x2}{data[0x4B]:x2}"; StringBuilder name = new StringBuilder(); for (int i = 0x58; i < data.Length - 1; i += 2) { char c = (char)BitConverter.ToUInt16(data, i); if (c == '\0') break; name.Append(c); } string finalName = name.Length > 2 ? name.ToString().Substring(2) : name.ToString(); _dbManager.UpdateMonitoring(server.GameId, session, maxPlayers.ToString(), players.ToString(), instanceGuid, finalName.Trim()); _logger.Log($"Updated monitoring for {server.GameId}: {players}/{maxPlayers}, Session: {session}, Name: {finalName}, IP: {targetIp}:{targetPort} (original IP: {server.IP})"); } catch (SocketException ex) { _logger.Log($"Socket error querying player count for {server.GameId} ({server.IP}:{server.GamePort}, used {targetIp}:{targetPort}): {ex.Message} (ErrorCode: {ex.SocketErrorCode})"); _dbManager.UpdateMonitoring(server.GameId, "unknown", "0", "0", "unknown", "Server offline"); } catch (Exception ex) { _logger.Log($"Error querying player count for {server.GameId} ({server.IP}:{server.GamePort}, used {targetIp}:{targetPort}): {ex.Message}"); _dbManager.UpdateMonitoring(server.GameId, "unknown", "0", "0", "unknown", "Server offline"); } } } catch (Exception ex) { _logger.Log($"Error in CheckPlayerCountAsync loop: {ex.Message}"); throw; } await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken); } private async Task ReceivePacketsAsync() { while (!_cancellationTokenSource.Token.IsCancellationRequested) { try { var result = await _udpClient.ReceiveAsync(); byte[] data = result.Buffer; IPEndPoint clientEndPoint = result.RemoteEndPoint; await ProcessPacketAsync(data, clientEndPoint); } catch (Exception ex) { _logger.Log($"Error receiving packet: {ex.Message}"); } } } private async Task ProcessPacketAsync(byte[] data, IPEndPoint clientEndPoint) { try { string hexData = BitConverter.ToString(data).Replace("-", "").ToLower(); _logger.Log($"Received from {clientEndPoint}: {hexData}, Length: {data.Length}"); int clientPort = clientEndPoint.Port; int comState = _clientStates.GetOrAdd(clientPort, 1); string tempId = _clientTempIds.GetValueOrDefault(clientPort, data.Length >= 12 ? BitConverter.ToString(data, 8, 4).Replace("-", "").ToLower() : "Unknown"); _logger.Log($"Current comState for {clientEndPoint}: {comState}, Temp ID: {tempId}"); if (comState == 1 && data.Length >= 16 && data[0] == 0x88 && data[1] == 0x01) { string tempServerId = BitConverter.ToString(data, 8, 4).Replace("-", "").ToLower(); _clientTempIds[clientPort] = tempServerId; _clientIdleValues[clientPort] = (0, 0); _echoSentFlags.TryRemove(clientPort, out _); _logger.Log($"Registered client with Temp ID: {tempServerId} from {clientEndPoint}"); byte[] response = new byte[] { 0x88, 0x02, data[2], data[3], 0x02, 0x00, 0x01, 0x00, data[8], data[9], data[10], data[11], 0x00, 0x00, 0x00, 0x00 }; await _udpClient.SendAsync(response, response.Length, clientEndPoint); _logger.Log($"Sent response to {clientEndPoint}: {BitConverter.ToString(response).Replace("-", "")}"); _clientStates[clientPort] = 2; } else if (comState == 2 && ((data.Length == 16 && data[0] == 0x80 && data[1] == 0x02 && data[2] == 0x01 && data[3] == 0x00) || (data.Length == 4 && data[0] == 0x3f && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x00))) { if (!_echoSentFlags.ContainsKey(clientPort)) { byte[] response = new byte[] { 0x3f, 0x00, 0x00, 0x00 }; await _udpClient.SendAsync(response, response.Length, clientEndPoint); _logger.Log($"Sent echo to {clientEndPoint}: {BitConverter.ToString(response).Replace("-", "")}"); _echoSentFlags[clientPort] = true; } else { _logger.Log($"Skipping duplicate echo response for {clientEndPoint}"); } _clientStates[clientPort] = 2; _clientIdleValues[clientPort] = (0, 0); } else if (comState == 2 && data.Length == 96 && data[0] == 0x7f && data[1] == 0x00 && data[2] == 0x01 && data[3] == 0x00) { string protocolString = "Gun Base Network instance"; byte[] protocolStringBytes = Encoding.Unicode.GetBytes(protocolString + "\0"); byte[] response = new byte[] { 0x7f, 0x00, 0x01, 0x02, 0xc2, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x01, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, 0x74, 0xa7, 0x35, 0x88, 0xe5, 0x06, 0x44, 0x9b, 0x84, 0x4a, 0xa3, 0x19, 0xcc, 0x61, 0x7c, 0x26, 0xf0, 0x90, 0xa6, 0xf0, 0x26, 0x57, 0x4e, 0xac, 0xa0, 0xec, 0xf8, 0x68, 0xe4, 0x8d, 0x21, 0x6f, 0x74, 0xd7, 0x84, 0x17, 0xbb, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb, 0x74, 0x87, 0x35, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0x74, 0xd7, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x17, 0xbb, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }.Concat(protocolStringBytes).Concat(new byte[] { 0x00, 0x00, 0x00, 0x00, 0xdc, 0x42, 0x61, 0x6c }).ToArray(); await _udpClient.SendAsync(response, response.Length, clientEndPoint); _logger.Log($"Sent response to {clientEndPoint}: {BitConverter.ToString(response).Replace("-", "")}"); _clientStates[clientPort] = 3; _echoSentFlags.TryRemove(clientPort, out _); } else if (comState == 2 && ((data.Length == 12 && data[0] == 0x80 && data[1] == 0x06 && data[2] == 0x01 && data[3] == 0x00 && data[4] == 0x02 && data[5] == 0x01) || (data.Length == 4 && data[0] == 0x3f && data[1] == 0x01 && data[2] == 0x00 && data[3] == 0x01))) { _logger.Log($"Ignoring duplicate 800601000201 or 3f010001 from {clientEndPoint}"); } else if (comState == 3 && data.Length == 8 && data[0] == 0x7f && (data[1] == 0x00 || data[1] == 0x01) && data[2] == 0x02 && data[3] == 0x02 && data[4] == 0xc3) { byte[] response = new byte[] { 0x80, 0x06, 0x01, 0x00, 0x02, 0x03, 0x00, 0x00, 0x7e, 0xdf, 0x67, 0x6c }; await _udpClient.SendAsync(response, response.Length, clientEndPoint); _logger.Log($"Sent response to {clientEndPoint}: {BitConverter.ToString(response).Replace("-", "")}"); _clientStates[clientPort] = 3; } else if (comState == 3 && data.Length >= 46 && data[0] == 0x3f && data[1] == 0x00 && data[2] == 0x03 && data[3] == 0x02) { string packetString = Encoding.UTF8.GetString(data, 4, data.Length - 4).TrimEnd('\0'); if (packetString.StartsWith(" \0"; byte[] response2 = new byte[] { 0x3f, 0x00, 0x02, 0x04 }.Concat(Encoding.UTF8.GetBytes(connectRes)).ToArray(); await _udpClient.SendAsync(response2, response2.Length, clientEndPoint); _logger.Log($"Sent response to {clientEndPoint}: {BitConverter.ToString(response2).Replace("-", "")}"); _clientStates[clientPort] = 4; } else { _logger.Log($"Invalid packet from {clientEndPoint}"); } } else if (comState == 4 && data.Length >= 200 && data[0] == 0x3f && data[1] == 0x00 && data[2] == 0x04 && data[3] == 0x03) { string packetString = Encoding.UTF8.GetString(data, 4, data.Length - 4).TrimEnd('\0'); if (packetString.StartsWith(" from {clientEndPoint} with CdKey: {cdKey}"); byte[] response1 = new byte[] { 0x80, 0x06, 0x01, 0x00, 0x03, 0x05, 0x00, 0x00, 0xf5, 0xe5, 0x67, 0x6c }; await _udpClient.SendAsync(response1, response1.Length, clientEndPoint); _logger.Log($"Sent response to {clientEndPoint}: {BitConverter.ToString(response1).Replace("-", "")}"); string conInfoRes = "\0"; byte[] response2 = new byte[] { 0x3f, 0x00, 0x03, 0x05 }.Concat(Encoding.UTF8.GetBytes(conInfoRes)).ToArray(); await _udpClient.SendAsync(response2, response2.Length, clientEndPoint); _logger.Log($"Sent response to {clientEndPoint}: {BitConverter.ToString(response2).Replace("-", "")}"); _clientStates[clientPort] = 5; } else { _logger.Log($"Invalid packet from {clientEndPoint}"); } } else if (comState == 5 && data.Length >= 58 && data[0] == 0x3f && (data[1] == 0x00 || data[1] == 0x01)) { string packetString = Encoding.UTF8.GetString(data, 4, data.Length - 4).TrimEnd('\0'); if (packetString.StartsWith("= 6) { gameId = parts[4] + ":" + parts[5]; } } ServerInfo serverInfo; string serverIp = clientEndPoint.Address.ToString(); if (serverIp == _localServerIp) { serverIp = _publicServerIp; } if (!string.IsNullOrEmpty(gameId) && _servers.TryGetValue(gameId, out var existingServer)) { serverInfo = existingServer; _servers.TryRemove(tempServerId, out _); serverInfo.IP = serverIp; serverInfo.ClientPort = clientEndPoint.Port; serverInfo.GamePort = int.TryParse(optsMatch.Groups[1].Value.Split(':')[0].Split(',')[0], out int port) ? port : serverInfo.GamePort; serverInfo.MaxPlayers = optsMatch.Groups[1].Value.Split(':')[1].Split(',')[0]; serverInfo.Pass = optsMatch.Groups[1].Value.Split(':')[2]; serverInfo.PvP = optsMatch.Groups[1].Value.Split(':')[3]; serverInfo.Name = serverName; serverInfo.GameOpts = gameOpts; serverInfo.LastSeen = DateTime.Now; serverInfo.Active = true; _logger.Log($"Updated existing server with GameId: {gameId}, Name: {serverName}, IP: {serverInfo.IP}, ClientPort: {serverInfo.ClientPort}, GameOpts: {gameOpts}"); } else { serverInfo = new ServerInfo { Id = tempServerId, GameId = gameId, IP = serverIp, ClientPort = clientEndPoint.Port, GamePort = int.TryParse(optsMatch.Groups[1].Value.Split(':')[0].Split(',')[0], out int port) ? port : clientEndPoint.Port, Name = serverName, MaxPlayers = optsMatch.Success ? optsMatch.Groups[1].Value.Split(':')[1].Split(',')[0] : "0", Pass = optsMatch.Success ? optsMatch.Groups[1].Value.Split(':')[2] : "0", PvP = optsMatch.Success ? optsMatch.Groups[1].Value.Split(':')[3] : "0", GameOpts = gameOpts, Active = true, LastSeen = DateTime.Now }; if (!string.IsNullOrEmpty(gameId)) { _servers[gameId] = serverInfo; _servers.TryRemove(tempServerId, out _); _logger.Log($"Registered new server with GameId: {gameId}, Name: {serverName}, GameOpts: {gameOpts}"); } else { _servers[tempServerId] = serverInfo; _logger.Log($"Registered new server with Temp ID: {tempServerId}, Name: {serverName}, GameOpts: {gameOpts}"); } } if (!string.IsNullOrEmpty(serverInfo.GameId)) { _dbManager.UpdateServer(serverInfo.GameId, serverInfo); } _logger.Log($"Updated server {serverInfo.GameId ?? tempServerId} with Name: {serverName}, GamePort: {serverInfo.GamePort}, ClientPort: {serverInfo.ClientPort}, GameId: {serverInfo.GameId}, GameOpts: {serverInfo.GameOpts}"); string hostDataRes = "\0"; byte[] response = new byte[] { 0x3f, 0x00, 0x04, 0x06 }.Concat(Encoding.UTF8.GetBytes(hostDataRes)).ToArray(); await _udpClient.SendAsync(response, response.Length, clientEndPoint); _logger.Log($"Sent response to {clientEndPoint}: {BitConverter.ToString(response).Replace("-", "")}"); _clientStates[clientPort] = 6; byte[] keepAlivePacket = new byte[] { 0x3f, 0x00, 0x05, 0x06 }; string targetIp = serverInfo.IP == _publicServerIp ? _localServerIp : serverInfo.IP; await _udpClient.SendAsync(keepAlivePacket, keepAlivePacket.Length, targetIp, serverInfo.ClientPort); _logger.Log($"Sent initial keep-alive to {targetIp}:{serverInfo.ClientPort}: {BitConverter.ToString(keepAlivePacket).Replace("-", "")}"); } else if (packetString.Contains("PageNo=")) { var pageNoMatch = Regex.Match(packetString, @"PageNo=""([^""]*)"""); if (pageNoMatch.Success) { string pageNo = pageNoMatch.Groups[1].Value; var iniServers = _dbManager.GetServers(); var serverList = new List(); foreach (var section in iniServers) { var serverData = section.Value; if (serverData.TryGetValue("GameId", out var gameId) && !string.IsNullOrEmpty(gameId)) { var serverInfo = new ServerInfo { GameId = gameId, Name = serverData.GetValueOrDefault("Name", "Unknown"), IP = serverData.GetValueOrDefault("IP", "0.0.0.0"), GamePort = int.TryParse(serverData.GetValueOrDefault("GamePort", "2300"), out var port) ? port : 2300, MaxPlayers = serverData.GetValueOrDefault("MaxPlayers", "0"), Pass = serverData.GetValueOrDefault("Pass", "0"), PvP = serverData.GetValueOrDefault("PvP", "0"), GameOpts = serverData.GetValueOrDefault("GameOpts", ""), Active = false, LastSeen = DateTime.MinValue }; serverList.Add(serverInfo); } } _logger.Log($"Processing PageNo={pageNo}, INI servers: {serverList.Count}"); var idleValues = _clientIdleValues.GetOrAdd(clientPort, (0, 0)); byte seq1 = (byte)(data[2] - 1 - idleValues.idle01); byte seq2 = (byte)(data[3] + 2 + idleValues.idle02); _clientIdleValues[clientPort] = (idleValues.idle01 + (data[1] == 0x01 ? 1 : 0), idleValues.idle02); _logger.Log($"Seq numbers for PageNo={pageNo}: seq1={seq1:X2}, seq2={seq2:X2}, idle01={idleValues.idle01}, idle02={idleValues.idle02}"); string pageRes; if (int.TryParse(pageNo, out int pageIndex)) { string vTotal = serverList.Count.ToString(); if (pageIndex < serverList.Count) { var server = serverList[pageIndex]; string displayIp = server.IP == _publicServerIp ? _publicServerIp : server.IP; string gameOpts = !string.IsNullOrEmpty(server.GameOpts) ? server.GameOpts : $"{server.GamePort},{server.GamePort},{server.GamePort},{server.GamePort}:{server.MaxPlayers}:{server.Pass}:{server.PvP}:{server.GameId}"; pageRes = $"" + $"" + "\0"; _logger.Log($"Added server to PageRes: GameId={server.GameId}, Name={server.Name}, IP={displayIp}, GameOpts={gameOpts}, VTotal={vTotal}"); } else { pageRes = $"\0"; _logger.Log($"No server found for PageNo={pageNo}, INI servers count: {serverList.Count}, VTotal={vTotal}"); } } else { pageRes = $"\0"; _logger.Log($"Invalid PageNo value: {pageNo}, VTotal={serverList.Count}"); } byte[] response = new byte[] { 0x3f, 0x00, seq1, seq2 }.Concat(Encoding.UTF8.GetBytes(pageRes)).ToArray(); await _udpClient.SendAsync(response, response.Length, clientEndPoint); _logger.Log($"Sent PageRes to {clientEndPoint}: {BitConverter.ToString(response).Replace("-", "")}"); } else { _logger.Log($"Invalid PageNo packet from {clientEndPoint}: {packetString}"); } } else { _logger.Log($"Invalid or packet from {clientEndPoint}: {packetString}"); } } else if (comState == 5 && data.Length == 4 && data[0] == 0x3f && (data[1] == 0x0a || data[1] == 0x0b)) { _logger.Log($"Received packet {BitConverter.ToString(data).Replace("-", "")} from {clientEndPoint}, no response sent (mimicking official server)."); } else if (comState == 6 && data.Length == 4 && data[0] == 0x3f && (data[1] == 0x00 || data[1] == 0x01) && data[2] == 0x06 && data[3] == 0x05) { byte[] response = new byte[] { 0x80, 0x06, 0x01, 0x00, 0x06, 0x06, 0x00, 0x00, 0xdf, 0x2a, 0x97, 0x00 }; await _udpClient.SendAsync(response, response.Length, clientEndPoint); _logger.Log($"Sent keep-alive response to {clientEndPoint}: {BitConverter.ToString(response).Replace("-", "")}"); string serverId = _servers.FirstOrDefault(s => s.Value.ClientPort == clientPort).Value?.GameId ?? "Unknown"; if (_servers.TryGetValue(serverId, out var serverInfo)) { serverInfo.LastSeen = DateTime.Now; _logger.Log($"Updated LastSeen for {serverId}"); } } else if (comState == 6 && data.Length == 12 && data[0] == 0x80 && data[1] == 0x06 && data[2] == 0x01 && data[3] == 0x00 && data[4] == 0x06 && data[5] == 0x06) { byte[] response = new byte[] { 0x80, 0x06, 0x01, 0x00, 0x06, 0x06, 0x00, 0x00, 0xdf, 0x2a, 0x97, 0x00 }; await _udpClient.SendAsync(response, response.Length, clientEndPoint); _logger.Log($"Sent keep-alive response to {clientEndPoint}: {BitConverter.ToString(response).Replace("-", "")}"); string serverId = _servers.FirstOrDefault(s => s.Value.ClientPort == clientPort).Value?.GameId ?? "Unknown"; if (_servers.TryGetValue(serverId, out var serverInfo)) { serverInfo.LastSeen = DateTime.Now; serverInfo.Active = true; _logger.Log($"Updated LastSeen and Active for {serverId}"); } } else if (data.Length >= 12 && data[0] == 0x80 && data[1] == 0x06) { string serverId = _servers.FirstOrDefault(s => s.Value.ClientPort == clientPort).Value?.GameId ?? "Unknown"; if (_servers.TryGetValue(serverId, out var serverInfo)) { serverInfo.LastSeen = DateTime.Now; serverInfo.Active = true; _logger.Log($"Received 8006 packet from {clientEndPoint}, updated LastSeen and Active for {serverId}, no response sent."); } } else { _logger.Log($"Unknown packet, no response sent to {clientEndPoint}"); } } catch (Exception ex) { _logger.Log($"Error processing packet from {clientEndPoint}: {ex.Message}"); } } public void Dispose() { _cancellationTokenSource.Cancel(); _udpClient.Close(); _dbManager.Dispose(); _logger.Log("Server stopped and database disposed."); } } }