Init
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/.vs
|
||||||
|
/obj
|
||||||
12
Configuration.cs
Normal file
12
Configuration.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
59
Controllers/MonitoringController.cs
Normal file
59
Controllers/MonitoringController.cs
Normal file
@@ -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<IEnumerable<MonitoringInfo>> GetMonitoringData()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var monitoringData = _dbManager.GetCollection<MonitoringInfo>("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<MonitoringInfo> GetMonitoringDataByGameId(string gameId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var monitoringData = _dbManager.GetCollection<MonitoringInfo>("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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
DictionaryExtensions.cs
Normal file
13
DictionaryExtensions.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// DictionaryExtensions.cs
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace FreelancerListServer
|
||||||
|
{
|
||||||
|
public static class DictionaryExtensions
|
||||||
|
{
|
||||||
|
public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue)
|
||||||
|
{
|
||||||
|
return dictionary.TryGetValue(key, out TValue value) ? value : defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
FLListServer.csproj
Normal file
16
FLListServer.csproj
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="LiteDB" Version="5.0.21" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
22
FLListServer.sln
Normal file
22
FLListServer.sln
Normal file
@@ -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
|
||||||
643
FreelancerListServer.cs
Normal file
643
FreelancerListServer.cs
Normal file
@@ -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<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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
LiteDB.Studio.exe
Normal file
BIN
LiteDB.Studio.exe
Normal file
Binary file not shown.
405
LiteDbManager.cs
Normal file
405
LiteDbManager.cs
Normal file
@@ -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<ServerInfo> _servers;
|
||||||
|
private readonly ILiteCollection<MonitoringInfo> _monitoring;
|
||||||
|
private readonly ILiteCollection<Configuration> _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<ServerInfo>("servers");
|
||||||
|
_monitoring = _db.GetCollection<MonitoringInfo>("monitoring");
|
||||||
|
_configurations = _db.GetCollection<Configuration>("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<string, string>
|
||||||
|
{
|
||||||
|
{ "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<string, Dictionary<string, string>> GetServers()
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, Dictionary<string, string>>();
|
||||||
|
foreach (var server in _servers.FindAll())
|
||||||
|
{
|
||||||
|
var dict = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["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<string, Dictionary<string, string>> _sections;
|
||||||
|
private readonly object _lock = new object();
|
||||||
|
|
||||||
|
public IniFile(string path)
|
||||||
|
{
|
||||||
|
_path = path;
|
||||||
|
_sections = new Dictionary<string, Dictionary<string, string>>();
|
||||||
|
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<string, string>();
|
||||||
|
}
|
||||||
|
else if (currentSection != null && trimmed.Contains("="))
|
||||||
|
{
|
||||||
|
var parts = trimmed.Split('=', 2);
|
||||||
|
_sections[currentSection][parts[0].Trim()] = parts[1].Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, Dictionary<string, string>> GetServers()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
return new Dictionary<string, Dictionary<string, string>>(_sections);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MonitoringIniFile
|
||||||
|
{
|
||||||
|
private readonly string _path;
|
||||||
|
private readonly Dictionary<string, Dictionary<string, string>> _sections;
|
||||||
|
private readonly object _lock = new object();
|
||||||
|
|
||||||
|
public MonitoringIniFile(string path)
|
||||||
|
{
|
||||||
|
_path = path;
|
||||||
|
_sections = new Dictionary<string, Dictionary<string, string>>();
|
||||||
|
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<string, string>();
|
||||||
|
}
|
||||||
|
else if (currentSection != null && trimmed.Contains("="))
|
||||||
|
{
|
||||||
|
var parts = trimmed.Split('=', 2);
|
||||||
|
_sections[currentSection][parts[0].Trim()] = parts[1].Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, Dictionary<string, string>> GetServers()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
return new Dictionary<string, Dictionary<string, string>>(_sections);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public ILiteCollection<T> GetCollection<T>(string collectionName)
|
||||||
|
{
|
||||||
|
return _db.GetCollection<T>(collectionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Logger.cs
Normal file
43
Logger.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
MonitoringInfo.cs
Normal file
16
MonitoringInfo.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
41
Program.cs
Normal file
41
Program.cs
Normal file
@@ -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<Startup>();
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
ServerInfo.cs
Normal file
22
ServerInfo.cs
Normal file
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
41
Startup.cs
Normal file
41
Startup.cs
Normal file
@@ -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<LiteDbManager>();
|
||||||
|
services.AddSingleton<Logger>();
|
||||||
|
// 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user