commit b9d7b1c9f72eb6690387d8aeafcda7e2b2b41a00 Author: Nekura <78934567+Nekura@users.noreply.github.com> Date: Fri Jul 25 18:39:48 2025 +0200 Init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..06a280c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.vs +/obj diff --git a/Configuration.cs b/Configuration.cs new file mode 100644 index 0000000..0f05ea8 --- /dev/null +++ b/Configuration.cs @@ -0,0 +1,12 @@ +// Configuration.cs +using LiteDB; + +namespace FreelancerListServer +{ + public class Configuration + { + [BsonId] // Primärschlüssel in LiteDB + public string Key { get; set; } + public string Value { get; set; } + } +} \ No newline at end of file diff --git a/Controllers/MonitoringController.cs b/Controllers/MonitoringController.cs new file mode 100644 index 0000000..8dc4e96 --- /dev/null +++ b/Controllers/MonitoringController.cs @@ -0,0 +1,59 @@ +// MonitoringController.cs +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace FreelancerListServer.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class MonitoringController : ControllerBase + { + private readonly LiteDbManager _dbManager; + private readonly Logger _logger; + + public MonitoringController(LiteDbManager dbManager, Logger logger) + { + _dbManager = dbManager; + _logger = logger; + } + + [HttpGet] + public ActionResult> GetMonitoringData() + { + try + { + var monitoringData = _dbManager.GetCollection("monitoring").FindAll().ToList(); + _logger.Log($"API: Retrieved {monitoringData.Count} monitoring entries."); + return Ok(monitoringData); + } + catch (Exception ex) + { + _logger.Log($"API: Error retrieving monitoring data: {ex.Message}"); + return StatusCode(500, new { error = "Internal server error" }); + } + } + + [HttpGet("{gameId}")] + public ActionResult GetMonitoringDataByGameId(string gameId) + { + try + { + var monitoringData = _dbManager.GetCollection("monitoring").FindOne(x => x.GameId == gameId); + if (monitoringData == null) + { + _logger.Log($"API: Monitoring data for GameId={gameId} not found."); + return NotFound(new { error = $"Monitoring data for GameId={gameId} not found" }); + } + _logger.Log($"API: Retrieved monitoring data for GameId={gameId}."); + return Ok(monitoringData); + } + catch (Exception ex) + { + _logger.Log($"API: Error retrieving monitoring data for GameId={gameId}: {ex.Message}"); + return StatusCode(500, new { error = "Internal server error" }); + } + } + } +} \ No newline at end of file diff --git a/DictionaryExtensions.cs b/DictionaryExtensions.cs new file mode 100644 index 0000000..400c2db --- /dev/null +++ b/DictionaryExtensions.cs @@ -0,0 +1,13 @@ +// DictionaryExtensions.cs +using System.Collections.Generic; + +namespace FreelancerListServer +{ + public static class DictionaryExtensions + { + public static TValue GetValueOrDefault(this IDictionary dictionary, TKey key, TValue defaultValue) + { + return dictionary.TryGetValue(key, out TValue value) ? value : defaultValue; + } + } +} \ No newline at end of file diff --git a/FLListServer.csproj b/FLListServer.csproj new file mode 100644 index 0000000..e120818 --- /dev/null +++ b/FLListServer.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + diff --git a/FLListServer.sln b/FLListServer.sln new file mode 100644 index 0000000..40d3da8 --- /dev/null +++ b/FLListServer.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35514.174 d17.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FLListServer", "FLListServer.csproj", "{46A4A6ED-911B-40FE-8EB1-E1BEBDE0B550}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {46A4A6ED-911B-40FE-8EB1-E1BEBDE0B550}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {46A4A6ED-911B-40FE-8EB1-E1BEBDE0B550}.Debug|Any CPU.Build.0 = Debug|Any CPU + {46A4A6ED-911B-40FE-8EB1-E1BEBDE0B550}.Release|Any CPU.ActiveCfg = Release|Any CPU + {46A4A6ED-911B-40FE-8EB1-E1BEBDE0B550}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/FreelancerListServer.cs b/FreelancerListServer.cs new file mode 100644 index 0000000..a73825e --- /dev/null +++ b/FreelancerListServer.cs @@ -0,0 +1,643 @@ +// 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."); + } + } +} \ No newline at end of file diff --git a/LiteDB.Studio.exe b/LiteDB.Studio.exe new file mode 100644 index 0000000..c3b23d5 Binary files /dev/null and b/LiteDB.Studio.exe differ diff --git a/LiteDbManager.cs b/LiteDbManager.cs new file mode 100644 index 0000000..d2b7f46 --- /dev/null +++ b/LiteDbManager.cs @@ -0,0 +1,405 @@ +// LiteDbManager.cs +using LiteDB; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace FreelancerListServer +{ + public class LiteDbManager : IDisposable + { + private readonly LiteDatabase _db; + private readonly ILiteCollection _servers; + private readonly ILiteCollection _monitoring; + private readonly ILiteCollection _configurations; + private readonly Logger _logger; + + public LiteDbManager(string dbPath = "freelancer.db") + { + // Shared Mode + var connectionString = new ConnectionString(dbPath) + { + Connection = ConnectionType.Shared + }; + _db = new LiteDatabase(connectionString); + _servers = _db.GetCollection("servers"); + _monitoring = _db.GetCollection("monitoring"); + _configurations = _db.GetCollection("configurations"); + _logger = new Logger(); + + // Indizes für schnellere Abfragen + _servers.EnsureIndex(x => x.GameId); + _monitoring.EnsureIndex(x => x.GameId); + _configurations.EnsureIndex(x => x.Key); + + // Standard-Konfigurationswerte setzen + InitializeDefaultConfigurations(); + } + + private void InitializeDefaultConfigurations() + { + var defaultConfigs = new Dictionary + { + { "LocalServerIp", "192.168.200.10" }, + { "PublicServerIp", "195.201.106.232" } + }; + + foreach (var config in defaultConfigs) + { + if (_configurations.FindOne(x => x.Key == config.Key) == null) + { + _configurations.Insert(new Configuration + { + Key = config.Key, + Value = config.Value + }); + _logger.Log($"Initialized default configuration: {config.Key} = {config.Value}"); + } + } + } + + public string GetConfiguration(string key, string defaultValue = null) + { + var config = _configurations.FindOne(x => x.Key == key); + if (config == null) + { + _logger.Log($"Configuration key {key} not found, returning default: {defaultValue}"); + return defaultValue; + } + return config.Value; + } + + public void SetConfiguration(string key, string value) + { + var config = _configurations.FindOne(x => x.Key == key); + if (config != null) + { + config.Value = value; + _configurations.Update(config); + _logger.Log($"Updated configuration: {key} = {value}"); + } + else + { + _configurations.Insert(new Configuration + { + Key = key, + Value = value + }); + _logger.Log($"Inserted new configuration: {key} = {value}"); + } + } + + public void UpdateServer(string gameId, ServerInfo server) + { + var existingServer = _servers.FindOne(x => x.GameId == gameId); + if (existingServer != null) + { + existingServer.Name = ConvertNameToHtmlEntities(server.Name ?? "Unknown"); + existingServer.IP = server.IP; + existingServer.GamePort = server.GamePort; + existingServer.ClientPort = server.ClientPort; + existingServer.MaxPlayers = server.MaxPlayers ?? "0"; + existingServer.Pass = server.Pass ?? "0"; + existingServer.PvP = server.PvP ?? "0"; + existingServer.GameOpts = server.GameOpts ?? ""; + existingServer.Active = server.Active; + existingServer.LastSeen = server.LastSeen; + existingServer.Id = server.Id; + _servers.Update(existingServer); + } + else + { + server.GameId = gameId; + server.Name = ConvertNameToHtmlEntities(server.Name ?? "Unknown"); + _servers.Insert(server); + } + _logger.Log($"Updated server in LiteDB: GameId={gameId}, Name={server.Name}, IP={server.IP}, GamePort={server.GamePort}"); + } + + public void UpdateMonitoring(string gameId, string session, string maxPlayers, string players, string instanceGuid, string name) + { + var monitoringInfo = _monitoring.FindOne(x => x.GameId == gameId); + if (monitoringInfo != null) + { + monitoringInfo.Session = session; + monitoringInfo.MaxPlayers = maxPlayers; + monitoringInfo.Players = players; + monitoringInfo.InstanceGuid = instanceGuid; + monitoringInfo.Name = name; + _monitoring.Update(monitoringInfo); + } + else + { + _monitoring.Insert(new MonitoringInfo + { + GameId = gameId, + Session = session, + MaxPlayers = maxPlayers, + Players = players, + InstanceGuid = instanceGuid, + Name = name + }); + } + _logger.Log($"Updated monitoring in LiteDB: GameId={gameId}, Players={players}/{maxPlayers}, Name={name}"); + } + + public Dictionary> GetServers() + { + var result = new Dictionary>(); + foreach (var server in _servers.FindAll()) + { + var dict = new Dictionary + { + ["GameId"] = server.GameId, + ["Name"] = server.Name, + ["IP"] = server.IP, + ["GamePort"] = server.GamePort.ToString(), + ["MaxPlayers"] = server.MaxPlayers, + ["Pass"] = server.Pass, + ["PvP"] = server.PvP, + ["GameOpts"] = server.GameOpts + }; + result[$"Server_{server.GameId}"] = dict; + } + return result; + } + + public void MigrateFromIni(string serversIniPath, string monitoringIniPath, bool updateExisting = false) + { + int serversImported = 0; + int serversSkipped = 0; + int monitoringImported = 0; + int monitoringSkipped = 0; + + // Migration für servers.ini + if (File.Exists(serversIniPath)) + { + var iniFile = new IniFile(serversIniPath); + var servers = iniFile.GetServers(); + foreach (var section in servers) + { + var serverData = section.Value; + if (serverData.TryGetValue("GameId", out var gameId) && !string.IsNullOrEmpty(gameId)) + { + // Prüfen, ob der Server bereits in der Datenbank existiert + if (_servers.FindOne(x => x.GameId == gameId) != null) + { + if (updateExisting) + { + // Bestehenden Server aktualisieren + 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 + }; + UpdateServer(gameId, serverInfo); + serversImported++; + _logger.Log($"Updated existing server from {serversIniPath}: GameId={gameId}"); + } + else + { + serversSkipped++; + _logger.Log($"Skipped existing server from {serversIniPath}: GameId={gameId}"); + } + } + else + { + // Neuen Server importieren + 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 + }; + UpdateServer(gameId, serverInfo); + serversImported++; + _logger.Log($"Imported new server from {serversIniPath}: GameId={gameId}"); + } + } + } + _logger.Log($"Migrated {serversImported} servers, skipped {serversSkipped} existing servers from {serversIniPath} to LiteDB."); + } + + // Migration für monitoring.ini + if (File.Exists(monitoringIniPath)) + { + var monitoringIni = new MonitoringIniFile(monitoringIniPath); + var monitoringData = monitoringIni.GetServers(); + foreach (var section in monitoringData) + { + var data = section.Value; + if (data.TryGetValue("GameId", out var gameId) && !string.IsNullOrEmpty(gameId)) + { + // Prüfen, ob der Monitoring-Eintrag bereits existiert + if (_monitoring.FindOne(x => x.GameId == gameId) != null) + { + if (updateExisting) + { + // Bestehenden Monitoring-Eintrag aktualisieren + UpdateMonitoring( + gameId, + data.GetValueOrDefault("Session", "unknown"), + data.GetValueOrDefault("MaxPlayers", "0"), + data.GetValueOrDefault("Players", "0"), + data.GetValueOrDefault("InstanceGuid", "unknown"), + data.GetValueOrDefault("Name", "Unknown") + ); + monitoringImported++; + _logger.Log($"Updated existing monitoring entry from {monitoringIniPath}: GameId={gameId}"); + } + else + { + monitoringSkipped++; + _logger.Log($"Skipped existing monitoring entry from {monitoringIniPath}: GameId={gameId}"); + } + } + else + { + // Neuen Monitoring-Eintrag importieren + UpdateMonitoring( + gameId, + data.GetValueOrDefault("Session", "unknown"), + data.GetValueOrDefault("MaxPlayers", "0"), + data.GetValueOrDefault("Players", "0"), + data.GetValueOrDefault("InstanceGuid", "unknown"), + data.GetValueOrDefault("Name", "Unknown") + ); + monitoringImported++; + _logger.Log($"Imported new monitoring entry from {monitoringIniPath}: GameId={gameId}"); + } + } + } + _logger.Log($"Migrated {monitoringImported} monitoring entries, skipped {monitoringSkipped} existing entries from {monitoringIniPath} to LiteDB."); + } + } + + private string ConvertNameToHtmlEntities(string name) + { + if (string.IsNullOrEmpty(name)) return "Unknown"; + return name.Replace(">", ">").Replace("<", "<"); + } + + public void Dispose() + { + _db?.Dispose(); + _logger.Log("LiteDB connection disposed."); + } + + // Temporäre INI-Klassen für die Migration + private class IniFile + { + private readonly string _path; + private readonly Dictionary> _sections; + private readonly object _lock = new object(); + + public IniFile(string path) + { + _path = path; + _sections = new Dictionary>(); + Load(); + } + + private void Load() + { + lock (_lock) + { + if (!File.Exists(_path)) return; + + string currentSection = null; + foreach (var line in File.ReadAllLines(_path)) + { + var trimmed = line.Trim(); + if (string.IsNullOrEmpty(trimmed) || trimmed.StartsWith(";")) continue; + + if (trimmed.StartsWith("[") && trimmed.EndsWith("]")) + { + currentSection = trimmed.Substring(1, trimmed.Length - 2); + _sections[currentSection] = new Dictionary(); + } + else if (currentSection != null && trimmed.Contains("=")) + { + var parts = trimmed.Split('=', 2); + _sections[currentSection][parts[0].Trim()] = parts[1].Trim(); + } + } + } + } + + public Dictionary> GetServers() + { + lock (_lock) + { + return new Dictionary>(_sections); + } + } + } + + private class MonitoringIniFile + { + private readonly string _path; + private readonly Dictionary> _sections; + private readonly object _lock = new object(); + + public MonitoringIniFile(string path) + { + _path = path; + _sections = new Dictionary>(); + Load(); + } + + private void Load() + { + lock (_lock) + { + if (!File.Exists(_path)) return; + + string currentSection = null; + foreach (var line in File.ReadAllLines(_path)) + { + var trimmed = line.Trim(); + if (string.IsNullOrEmpty(trimmed) || trimmed.StartsWith(";")) continue; + + if (trimmed.StartsWith("[") && trimmed.EndsWith("]")) + { + currentSection = trimmed.Substring(1, trimmed.Length - 2); + _sections[currentSection] = new Dictionary(); + } + else if (currentSection != null && trimmed.Contains("=")) + { + var parts = trimmed.Split('=', 2); + _sections[currentSection][parts[0].Trim()] = parts[1].Trim(); + } + } + } + } + + public Dictionary> GetServers() + { + lock (_lock) + { + return new Dictionary>(_sections); + } + } + } + public ILiteCollection GetCollection(string collectionName) + { + return _db.GetCollection(collectionName); + } + } +} \ No newline at end of file diff --git a/Logger.cs b/Logger.cs new file mode 100644 index 0000000..62defd7 --- /dev/null +++ b/Logger.cs @@ -0,0 +1,43 @@ +// Logger.cs +using System; +using System.IO; + +namespace FreelancerListServer +{ + public class Logger + { + private readonly string _logDirectory; + private string _currentLogFile; + private DateTime _nextRotation; + private readonly object _lock = new object(); + + public Logger(string logDirectory = "logs") + { + _logDirectory = logDirectory; + Directory.CreateDirectory(_logDirectory); + UpdateLogFileName(); + } + + private void UpdateLogFileName() + { + var now = DateTime.Now; + _currentLogFile = Path.Combine(_logDirectory, $"server_{now:yyyyMMdd_HH}.log"); + _nextRotation = now.AddHours(1).Date.AddHours(now.Hour + 1); + } + + public void Log(string message) + { + lock (_lock) + { + if (DateTime.Now >= _nextRotation) + { + UpdateLogFileName(); + } + + string logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {message}"; + Console.WriteLine(logEntry); + File.AppendAllText(_currentLogFile, logEntry + Environment.NewLine); + } + } + } +} \ No newline at end of file diff --git a/MonitoringInfo.cs b/MonitoringInfo.cs new file mode 100644 index 0000000..1f86c33 --- /dev/null +++ b/MonitoringInfo.cs @@ -0,0 +1,16 @@ +// MonitoringInfo.cs +using LiteDB; + +namespace FreelancerListServer +{ + public class MonitoringInfo + { + [BsonId] // Primärschlüssel in LiteDB + public string GameId { get; set; } + public string Session { get; set; } + public string MaxPlayers { get; set; } + public string Players { get; set; } + public string InstanceGuid { get; set; } + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..c481d74 --- /dev/null +++ b/Program.cs @@ -0,0 +1,41 @@ +// Program.cs +// dotnet publish -c Release -r linux-x64 --self-contained true -o ./publish + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; +using System; +using System.Threading.Tasks; + +namespace FreelancerListServer +{ + class Program + { + static async Task Main(string[] args) + { + try + { + // Starte den HTTP-Server + var hostTask = Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + webBuilder.UseUrls("http://0.0.0.0:5000"); + }) + .Build() + .RunAsync(); + + // Starte den UDP-Server + using var server = new FreelancerListServer(2300, "0.0.0.0"); + await server.StartAsync(); + + // Warte auf den HTTP-Server + await hostTask; + } + catch (Exception ex) + { + var logger = new Logger(); + logger.Log($"Server error: {ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/ServerInfo.cs b/ServerInfo.cs new file mode 100644 index 0000000..95c672b --- /dev/null +++ b/ServerInfo.cs @@ -0,0 +1,22 @@ +// ServerInfo.cs +using LiteDB; + +namespace FreelancerListServer +{ + public class ServerInfo + { + [BsonId] // Primärschlüssel in LiteDB + public string GameId { get; set; } + public string Id { get; set; } + public string Name { get; set; } + public string IP { get; set; } + public int ClientPort { get; set; } + public int GamePort { get; set; } + public bool Active { get; set; } + public DateTime LastSeen { get; set; } + public string MaxPlayers { get; set; } + public string Pass { get; set; } + public string PvP { get; set; } + public string GameOpts { get; set; } + } +} \ No newline at end of file diff --git a/Startup.cs b/Startup.cs new file mode 100644 index 0000000..6d57a66 --- /dev/null +++ b/Startup.cs @@ -0,0 +1,41 @@ +// Startup.cs +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace FreelancerListServer +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers().AddNewtonsoftJson(); // Für JSON-Serialisierung + services.AddSingleton(); + services.AddSingleton(); + // Optional: CORS aktivieren + services.AddCors(options => + { + options.AddPolicy("AllowAll", builder => + { + builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); + }); + }); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + app.UseCors("AllowAll"); // Optional: CORS aktivieren + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} \ No newline at end of file