From 34e6751d33400ece0fc01a600923c06208e6b517 Mon Sep 17 00:00:00 2001 From: FangkuaiYa <2683748223@qq.com> Date: Sat, 12 Sep 2026 02:33:43 +0800 Subject: [PATCH] Some support and improvements --- marketplace/Empostor.Plugins.PlayerLog.dll | Bin 21504 -> 22528 bytes .../PlayerLogStore.cs | 17 ++++ src/Empostor.Server/Empostor.Server.csproj | 1 + src/Empostor.Server/Http/AdminController.cs | 81 +++++++++++++++++- .../Http/AdminTemplateDefaults.cs | 46 ++++++++-- src/Empostor.Server/Net/Matchmaker.cs | 2 +- .../PublishProfiles/FolderProfile1.pubxml | 21 +++++ src/Empostor.Server/packages.lock.json | 6 ++ .../Next.Hazel/Udp/UdpConnectionListener.cs | 6 +- .../Next.Hazel/Udp/UdpPacketFilter.cs | 2 + 10 files changed, 171 insertions(+), 11 deletions(-) create mode 100644 src/Empostor.Server/Properties/PublishProfiles/FolderProfile1.pubxml diff --git a/marketplace/Empostor.Plugins.PlayerLog.dll b/marketplace/Empostor.Plugins.PlayerLog.dll index 28dfbef3f24e06971e119f48e32952da3c5a9af1..6b5d7f025ad1778e266de96085c6bc30a557e89c 100644 GIT binary patch delta 8695 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 delta 7944 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 public List GetByClient(int clientId) => _entries.Where(e => e.ClientId == clientId).ToList(); + public int GetCount() => _count; + + public int GetCountByClient(int clientId) => + _entries.Count(e => e.ClientId == clientId); + + public List GetPage(int page, int pageSize) + { + var skip = (page - 1) * pageSize; + return _entries.Skip(skip).Take(pageSize).ToList(); + } + + public List GetPageByClient(int clientId, int page, int pageSize) + { + var skip = (page - 1) * pageSize; + return _entries.Where(e => e.ClientId == clientId).Skip(skip).Take(pageSize).ToList(); + } + public List GetLoggedClientIds() => _entries.Where(e => e.ClientId.HasValue).Select(e => e.ClientId!.Value).Distinct().ToList(); diff --git a/src/Empostor.Server/Empostor.Server.csproj b/src/Empostor.Server/Empostor.Server.csproj index 09bd6ff..4f14fcc 100644 --- a/src/Empostor.Server/Empostor.Server.csproj +++ b/src/Empostor.Server/Empostor.Server.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Empostor.Server/Http/AdminController.cs b/src/Empostor.Server/Http/AdminController.cs index 5391bcc..dea29fe 100644 --- a/src/Empostor.Server/Http/AdminController.cs +++ b/src/Empostor.Server/Http/AdminController.cs @@ -48,6 +48,7 @@ namespace Empostor.Server.Http private readonly IpGeolocationService _ipGeo; private readonly AdminExtensionRegistry _extensions; private readonly AdminThemeRegistry _themes; + private readonly PlayerLogStore? _playerLogs; private readonly string _passwordHash; private static readonly ConcurrentDictionary _loginFailures = new(); @@ -63,7 +64,8 @@ namespace Empostor.Server.Http IOptions httpServerConfig, IpGeolocationService ipGeo, AdminExtensionRegistry extensions, - AdminThemeRegistry themes) + AdminThemeRegistry themes, + PlayerLogStore? playerLogs = null) { _logger = logger; _gameManager = gameManager; @@ -77,6 +79,7 @@ namespace Empostor.Server.Http _ipGeo = ipGeo; _extensions = extensions; _themes = themes; + _playerLogs = playerLogs; _passwordHash = ComputeHash(_config.Password); if (string.IsNullOrEmpty(_config.Password) @@ -681,6 +684,82 @@ namespace Empostor.Server.Http }); } + [HttpGet("/api/admin/player/logs/clients")] + public IActionResult GetPlayerLogClients() + { + if (!IsAuthenticated()) return Unauthorized(); + if (_playerLogs == null) return Ok(Array.Empty()); + + var ids = _playerLogs.GetLoggedClientIds(); + var result = ids.Select(id => new + { + id, + name = _playerLogs.GetLatestName(id) ?? $"Player#{id}", + friendCode = _playerLogs.GetLatestFriendCode(id) ?? "—", + }).ToList(); + return Ok(result); + } + + [HttpGet("/api/admin/player/logs")] + public IActionResult GetPlayerLogs([FromQuery] int? clientId, [FromQuery] int page = 1, [FromQuery] int pageSize = 100) + { + if (!IsAuthenticated()) return Unauthorized(); + if (_playerLogs == null) return Ok(new { page = 1, pageSize = 0, total = 0, totalPages = 0, entries = Array.Empty() }); + + pageSize = Math.Clamp(pageSize, 1, 500); + var total = clientId.HasValue ? _playerLogs.GetCountByClient(clientId.Value) : _playerLogs.GetCount(); + var totalPages = (int)Math.Ceiling((double)total / pageSize); + var entries = clientId.HasValue + ? _playerLogs.GetPageByClient(clientId.Value, page, pageSize) + : _playerLogs.GetPage(page, pageSize); + + return Ok(new + { + page, + pageSize, + total, + totalPages, + entries = entries.Select(e => new + { + time = e.Time.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"), + type = e.Type, + playerName = e.PlayerName ?? "—", + friendCode = e.FriendCode ?? "—", + gameCode = e.GameCode ?? "—", + detail = e.Detail ?? "", + }).ToList(), + }); + } + + [HttpGet("/api/admin/player/logs/export")] + public IActionResult ExportPlayerLogs([FromQuery] int? clientId) + { + if (!IsAuthenticated()) return Unauthorized(); + if (_playerLogs == null) return File(Array.Empty(), "application/json", "player_logs.json"); + + var bytes = clientId.HasValue ? _playerLogs.ExportJson(clientId.Value) : _playerLogs.ExportJson(); + return File(bytes, "application/json", "player_logs.json"); + } + + [HttpPost("/api/admin/player/logs/clear")] + public IActionResult ClearPlayerLogs([FromQuery] string? olderThan) + { + if (!IsAuthenticated()) return Unauthorized(); + if (_playerLogs == null) return Ok(new { cleared = true }); + + DateTime? cutoff = olderThan?.ToLowerInvariant() switch + { + "1h" => DateTime.UtcNow.AddHours(-1), + "24h" => DateTime.UtcNow.AddDays(-1), + "7d" => DateTime.UtcNow.AddDays(-7), + "30d" => DateTime.UtcNow.AddDays(-30), + _ => null, + }; + + _playerLogs.Clear(cutoff); + return Ok(new { cleared = true }); + } + private IGame? FindGame(string code) { try { return _gameManager.Find(new GameCode(code.ToUpperInvariant())); } diff --git a/src/Empostor.Server/Http/AdminTemplateDefaults.cs b/src/Empostor.Server/Http/AdminTemplateDefaults.cs index eb5687d..21be5fe 100644 --- a/src/Empostor.Server/Http/AdminTemplateDefaults.cs +++ b/src/Empostor.Server/Http/AdminTemplateDefaults.cs @@ -1150,6 +1150,8 @@ internal static class AdminTemplateDefaults Loading... +
+
@@ -1373,25 +1375,27 @@ internal static class AdminTemplateDefaults
${!data.upToDate ? '

' + _('updates.update_hint', 'A new version is available. Update manually.') + '

' : ''}`; } - async function fPlayerLogs() { + let plPage = 1; + const plPageSize = 100; + + async function fPlayerLogs(page) { + if (page !== undefined) plPage = page; const sel = document.getElementById('pl-client'); const curVal = sel.value; const typeSel = document.getElementById('pl-type'); const curType = typeSel.value; const tb = document.getElementById('pl-t'); - // populate client list if empty if (!sel.hasAttribute('data-loaded')) { try { const { data: cls } = await api('GET', '/api/admin/player/logs/clients'); sel.innerHTML = ''; - cls.forEach(c => { sel.innerHTML += ``; }); + cls.forEach(c => { sel.innerHTML += ``; }); sel.value = curVal; sel.setAttribute('data-loaded', '1'); } catch { } } - // populate type filter if (!typeSel.hasAttribute('data-loaded')) { const types = ['Chat','Report','Murder','Exile','Vote','Task','Vent','Meeting','Connect','Game','Join','Leave']; types.forEach(t => { typeSel.innerHTML += ``; }); @@ -1399,22 +1403,48 @@ internal static class AdminTemplateDefaults typeSel.setAttribute('data-loaded', '1'); } - const url = sel.value ? `/api/admin/player/logs?clientId=${sel.value}` : '/api/admin/player/logs'; - const { data: logs } = await api('GET', url); - let items = logs; + let url = sel.value + ? `/api/admin/player/logs?clientId=${sel.value}&page=${plPage}&pageSize=${plPageSize}` + : `/api/admin/player/logs?page=${plPage}&pageSize=${plPageSize}`; + const { data } = await api('GET', url); + let items = data.entries || []; if (curType) items = items.filter(l => l.type === curType); + if (!items.length) { tb.innerHTML = '' + _('player_logs.no_logs', 'No logs.') + ''; + document.getElementById('pl-pager').innerHTML = ''; return; } + const typeColor = { Chat: 'var(--a)', Report: 'var(--r)', Murder: 'var(--r)', Exile: 'var(--o)', Vote: 'var(--y)', Task: 'var(--g)', Vent: 'var(--p)', Meeting: 'var(--m)', Connect: 'var(--g)', Game: 'var(--m)', Join: 'var(--g)', Leave: 'var(--o)' }; tb.innerHTML = items.map(l => ` ${e(l.time)} ${e(l.type)} - ${l.clientId ? '' + e(l.playerName) + '
' + e(l.friendCode) + '' : '—'} + ${e(l.playerName)}
${e(l.friendCode)} ${l.gameCode && l.gameCode !== '—' ? '' + e(l.gameCode) + '' : '—'} ${e(l.detail)} `).join(''); + + renderPlPager(data.totalPages, data.total); + } + + function renderPlPager(totalPages, total) { + const pg = document.getElementById('pl-pager'); + if (totalPages <= 1) { pg.innerHTML = ''; return; } + let h = `${total} 条`; + if (plPage > 1) h += ``; + const range = 5; + let start = Math.max(1, plPage - range); + let end = Math.min(totalPages, plPage + range); + if (start > 1) h += ``; + if (start > 2) h += `…`; + for (let i = start; i <= end; i++) { + h += ``; + } + if (end < totalPages - 1) h += `…`; + if (end < totalPages) h += ``; + if (plPage < totalPages) h += ``; + pg.innerHTML = h; } function exportLogs() { diff --git a/src/Empostor.Server/Net/Matchmaker.cs b/src/Empostor.Server/Net/Matchmaker.cs index 6dfef99..a4f67df 100644 --- a/src/Empostor.Server/Net/Matchmaker.cs +++ b/src/Empostor.Server/Net/Matchmaker.cs @@ -238,7 +238,7 @@ namespace Empostor.Server.Net // Malformed / unauthenticated UDP handshake. Dispose the // underlying connection so an attacker's socket is closed and // removed from the listener — never leave it half-initialised. - _logger.LogDebug(ex, "Rejecting invalid UDP handshake on port {Port}", port); + _logger.LogWarning(ex, "Rejecting invalid UDP handshake on port {Port}", port); connection?.DisposeInnerConnection(); e.Connection.Dispose(); } diff --git a/src/Empostor.Server/Properties/PublishProfiles/FolderProfile1.pubxml b/src/Empostor.Server/Properties/PublishProfiles/FolderProfile1.pubxml new file mode 100644 index 0000000..b09157b --- /dev/null +++ b/src/Empostor.Server/Properties/PublishProfiles/FolderProfile1.pubxml @@ -0,0 +1,21 @@ + + + + + false + false + true + Release + Any CPU + FileSystem + bin\Release\net8.0\publish\ + FileSystem + <_TargetId>Folder + + net8.0 + linux-x64 + true + 1b0390af-a4f3-4fe4-b093-708b0135c0b3 + false + + \ No newline at end of file diff --git a/src/Empostor.Server/packages.lock.json b/src/Empostor.Server/packages.lock.json index 9d36848..9fb6c34 100644 --- a/src/Empostor.Server/packages.lock.json +++ b/src/Empostor.Server/packages.lock.json @@ -474,6 +474,12 @@ "empostor.api.innersloth.generator": { "type": "Project" }, + "empostor.plugins.playerlog": { + "type": "Project", + "dependencies": { + "Empostor.Api": "[2.0.0-dev, )" + } + }, "NextFast.Hazel": { "type": "Project", "dependencies": { diff --git a/src/NextFast.Hazel-New/Next.Hazel/Udp/UdpConnectionListener.cs b/src/NextFast.Hazel-New/Next.Hazel/Udp/UdpConnectionListener.cs index 47589f6..5fd37cf 100644 --- a/src/NextFast.Hazel-New/Next.Hazel/Udp/UdpConnectionListener.cs +++ b/src/NextFast.Hazel-New/Next.Hazel/Udp/UdpConnectionListener.cs @@ -179,7 +179,11 @@ public class UdpConnectionListener : NetworkConnectionListener } // Check for malformed connection attempts - if (data.Buffer[0] != (byte)UdpSendOption.Hello) return; + if (data.Buffer[0] != (byte)UdpSendOption.Hello) + { + Logger.Debug("Rejected non-Hello packet from {0} (first byte: 0x{1:X2})", data.RemoteEndPoint, data.Buffer[0]); + return; + } // Check rateLimit. if (!_connectionRateLimit.IsAllowed(data.RemoteEndPoint.Address)) diff --git a/src/NextFast.Hazel-New/Next.Hazel/Udp/UdpPacketFilter.cs b/src/NextFast.Hazel-New/Next.Hazel/Udp/UdpPacketFilter.cs index a5dda1c..8fed83b 100644 --- a/src/NextFast.Hazel-New/Next.Hazel/Udp/UdpPacketFilter.cs +++ b/src/NextFast.Hazel-New/Next.Hazel/Udp/UdpPacketFilter.cs @@ -61,12 +61,14 @@ public sealed class UdpPacketFilter : IDisposable // 1. Size check (cheapest, no state). if (length < MinPacketLength || length > MaxPacketLength) { + Logger.Debug("UdpPacketFilter dropped packet from {Ip}: length {Length} outside [{Min}, {Max}]", ip, length, MinPacketLength, MaxPacketLength); return true; } // 2. Send-option byte check (layer-7, no state). if (!_validOption[buffer[0]]) { + Logger.Debug("UdpPacketFilter dropped packet from {Ip}: invalid first byte 0x{Byte:X2}", ip, buffer[0]); return true; }