Files
ListServer/FreelancerListServer.cs
Nekura b9d7b1c9f7 Init
2025-07-25 18:39:48 +02:00

643 lines
38 KiB
C#

// 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<int, int> _clientStates;
private readonly ConcurrentDictionary<string, ServerInfo> _servers;
private readonly ConcurrentDictionary<int, string> _clientTempIds;
private readonly ConcurrentDictionary<int, (int idle01, int idle02)> _clientIdleValues;
private readonly ConcurrentDictionary<int, bool> _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<int, int>();
_servers = new ConcurrentDictionary<string, ServerInfo>();
_clientTempIds = new ConcurrentDictionary<int, string>();
_clientIdleValues = new ConcurrentDictionary<int, (int idle01, int idle02)>();
_echoSentFlags = new ConcurrentDictionary<int, bool>();
_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<string>(_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("<Connect"))
{
byte[] response1 = new byte[] { 0x80, 0x06, 0x01, 0x00, 0x02, 0x04, 0x00, 0x00, 0x05, 0xe2, 0x67, 0x6c };
await _udpClient.SendAsync(response1, response1.Length, clientEndPoint);
_logger.Log($"Sent response to {clientEndPoint}: {BitConverter.ToString(response1).Replace("-", "")}");
string connectRes = "<ConnectRes Cx=\"0x\" HR=\"0x0\" Random=\"-1973390402\" ><RSAKey Val=\"giaaaaaPaaGutfumaiaaaeaabaW15q5mqNXZY!tRuUMGXfIxDy9TwdUw.DCq0fpvC9YBqcMKxNH7dsMfXvTc.mamkAUkK2LIlqRM.q1ur!b2ed1T\"/></ConnectRes> \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 <Connect> 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("<ConInfo"))
{
var cdKeyMatch = Regex.Match(packetString, @"CdKey Val=""([^""]*)""");
string cdKey = cdKeyMatch.Success ? cdKeyMatch.Groups[1].Value : "Unknown";
_logger.Log($"Received <ConInfo> 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 = "<ConInfoRes Cx=\"0x\" HR=\"0x0\" Time=\"202834681\" />\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 <ConInfo> 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("<HostData"))
{
var nameMatch = Regex.Match(packetString, @"GName=""([^""]*)""");
var optsMatch = Regex.Match(packetString, @"GameOpts=""([^""]*)""");
string serverName = nameMatch.Success ? nameMatch.Groups[1].Value : "Unknown";
string gameOpts = optsMatch.Success ? optsMatch.Groups[1].Value : "";
string tempServerId = _clientTempIds.GetValueOrDefault(clientPort, "Unknown");
string gameId = null;
if (optsMatch.Success)
{
var parts = optsMatch.Groups[1].Value.Split(':');
if (parts.Length >= 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 = "<HostDataRes Cx=\"0x0\" HR=\"0x0\" />\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<ServerInfo>();
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 = $"<PageRes Cx=\"0x0\" HR=\"0x0\" Cnt=\"1\" Vid=\"101\" VIdx=\"{pageNo}\" VTotal=\"{vTotal}\" >" +
$"<Row A=\"{pageIndex}\" A=\"{server.Name}\" A=\"\" A=\"16777227\" A=\"{gameOpts}\" A=\"{displayIp}\" />" +
"</PageRes>\0";
_logger.Log($"Added server to PageRes: GameId={server.GameId}, Name={server.Name}, IP={displayIp}, GameOpts={gameOpts}, VTotal={vTotal}");
}
else
{
pageRes = $"<PageRes Cx=\"0x0\" HR=\"0x0\" Cnt=\"0\" Vid=\"101\" VIdx=\"{pageNo}\" VTotal=\"{vTotal}\" ></PageRes>\0";
_logger.Log($"No server found for PageNo={pageNo}, INI servers count: {serverList.Count}, VTotal={vTotal}");
}
}
else
{
pageRes = $"<PageRes Cx=\"0x0\" HR=\"0x0\" Cnt=\"0\" Vid=\"101\" VIdx=\"{pageNo}\" VTotal=\"{serverList.Count}\" ></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 <HostData> or <Page> 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.");
}
}
}