diff --git a/src/Empostor.Api/Config/AuthApiConfig.cs b/src/Empostor.Api/Config/AuthApiConfig.cs index bb0c0ee..61c1d64 100644 --- a/src/Empostor.Api/Config/AuthApiConfig.cs +++ b/src/Empostor.Api/Config/AuthApiConfig.cs @@ -15,13 +15,13 @@ public class AuthApiConfig public AuthApiMode Mode { get; set; } = AuthApiMode.Innersloth; - public string NikoApiKey { get; set; } = ""; + public string NikoApiKey { get; set; } = string.Empty; public string NikoApiBaseUrl { get; set; } = "https://au-verify.niko233.top"; public string RelayApiBaseUrl { get; set; } = "http://localhost:5100"; - public string RelayApiKey { get; set; } = ""; + public string RelayApiKey { get; set; } = string.Empty; public string UmeApiBaseUrl { get; set; } = "https://auverify.hayashiume.top"; diff --git a/src/Empostor.Api/Innersloth/TextBox.cs b/src/Empostor.Api/Innersloth/TextBox.cs index 5416caf..13a5d0f 100644 --- a/src/Empostor.Api/Innersloth/TextBox.cs +++ b/src/Empostor.Api/Innersloth/TextBox.cs @@ -1,4 +1,4 @@ -namespace Empostor.Api.Innersloth +namespace Empostor.Api.Innersloth { public static class TextBox { diff --git a/src/Empostor.Api/Utils/IModuleTagRegistry.cs b/src/Empostor.Api/Utils/IModuleTagRegistry.cs new file mode 100644 index 0000000..f12a54c --- /dev/null +++ b/src/Empostor.Api/Utils/IModuleTagRegistry.cs @@ -0,0 +1,24 @@ +namespace Empostor.Api.Utils; + +/// +/// Allows plugins to register a custom module tag for coloured +/// console output. Inject via DI and call +/// during plugin construction. +/// +public interface IModuleTagRegistry +{ + /// + /// Register a namespace → tag mapping. + /// + /// + /// Full or partial namespace prefix, e.g. "Empostor.Plugin.MyMod". + /// + /// + /// Short tag shown in console brackets, e.g. "MyMod". + /// + /// + /// Optional ANSI SGR colour, e.g. "\x1b[92m" for bright green. + /// Omit for auto-assignment from the palette. + /// + void Register(string namespacePrefix, string tag, string? ansiColor = null); +} diff --git a/src/Empostor.Plugin.Chat/ChatPlugin.cs b/src/Empostor.Plugin.Chat/ChatPlugin.cs index a81905b..0d74aaa 100644 --- a/src/Empostor.Plugin.Chat/ChatPlugin.cs +++ b/src/Empostor.Plugin.Chat/ChatPlugin.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Empostor.Api.Plugins; +using Empostor.Api.Utils; using Microsoft.Extensions.Logging; namespace Empostor.Plugin.Chat; @@ -9,14 +10,15 @@ public sealed class ChatPlugin : PluginBase { private readonly ILogger _logger; - public ChatPlugin(ILogger logger) + public ChatPlugin(ILogger logger, IModuleTagRegistry tags) { _logger = logger; + tags.Register("Empostor.Plugin.Chat", "Chat", "\x1b[92m"); } public override ValueTask EnableAsync() { - _logger.LogInformation("[Chat] Enabled — limiting message length and logging chat."); + _logger.LogInformation("Chat plugin enabled — logging chat and limiting message length."); return default; } diff --git a/src/Empostor.Plugin.Chat/ChatService.cs b/src/Empostor.Plugin.Chat/ChatService.cs index 6d944bf..5712cf5 100644 --- a/src/Empostor.Plugin.Chat/ChatService.cs +++ b/src/Empostor.Plugin.Chat/ChatService.cs @@ -1,7 +1,6 @@ using System; using System.IO; using System.Text.Json; -using Empostor.Api; using Empostor.Api.Events.Player; using Microsoft.Extensions.Logging; @@ -22,33 +21,26 @@ public sealed class ChatService public void HandleChatMessage(IPlayerChatEvent e) { - var client = e.ClientPlayer.Client; - var clientId = client.Id; - var playerName = client.Name; + var playerName = e.ClientPlayer.Client.Name; var slot = e.ClientPlayer.Character?.PlayerId ?? 0; - var gameCode = Api.Innersloth.GameCodeParser.IntToGameName(e.Game.Code); - int channel; string channelName; if (e.IsCancelled) { - channel = 0; channelName = "Canceled"; } else if (!e.SendToAllPlayers) { - channel = 255; channelName = "Command"; } else { - channel = -1; channelName = "Public"; } _logger.LogInformation( - "{Code} - [{ClientId}][{Name}][{Slot}] => [{Channel}]{ChannelName}: [{Message}]", - gameCode, clientId, playerName, slot, channel, channelName, e.Message); + "✉ {Name} [{Slot}] → {ChannelName}: {Message}", + playerName, slot, channelName, e.Message); var isHost = e.ClientPlayer.IsHost; var maxLength = isHost ? _config.HostMaxMessageLength : _config.PlayerMaxMessageLength; @@ -56,7 +48,7 @@ public sealed class ChatService if (e.Message.Length > maxLength) { _logger.LogWarning( - "Cancelling chat message from {PlayerName} ({PlayerType}) of {Length} chars (max: {MaxLength}): too long", + "✉ {PlayerName} | {PlayerType} | blocked: {Length}/{MaxLength} chars", playerName, isHost ? "host" : "player", e.Message.Length, maxLength); e.PlayerControl.SendChatToPlayerAsync(_config.TooLongMessage, e.PlayerControl); diff --git a/src/Empostor.Plugins.FixedCode/FixedCodePlugin.cs b/src/Empostor.Plugins.FixedCode/FixedCodePlugin.cs index b230a9c..e33081d 100644 --- a/src/Empostor.Plugins.FixedCode/FixedCodePlugin.cs +++ b/src/Empostor.Plugins.FixedCode/FixedCodePlugin.cs @@ -12,7 +12,7 @@ public sealed class FixedCodePlugin : PluginBase public FixedCodePlugin(ILogger logger, FixedCodeListener listener) { - _logger = logger; + _logger = logger; _listener = listener; } diff --git a/src/Empostor.Plugins.FriendCodeValidator/FriendCodeValidationListener.cs b/src/Empostor.Plugins.FriendCodeValidator/FriendCodeValidationListener.cs index 5bf0109..cfaeed7 100644 --- a/src/Empostor.Plugins.FriendCodeValidator/FriendCodeValidationListener.cs +++ b/src/Empostor.Plugins.FriendCodeValidator/FriendCodeValidationListener.cs @@ -15,7 +15,7 @@ namespace Empostor.Plugins.FriendCodeValidator private static readonly Regex Pattern = new(@"^([a-zA-Z]+)#(\d{4})$", RegexOptions.Compiled); - private static readonly HashSet WordsA = new(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet WordsA = new(StringComparer.OrdinalIgnoreCase) { "abiding", "abiotic", @@ -2420,7 +2420,7 @@ namespace Empostor.Plugins.FriendCodeValidator "zonal", }; - private static readonly HashSet WordsB = new(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet WordsB = new(StringComparer.OrdinalIgnoreCase) { "ability", "absence", @@ -5402,7 +5402,7 @@ namespace Empostor.Plugins.FriendCodeValidator // Try every split point: left+right must be (A,B) or (B,A) for (var i = 1; i < name.Length; i++) { - var left = name[..i]; + var left = name[..i]; var right = name[i..]; if ((WordsA.Contains(left) && WordsB.Contains(right)) || (WordsB.Contains(left) && WordsA.Contains(right))) diff --git a/src/Empostor.Plugins.Titles/Service/TitleEventListener.cs b/src/Empostor.Plugins.Titles/Service/TitleEventListener.cs index 1682f39..ae59566 100644 --- a/src/Empostor.Plugins.Titles/Service/TitleEventListener.cs +++ b/src/Empostor.Plugins.Titles/Service/TitleEventListener.cs @@ -14,7 +14,7 @@ public sealed class TitleEventListener : IEventListener public TitleEventListener(ILogger logger, TitleStore store) { _logger = logger; - _store = store; + _store = store; } [EventListener] diff --git a/src/Empostor.Plugins.Welcome/Service/WelcomeMessageService.cs b/src/Empostor.Plugins.Welcome/Service/WelcomeMessageService.cs index 3efb674..4d59055 100644 --- a/src/Empostor.Plugins.Welcome/Service/WelcomeMessageService.cs +++ b/src/Empostor.Plugins.Welcome/Service/WelcomeMessageService.cs @@ -51,7 +51,7 @@ public sealed class WelcomeMessageService if (colon < 1) continue; var lang = line[..colon].Trim(); - var msg = line[(colon + 1)..].Trim(); + var msg = line[(colon + 1)..].Trim(); if (!string.IsNullOrEmpty(msg)) _templates[lang] = msg; } @@ -71,7 +71,7 @@ public sealed class WelcomeMessageService return template .Replace("{Name}", name, StringComparison.OrdinalIgnoreCase) .Replace("{FriendCode}", friendCode ?? "None", StringComparison.OrdinalIgnoreCase) - .Replace("{GameCode}", gameCode, StringComparison.OrdinalIgnoreCase); + .Replace("{GameCode}", gameCode, StringComparison.OrdinalIgnoreCase); } private const string DefaultContent = """ diff --git a/src/Empostor.Server/Commands/CommandService.cs b/src/Empostor.Server/Commands/CommandService.cs index 83abcd1..63ed2e4 100644 --- a/src/Empostor.Server/Commands/CommandService.cs +++ b/src/Empostor.Server/Commands/CommandService.cs @@ -45,7 +45,9 @@ public sealed class CommandService public async ValueTask TryHandleAsync(CommandContext ctx) { if (!_commands.TryGetValue(ctx.Name, out var command)) + { return false; + } try { diff --git a/src/Empostor.Server/Commands/Commands/MaxCommand.cs b/src/Empostor.Server/Commands/Commands/MaxCommand.cs index 8b05992..e9809e2 100644 --- a/src/Empostor.Server/Commands/Commands/MaxCommand.cs +++ b/src/Empostor.Server/Commands/Commands/MaxCommand.cs @@ -32,7 +32,9 @@ public sealed class MaxCommand : ICommand var msg = ctx.GetString("command.max.set").Format(count).Get(); if (count > 15) + { msg += "\n" + ctx.GetString("command.max.warning"); + } await ctx.PlayerControl.SendChatToPlayerAsync(msg, ctx.PlayerControl); return true; diff --git a/src/Empostor.Server/Http/AdminController.cs b/src/Empostor.Server/Http/AdminController.cs index 7f7b02d..1f702bf 100644 --- a/src/Empostor.Server/Http/AdminController.cs +++ b/src/Empostor.Server/Http/AdminController.cs @@ -827,7 +827,7 @@ namespace Empostor.Server.Http friendCode = c.FriendCode ?? "—", gameVersion = c.GameVersion.ToString(), platform = c.PlatformSpecificData?.Platform.ToString() ?? "Unknown", - platformName = c.PlatformSpecificData?.PlatformName ?? "", + platformName = c.PlatformSpecificData?.PlatformName ?? string.Empty, language = LanguageHelper.GetDisplayName(c.Language), level = level is null or 0 or uint.MaxValue ? (int?)null : (int)level.Value, inGame = c.Player != null, diff --git a/src/Empostor.Server/Http/AdminTemplateDefaults.cs b/src/Empostor.Server/Http/AdminTemplateDefaults.cs index 136c278..b1919c9 100644 --- a/src/Empostor.Server/Http/AdminTemplateDefaults.cs +++ b/src/Empostor.Server/Http/AdminTemplateDefaults.cs @@ -18,9 +18,14 @@ internal static class AdminTemplateDefaults var loginPath = Path.Combine(PagesDir, "login.html"); var adminPath = Path.Combine(PagesDir, "admin.html"); if (!File.Exists(loginPath)) + { File.WriteAllText(loginPath, LoginHtml); + } + if (!File.Exists(adminPath)) + { File.WriteAllText(adminPath, AdminHtml); + } } catch { diff --git a/src/Empostor.Server/Http/MonitorController.cs b/src/Empostor.Server/Http/MonitorController.cs index 3b401d3..a846f60 100644 --- a/src/Empostor.Server/Http/MonitorController.cs +++ b/src/Empostor.Server/Http/MonitorController.cs @@ -85,25 +85,25 @@ public sealed class MonitorController : ControllerBase public sealed class MonitorStatus { [JsonPropertyName("status")] - public string Status { get; set; } = ""; + public string Status { get; set; } = string.Empty; [JsonPropertyName("timestamp")] - public string Timestamp { get; set; } = ""; + public string Timestamp { get; set; } = string.Empty; [JsonPropertyName("uptime_seconds")] public long UptimeSeconds { get; set; } [JsonPropertyName("uptime_display")] - public string UptimeDisplay { get; set; } = ""; + public string UptimeDisplay { get; set; } = string.Empty; [JsonPropertyName("version")] - public string Version { get; set; } = ""; + public string Version { get; set; } = string.Empty; [JsonPropertyName("runtime")] - public string Runtime { get; set; } = ""; + public string Runtime { get; set; } = string.Empty; [JsonPropertyName("platform")] - public string Platform { get; set; } = ""; + public string Platform { get; set; } = string.Empty; [JsonPropertyName("processors")] public int Processors { get; set; } @@ -127,17 +127,17 @@ public sealed class MonitorStatus public sealed class MonitorGame { [JsonPropertyName("code")] - public string Code { get; set; } = ""; + public string Code { get; set; } = string.Empty; [JsonPropertyName("state")] - public string State { get; set; } = ""; + public string State { get; set; } = string.Empty; [JsonPropertyName("map")] - public string Map { get; set; } = ""; + public string Map { get; set; } = string.Empty; [JsonPropertyName("player_count")] public int PlayerCount { get; set; } [JsonPropertyName("host")] - public string Host { get; set; } = ""; + public string Host { get; set; } = string.Empty; } diff --git a/src/Empostor.Server/Http/PrivacyController.cs b/src/Empostor.Server/Http/PrivacyController.cs index b005505..131795d 100644 --- a/src/Empostor.Server/Http/PrivacyController.cs +++ b/src/Empostor.Server/Http/PrivacyController.cs @@ -27,7 +27,9 @@ public sealed class PrivacyController : ControllerBase public IActionResult GetPrivacy() { if (!System.IO.File.Exists(PrivacyFile)) + { return Content(DefaultPrivacyHtml(), "text/html; charset=utf-8"); + } var html = System.IO.File.ReadAllText(PrivacyFile); return Content(html, "text/html; charset=utf-8"); @@ -43,15 +45,17 @@ public sealed class PrivacyController : ControllerBase try { var doc = System.Text.Json.JsonDocument.Parse(body); - var content = doc.RootElement.GetProperty("content").GetString() ?? ""; - var token = doc.RootElement.GetProperty("token").GetString() ?? ""; + var content = doc.RootElement.GetProperty("content").GetString() ?? string.Empty; + var token = doc.RootElement.GetProperty("token").GetString() ?? string.Empty; var adminToken = Environment.GetEnvironmentVariable("EMP_HTTP_TOKEN") ?? Environment.GetEnvironmentVariable("EMP_ADMIN_TOKEN") ?? "empostor"; if (token != adminToken) + { return Unauthorized(new { error = "Invalid token." }); + } System.IO.File.WriteAllText(PrivacyFile, content); _logger.LogInformation("PrivacyControllerPrivacy policy updated."); @@ -65,15 +69,24 @@ public sealed class PrivacyController : ControllerBase private void EnsureFile() { - if (_initialized) return; + if (_initialized) + { + return; + } lock (_initLock) { - if (_initialized) return; + if (_initialized) + { + return; + } + _initialized = true; if (!Directory.Exists(PagesDir)) + { Directory.CreateDirectory(PagesDir); + } if (!System.IO.File.Exists(PrivacyFile)) { diff --git a/src/Empostor.Server/Http/TokenController.cs b/src/Empostor.Server/Http/TokenController.cs index e677860..3c3cdc4 100644 --- a/src/Empostor.Server/Http/TokenController.cs +++ b/src/Empostor.Server/Http/TokenController.cs @@ -391,7 +391,7 @@ public sealed class TokenController : ControllerBase var putBody = JsonSerializer.SerializeToUtf8Bytes(new NikoPutRequest { ApiKey = _authApiConfig.NikoApiKey, - FriendCode = "", + FriendCode = string.Empty, }); var putReq = new HttpRequestMessage(HttpMethod.Put, apiUrl) @@ -736,7 +736,7 @@ public sealed class TokenController : ControllerBase public required string ApiKey { get; init; } [JsonPropertyName("FriendCode")] - public string FriendCode { get; init; } = ""; + public string FriendCode { get; init; } = string.Empty; } private sealed class NikoCreateResponse diff --git a/src/Empostor.Server/Net/Client.cs b/src/Empostor.Server/Net/Client.cs index 23e5379..09963e0 100644 --- a/src/Empostor.Server/Net/Client.cs +++ b/src/Empostor.Server/Net/Client.cs @@ -50,7 +50,8 @@ namespace Empostor.Server.Net if (Player != null && Player.IsHost) { - var isHostCheatingAllowed = _antiCheatConfig.AllowCheatingHosts switch { + var isHostCheatingAllowed = _antiCheatConfig.AllowCheatingHosts switch + { CheatingHostMode.Always => true, CheatingHostMode.IfRequested => GameVersion.HasDisableServerAuthorityFlag, CheatingHostMode.Never => false, diff --git a/src/Empostor.Server/Net/Inner/Objects/InnerPlayerControl.cs b/src/Empostor.Server/Net/Inner/Objects/InnerPlayerControl.cs index 5396d05..ae3d858 100644 --- a/src/Empostor.Server/Net/Inner/Objects/InnerPlayerControl.cs +++ b/src/Empostor.Server/Net/Inner/Objects/InnerPlayerControl.cs @@ -1008,8 +1008,8 @@ namespace Empostor.Server.Net.Inner.Objects { if (PlayerInfo.PlayerLevel != level) { - _logger.LogInformation("{Code} - Player {Name} ({Id}) level changed {Old} -> {New}", - _game.Code, sender.Client.Name, sender.Client.Id, PlayerInfo.PlayerLevel, level); + _logger.LogInformation("★ {Name} ({Id}) | Level {Old} → {New}", + sender.Client.Name, sender.Client.Id, PlayerInfo.PlayerLevel, level); } PlayerInfo.PlayerLevel = level; @@ -1168,7 +1168,9 @@ namespace Empostor.Server.Net.Inner.Objects }; var handled = await _commandService.TryHandleAsync(ctx); if (handled) + { return false; + } } var @event = new PlayerChatEvent(Game, sender, this, message); diff --git a/src/Empostor.Server/Net/Manager/ClientManager.cs b/src/Empostor.Server/Net/Manager/ClientManager.cs index 1e0420a..bd92402 100644 --- a/src/Empostor.Server/Net/Manager/ClientManager.cs +++ b/src/Empostor.Server/Net/Manager/ClientManager.cs @@ -109,7 +109,7 @@ namespace Empostor.Server.Net.Manager if (versionCompare == ICompatibilityManager.VersionCompareResult.ServerTooOld && _compatibilityConfig.AllowFutureGameVersions && platformSpecificData != null) { - _logger.LogWarning("Client [{Id}]{Name} connected using future version {Version}", id, name, clientVersion); + _logger.LogWarning("#{Id} {Name} | using future version {Version}", id, name, clientVersion); } else if (versionCompare != ICompatibilityManager.VersionCompareResult.Compatible || platformSpecificData == null) @@ -134,7 +134,7 @@ namespace Empostor.Server.Net.Manager return; } - _logger.LogInformation("Client [{Id}]{Name} connected with host authority", id, name); + _logger.LogInformation("#{Id} {Name} | host authority enabled", id, name); } if (name.Length > 10) { await connection.CustomDisconnectAsync(DisconnectReason.Custom, DisconnectMessages.UsernameLength); return; } @@ -148,7 +148,7 @@ namespace Empostor.Server.Net.Manager var platformStr = platformSpecificData?.Platform.ToString() ?? "Unknown"; var reactorMods = connection.GetReactorMods(); var reactorStr = reactorMods?.Mods is { Count: > 0 } mods - ? $" [Reactor: {string.Join(", ", System.Linq.Enumerable.Select(mods, m => $"{m.Id} {m.Version}"))}]" + ? $" | Reactor: {string.Join(", ", System.Linq.Enumerable.Select(mods, m => $"{m.Id} {m.Version}"))}" : string.Empty; // Primary: match by delta port (nonce) @@ -184,13 +184,13 @@ namespace Empostor.Server.Net.Manager } _logger.LogInformation( - "Client [{Id}]{Name} from port {Port} {Location}[{Lang}] [{Platform}] is authorized as {FriendCode} {HashPuid}{Reactor}", + "#{Id} {Name} | port {Port} | {Location} | {Lang} | {Platform} | FC {FriendCode} | {HashPuid}{Reactor}", id, name, deltaPort, location, lang, platformStr, friendCode ?? "unknown", HashPuid(authInfo.ProductUserId), reactorStr); } else { _logger.LogWarning( - "Client [{Id}]{Name} port {Port} has no auth info, falling back to IP", + "#{Id} {Name} | port {Port} has no auth info, falling back to IP", id, name, deltaPort); } } @@ -203,13 +203,13 @@ namespace Empostor.Server.Net.Manager { friendCode = authInfo.FriendCode; _logger.LogInformation( - "Client [{Id}]{Name} from {Ip} {Location}[{Lang}] [{Platform}] is authorized as {FriendCode} {HashPuid}{Reactor}", + "#{Id} {Name} | {Ip} | {Location} | {Lang} | {Platform} | FC {FriendCode} | {HashPuid}{Reactor}", id, name, NormalizeIp(clientIp), location, lang, platformStr, friendCode ?? "unknown", HashPuid(authInfo.ProductUserId), reactorStr); } else { _logger.LogWarning( - "Client [{Id}]{Name}: no auth info found (port={Port}, ip={Ip} {Location}) [{Lang}] [{Platform}]{Reactor}", + "#{Id} {Name} | no auth | port {Port} | {Ip} | {Location} | {Lang} | {Platform}{Reactor}", id, name, deltaPort, NormalizeIp(clientIp), location, lang, platformStr, reactorStr); } } @@ -220,7 +220,7 @@ namespace Empostor.Server.Net.Manager client.DeltaPort = deltaPort; client.Id = id; - _logger.LogDebug("Client [{Id}]{Name} connected with FriendCode={FC} DeltaPort={DP}", id, name, client.FriendCode ?? "(none)", deltaPort); + _logger.LogDebug("#{Id} {Name} | connected | FC={FC} | port {DP}", id, name, client.FriendCode ?? "(none)", deltaPort); _clients.TryAdd(id, client); await _eventManager.CallAsync(new ClientConnectedEvent(connection, client)); } diff --git a/src/Empostor.Server/Net/State/Game.Data.cs b/src/Empostor.Server/Net/State/Game.Data.cs index 04a8122..a0d66a6 100644 --- a/src/Empostor.Server/Net/State/Game.Data.cs +++ b/src/Empostor.Server/Net/State/Game.Data.cs @@ -206,7 +206,7 @@ namespace Empostor.Server.Net.State continue; } - _logger.LogWarning("Couldn't find spawnable object {0}.", objectId); + _logger.LogWarning("Couldn't find spawnable object {ObjectId}", objectId); break; } diff --git a/src/Empostor.Server/Net/State/Game.Incoming.cs b/src/Empostor.Server/Net/State/Game.Incoming.cs index 4d5c12c..89e7206 100644 --- a/src/Empostor.Server/Net/State/Game.Incoming.cs +++ b/src/Empostor.Server/Net/State/Game.Incoming.cs @@ -72,7 +72,7 @@ namespace Empostor.Server.Net.State public async ValueTask HandleKickPlayer(int playerId, bool isBan) { - _logger.LogInformation("{Code} - Kicking player {PlayerId} (Ban={IsBan})", Code, playerId, isBan); + _logger.LogInformation("✂ Player {PlayerId} | Ban={IsBan}", playerId, isBan); using (var kickMsg = MessageWriter.Get(MessageType.Reliable)) { WriteKickPlayerMessage(kickMsg, false, playerId, isBan); @@ -121,25 +121,20 @@ namespace Empostor.Server.Net.State private async ValueTask HandleJoinGameNew(ClientPlayer sender, bool isNew) { var client = sender.Client; - var clientIp = client.Connection.EndPoint?.Address; - var ipStr = clientIp?.IsIPv4MappedToIPv6 == true ? clientIp.MapToIPv4().ToString() : clientIp?.ToString() ?? "?"; - var remotePort = (client.Connection.EndPoint as System.Net.IPEndPoint)?.Port ?? 0; var authority = client.GameVersion.HasDisableServerAuthorityFlag ? "true" : "false"; var version = client.GameVersion.ToString(); - var hashPuid = client.ProductUserId?.Length >= 9 ? client.ProductUserId[..9] : client.ProductUserId ?? "0"; var deltaPort = client.DeltaPort; - var location = await _ipGeo.GetLocationAsync(clientIp); var lang = LanguageHelper.GetDisplayName(client.Language); var platform = client.PlatformSpecificData?.Platform.ToString() ?? "Unknown"; var platformName = client.PlatformSpecificData?.PlatformName; var platformStr = string.IsNullOrEmpty(platformName) ? platform : $"{platform}/{platformName}"; var reactorMods = client.GetReactorMods(); var reactorStr = reactorMods?.Mods is { Count: > 0 } mods - ? $" [Reactor: {string.Join(", ", System.Linq.Enumerable.Select(mods, m => $"{m.Id} {m.Version}"))}]" + ? $" | Reactor: {string.Join(", ", System.Linq.Enumerable.Select(mods, m => $"{m.Id} {m.Version}"))}" : string.Empty; - _logger.LogInformation("{Code} - Player {Name} ({HashPuid}) ({Id}) [{Lang}] [{Platform}] is joining from ({Ip} {Location}:{RemotePort}) with v{Version}, Authority:{Authority}, port: {DeltaPort}{Reactor}", - Code, sender.Client.Name, hashPuid, sender.Client.Id, lang, platformStr, ipStr, location, remotePort, version, authority, deltaPort, reactorStr); + _logger.LogInformation("▶ {Name} ({Id}) joined | {Lang} | {Platform} | v{Version} | Authority:{Authority} | port {DeltaPort}{Reactor}", + sender.Client.Name, sender.Client.Id, lang, platformStr, version, authority, deltaPort, reactorStr); if (isNew) { @@ -229,8 +224,8 @@ namespace Empostor.Server.Net.State private async ValueTask HandleJoinGameNext(ClientPlayer sender, bool isNew) { var authority = sender.Client.GameVersion.HasDisableServerAuthorityFlag ? "true" : "false"; - _logger.LogInformation("{Code} - Player {Name} ({Id}) is rejoining. Player Authority : {Authority}, port: {DeltaPort}", - Code, sender.Client.Name, sender.Client.Id, authority, sender.Client.DeltaPort); + _logger.LogInformation("↻ {Name} ({Id}) rejoined | Authority:{Authority} | port {DeltaPort}", + sender.Client.Name, sender.Client.Id, authority, sender.Client.DeltaPort); if (isNew) { @@ -239,7 +234,7 @@ namespace Empostor.Server.Net.State if (sender.Client.Id == HostId) { - _logger.LogInformation("{Code} - Host {Name} ({Id}) is rejoining.", Code, sender.Client.Name, sender.Client.Id); + _logger.LogInformation("↻ {Name} ({Id}) host rejoined", sender.Client.Name, sender.Client.Id); GameState = GameStates.NotStarted; await HandleJoinGameNew(sender, false); await CheckLimboPlayers(); diff --git a/src/Empostor.Server/Net/State/Game.State.cs b/src/Empostor.Server/Net/State/Game.State.cs index 6452f5b..d0d58b5 100644 --- a/src/Empostor.Server/Net/State/Game.State.cs +++ b/src/Empostor.Server/Net/State/Game.State.cs @@ -33,8 +33,8 @@ namespace Empostor.Server.Net.State return false; } - _logger.LogInformation("{Code} - Player {Name} ({Id}) has left for \"{Reason}\" hashpuid : {HashPuid}", - Code, player.Client.Name, playerId, reason, + _logger.LogInformation("◀ {Name} ({Id}) left | {Reason} | {HashPuid}", + player.Client.Name, playerId, reason, player.Client.ProductUserId?.Length >= 9 ? player.Client.ProductUserId[..9] : player.Client.ProductUserId ?? "0"); if (GameState == GameStates.Starting || GameState == GameStates.Started || GameState == GameStates.NotStarted) { @@ -71,7 +71,7 @@ namespace Empostor.Server.Net.State await Task.Delay(_timeoutConfig.ConnectionTimeout); if (player.Client.Connection.IsConnected && player.Client.Connection is HazelConnection hazel) { - _logger.LogInformation("{Code} - Player {Name} ({Id}) kept connection open after leaving, disposing.", Code, player.Client.Name, playerId); + _logger.LogInformation("◀ {Name} ({Id}) left | kept connection, disposing", player.Client.Name, playerId); await player.Client.DisconnectAsync(isBan ? DisconnectReason.Banned : DisconnectReason.Kicked); } }); @@ -103,7 +103,7 @@ namespace Empostor.Server.Net.State } HostId = host.Client.Id; - _logger.LogInformation("{Code} - Assigned {Name} ({Id}) as new host.", Code, host.Client.Name, host.Client.Id); + _logger.LogInformation("★ {Name} ({Id}) became host", host.Client.Name, host.Client.Id); if (GameState == GameStates.Ended && host.Limbo == LimboStates.WaitingForHost) { GameState = GameStates.NotStarted; diff --git a/src/Empostor.Server/Net/State/Game.cs b/src/Empostor.Server/Net/State/Game.cs index 142f591..9c3f008 100644 --- a/src/Empostor.Server/Net/State/Game.cs +++ b/src/Empostor.Server/Net/State/Game.cs @@ -16,7 +16,6 @@ using Empostor.Api.Net.Manager; using Empostor.Api.Net.Messages.S2C; using Empostor.Server.Events; using Empostor.Server.Net.Manager; -using Empostor.Server.Utils; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -34,7 +33,6 @@ namespace Empostor.Server.Net.State private readonly ICompatibilityManager _compatibilityManager; private readonly CompatibilityConfig _compatibilityConfig; private readonly TimeoutConfig _timeoutConfig; - private readonly IpGeolocationService _ipGeo; public Game( ILogger logger, @@ -48,8 +46,7 @@ namespace Empostor.Server.Net.State IEventManager eventManager, ICompatibilityManager compatibilityManager, IOptions compatibilityConfig, - IOptions timeoutConfig, - IpGeolocationService ipGeo) + IOptions timeoutConfig) { _logger = logger; _serviceProvider = serviceProvider; @@ -68,7 +65,6 @@ namespace Empostor.Server.Net.State _compatibilityManager = compatibilityManager; _compatibilityConfig = compatibilityConfig.Value; _timeoutConfig = timeoutConfig.Value; - _ipGeo = ipGeo; Items = new ConcurrentDictionary(); } diff --git a/src/Empostor.Server/Program.cs b/src/Empostor.Server/Program.cs index 21b63fa..f52c64d 100644 --- a/src/Empostor.Server/Program.cs +++ b/src/Empostor.Server/Program.cs @@ -54,13 +54,15 @@ namespace Empostor.Server private static int Main(string[] args) { Log.Logger = new LoggerConfiguration() - .WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Source} - {Message:lj}{NewLine}{Exception}") + .WriteTo.Console(formatter: new EmpostorConsoleFormatter()) .Enrich.With() .CreateBootstrapLogger(); try { Log.Information("Empostor v{Version} starting", DotnetUtils.Version); - CreateHostBuilder(args).Build().Run(); + var host = CreateHostBuilder(args).Build(); + StartupBanner.Print(host.Services.GetRequiredService()); + host.Run(); return 0; } catch (Exception ex) @@ -163,6 +165,7 @@ namespace Empostor.Server }); services.AddSingleton(); + services.AddSingleton(ModuleTagRegistry.Instance); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(p => p.GetRequiredService()); @@ -255,12 +258,12 @@ namespace Empostor.Server .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) .Enrich.FromLogContext() .Enrich.With() - .WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Source} - {Message:lj}{NewLine}{Exception}") + .WriteTo.Console(formatter: new EmpostorConsoleFormatter()) .WriteTo.File( "Log/empostor-.log", rollingInterval: RollingInterval.Day, retainedFileCountLimit: 31, - outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Source} - {Message:lj}{NewLine}{Exception}") + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{Module}] {Message}{NewLine}{Exception}") .ReadFrom.Configuration(context.Configuration, new ConfigurationReaderOptions(ConfigurationAssemblySource.AlwaysScanDllFiles)); AssemblyLoadContext.Default.Resolving -= TryLoad; diff --git a/src/Empostor.Server/Utils/EmpostorConsoleFormatter.cs b/src/Empostor.Server/Utils/EmpostorConsoleFormatter.cs new file mode 100644 index 0000000..f5be36c --- /dev/null +++ b/src/Empostor.Server/Utils/EmpostorConsoleFormatter.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Serilog.Events; +using Serilog.Formatting; + +namespace Empostor.Server.Utils; + +public sealed class EmpostorConsoleFormatter : ITextFormatter +{ + private readonly bool _useColor; + + private const string Reset = "\x1b[0m"; + private const string Bold = "\x1b[1m"; + private const string Dim = "\x1b[2m"; + + private const string FgRed = "\x1b[91m"; + private const string FgGreen = "\x1b[92m"; + private const string FgYellow = "\x1b[93m"; + private const string FgBlue = "\x1b[94m"; + private const string FgMagenta = "\x1b[95m"; + private const string FgCyan = "\x1b[96m"; + private const string FgWhite = "\x1b[97m"; + private const string FgGray = "\x1b[90m"; + private const string FgDark = "\x1b[37m"; + + private const string BgRed = "\x1b[41m"; + + private static readonly Dictionary ModuleColors = new() + { + ["Auth"] = FgMagenta, + ["Game"] = FgCyan, + ["Chat"] = FgGreen, + ["Reactor"] = "\x1b[35m", + ["Admin"] = FgBlue, + ["Cmd"] = FgYellow, + ["Ban"] = FgRed, + ["Plugin"] = "\x1b[36m", + ["Discord"] = "\x1b[34m", + ["Stat"] = FgCyan, + }; + + public EmpostorConsoleFormatter() + { + _useColor = !Console.IsOutputRedirected; + } + + public void Format(LogEvent logEvent, TextWriter output) + { + if (_useColor) + { + FormatColored(logEvent, output); + } + else + { + FormatPlain(logEvent, output); + } + } + + private void FormatColored(LogEvent logEvent, TextWriter output) + { + var ts = logEvent.Timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff zzz"); + output.Write(Dim); + output.Write(FgGray); + output.Write(ts); + output.Write(Reset); + output.Write(' '); + + var (levelFg, levelBg, levelText) = LevelStyle(logEvent.Level); + if (levelBg.Length > 0) + { + output.Write(levelBg); + } + + output.Write(levelFg); + output.Write(Bold); + output.Write(levelText); + output.Write(Reset); + output.Write(' '); + + var module = GetModule(logEvent); + var modColor = GetModuleColor(module); + output.Write(modColor); + output.Write('['); + output.Write(module); + output.Write(']'); + output.Write(Reset); + output.Write(' '); + + output.Write(logEvent.RenderMessage()); + output.WriteLine(); + + if (logEvent.Exception != null) + { + output.Write(FgRed); + output.Write(logEvent.Exception); + output.Write(Reset); + output.WriteLine(); + } + } + + private static void FormatPlain(LogEvent logEvent, TextWriter output) + { + var ts = logEvent.Timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff zzz"); + var level = FormatLevel(logEvent.Level); + var module = GetModule(logEvent); + + output.Write($"{ts} [{level}] [{module}] "); + output.Write(logEvent.RenderMessage()); + output.WriteLine(); + + if (logEvent.Exception != null) + { + output.Write(logEvent.Exception.ToString()); + output.WriteLine(); + } + } + + private static string GetModule(LogEvent logEvent) + { + if (logEvent.Properties.TryGetValue("Module", out var v) && + v is ScalarValue sv && sv.Value is string module) + { + return module; + } + + return "Empostor"; + } + + private static string GetModuleColor(string module) + { + if (ModuleColors.TryGetValue(module, out var c)) + return c; + + // Check plugin-registered tags + var regColor = ModuleTagRegistry.Instance.GetColor(module); + return regColor ?? FgWhite; + } + + private static string FormatLevel(LogEventLevel level) => level switch + { + LogEventLevel.Verbose => "VRB", + LogEventLevel.Debug => "DBG", + LogEventLevel.Information => "INF", + LogEventLevel.Warning => "WRN", + LogEventLevel.Error => "ERR", + LogEventLevel.Fatal => "FTL", + _ => "INF", + }; + + private static (string fg, string bg, string text) LevelStyle(LogEventLevel level) => level switch + { + LogEventLevel.Fatal => (FgWhite, BgRed, "FTL"), + LogEventLevel.Error => (FgRed, string.Empty, "ERR"), + LogEventLevel.Warning => (FgYellow, string.Empty, "WRN"), + LogEventLevel.Information => (FgGray, string.Empty, "INF"), + LogEventLevel.Debug => (FgDark, string.Empty, "DBG"), + LogEventLevel.Verbose => (Dim + FgDark, string.Empty, "VRB"), + _ => (FgGray, string.Empty, "INF"), + }; +} diff --git a/src/Empostor.Server/Utils/ModuleTagRegistry.cs b/src/Empostor.Server/Utils/ModuleTagRegistry.cs new file mode 100644 index 0000000..a518e72 --- /dev/null +++ b/src/Empostor.Server/Utils/ModuleTagRegistry.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using Empostor.Api.Utils; + +namespace Empostor.Server.Utils; + +/// +/// Thread-safe registry for plugin module tags. +/// Plugins inject and call to assign a short tag (and optional ANSI colour) to their namespace prefix. +/// The enricher and console formatter consult this registry at runtime. +/// +public sealed class ModuleTagRegistry : IModuleTagRegistry +{ + public static ModuleTagRegistry Instance { get; } = new(); + + private readonly List<(string Prefix, string Tag, string? Color)> _entries = new(); + private readonly ConcurrentDictionary _colorCache = new(); + + private int _autoIdx = -1; + + private static readonly string[] AutoPalette = + { + "\x1b[96m", // Bright Cyan + "\x1b[95m", // Bright Magenta + "\x1b[94m", // Bright Blue + "\x1b[92m", // Bright Green + "\x1b[93m", // Bright Yellow + "\x1b[91m", // Bright Red + }; + + private ModuleTagRegistry() + { + } + + /// + /// Register a namespace → tag mapping. + /// + /// Full or partial namespace prefix, e.g. "Empostor.Plugin.MyMod". + /// Short tag shown in brackets, e.g. "MyMod" (keep it short). + /// + /// Optional ANSI SGR colour code (e.g. "\x1b[92m" for bright green). + /// When omitted a colour is auto-assigned from the palette. + /// + public void Register(string namespacePrefix, string tag, string? ansiColor = null) + { + var color = ansiColor ?? NextAutoColor(); + _entries.Add((namespacePrefix, tag, color)); + _colorCache[tag] = color; + } + + /// + /// Resolve a full SourceContext string to a (tag, colour) pair. + /// Returns ("Plugin", null) when no registered prefix matches. + /// + public (string Tag, string? Color) Resolve(string sourceContext) + { + foreach (var (prefix, tag, color) in _entries) + { + if (sourceContext.StartsWith(prefix, StringComparison.Ordinal)) + return (tag, color); + } + + return ("Plugin", null); + } + + public string? GetColor(string tag) + { + _colorCache.TryGetValue(tag, out var c); + return c; + } + + private string NextAutoColor() + { + var i = Interlocked.Increment(ref _autoIdx); + return AutoPalette[Math.Abs(i) % AutoPalette.Length]; + } +} diff --git a/src/Empostor.Server/Utils/ShortSourceEnricher.cs b/src/Empostor.Server/Utils/ShortSourceEnricher.cs index 69f0770..cf578fa 100644 --- a/src/Empostor.Server/Utils/ShortSourceEnricher.cs +++ b/src/Empostor.Server/Utils/ShortSourceEnricher.cs @@ -13,11 +13,49 @@ namespace Empostor.Server.Utils { var shortName = fullName[(fullName.LastIndexOf('.') + 1)..]; logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Source", shortName)); + + var module = GetModuleTag(fullName); + logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Module", module)); } else { logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Source", "Empostor")); + logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Module", "Empostor")); } } + + private static string GetModuleTag(string sourceContext) + { + if (sourceContext.StartsWith("Empostor.Server.Net.Manager")) return "Auth"; + if (sourceContext.StartsWith("Empostor.Server.Service.Auth")) return "Auth"; + + if (sourceContext.StartsWith("Empostor.Server.Net.State")) return "Game"; + if (sourceContext.StartsWith("Empostor.Server.Net.Inner")) return "Game"; + if (sourceContext.StartsWith("Empostor.Server.Net.Messages")) return "Game"; + if (sourceContext.StartsWith("Empostor.Server.Net.Factories")) return "Game"; + + if (sourceContext.StartsWith("Empostor.Server.Service.Admin.Reactor")) return "Reactor"; + if (sourceContext.StartsWith("Empostor.Server.Service.Admin.Ban")) return "Ban"; + if (sourceContext.StartsWith("Empostor.Server.Service.Admin.Chat")) return "Chat"; + if (sourceContext.StartsWith("Empostor.Server.Service.Admin.Report")) return "Admin"; + if (sourceContext.StartsWith("Empostor.Server.Service.Admin")) return "Admin"; + + if (sourceContext.StartsWith("Empostor.Server.Service.Api")) return "Discord"; + if (sourceContext.StartsWith("Empostor.Server.Service.Stat")) return "Stat"; + + if (sourceContext.StartsWith("Empostor.Server.Http")) return "Admin"; + + if (sourceContext.StartsWith("Empostor.Server.Commands")) return "Cmd"; + + if (sourceContext.StartsWith("Empostor.Server.Plugins")) return "Plugin"; + + if (sourceContext.StartsWith("Empostor.Plugin.")) + { + var (tag, _) = ModuleTagRegistry.Instance.Resolve(sourceContext); + return tag; + } + + return "Empostor"; + } } } diff --git a/src/Empostor.Server/Utils/StartupBanner.cs b/src/Empostor.Server/Utils/StartupBanner.cs new file mode 100644 index 0000000..cb7f27a --- /dev/null +++ b/src/Empostor.Server/Utils/StartupBanner.cs @@ -0,0 +1,172 @@ +using System; +using System.IO; +using System.Linq; +using Empostor.Api.Config; +using Empostor.Api.Utils; +using Microsoft.Extensions.Configuration; + +namespace Empostor.Server.Utils; + +internal static class StartupBanner +{ + private const string Bold = "\x1b[1m"; + private const string Dim = "\x1b[2m"; + private const string Reset = "\x1b[0m"; + + private const string Cyan = "\x1b[96m"; + private const string Green = "\x1b[92m"; + private const string Yellow = "\x1b[93m"; + private const string Red = "\x1b[91m"; + private const string White = "\x1b[97m"; + private const string Gray = "\x1b[90m"; + + private const string BoxH = "─"; + private const string BoxV = "│"; + private const string BoxTL = "┌"; + private const string BoxTR = "┐"; + private const string BoxBL = "└"; + private const string BoxBR = "┘"; + private const string BoxML = "├"; + private const string BoxMR = "┤"; + + private static readonly bool UseColor = !Console.IsOutputRedirected; + + public static void Print(IConfiguration configuration) + { + var prevEncoding = Console.OutputEncoding; + try { Console.OutputEncoding = System.Text.Encoding.UTF8; } + catch { /* best-effort */ } + + try + { + PrintInternal(configuration); + } + finally + { + try { Console.OutputEncoding = prevEncoding; } + catch { /* ignore */ } + } + } + + private static void PrintInternal(IConfiguration configuration) + { + var server = configuration.GetSection(ServerConfig.Section).Get() ?? new ServerConfig(); + var http = configuration.GetSection(HttpServerConfig.Section).Get() ?? new HttpServerConfig(); + var anticheat = configuration.GetSection(AntiCheatConfig.Section).Get() ?? new AntiCheatConfig(); + var logLevel = configuration.GetValue("Serilog:MinimumLevel") ?? "Information"; + var logPath = Path.Combine(Directory.GetCurrentDirectory(), "Log", $"empostor-{DateTime.Now:yyyy-MM-dd}.log"); + + var version = DotnetUtils.Version; + + string[] labelTexts = { "Public IP", "Listen Port", "HTTP API", "Admin Panel", "Port Pool", "AntiCheat", "Log Level", "Log File" }; + var maxLabel = labelTexts.Max(t => t.Length) + 2; + + var width = 62; + + using var sw = new StringWriter(); + + sw.WriteLine(C(BoxTL + new string(BoxH[0], width - 2) + BoxTR, Cyan)); + + Center(sw, width, Bold + White + "Empostor" + Reset + C(" v" + version, Cyan)); + Center(sw, width, C("Among Us Private Server", Gray)); + sw.WriteLine(C(BoxML + new string(BoxH[0], width - 2) + BoxMR, Cyan)); + + Row(sw, width, maxLabel, "Public IP", server.PublicIp); + Row(sw, width, maxLabel, "Listen Port", $"{server.ListenPort} (UDP)"); + Row(sw, width, maxLabel, "HTTP API", $"http://{http.ListenIp}:{http.ListenPort}"); + Row(sw, width, maxLabel, "Admin Panel", $"http://{server.PublicIp}:{http.ListenPort}/admin"); + var poolInfo = server.DeltaPortStart > 0 && server.DeltaPortEnd >= server.DeltaPortStart + ? $"{server.DeltaPortStart}-{server.DeltaPortEnd} ({server.DeltaPortEnd - server.DeltaPortStart + 1} ports)" + : C("disabled", Yellow); + Row(sw, width, maxLabel, "Port Pool", poolInfo); + Row(sw, width, maxLabel, "AntiCheat", anticheat.Enabled ? C("✔ Enabled", Green) : C("✗ Disabled", Red)); + Row(sw, width, maxLabel, "Log Level", logLevel); + Row(sw, width, maxLabel, "Log File", TrimPath(logPath, width - maxLabel - 5)); + + sw.WriteLine(C(BoxBL + new string(BoxH[0], width - 2) + BoxBR, Cyan)); + + Console.WriteLine(sw.ToString()); + } + + private static void Row(StringWriter sw, int width, int labelW, string label, string value) + { + sw.Write(C(BoxV, Cyan)); + sw.Write(' '); + sw.Write(C(PadRight(label, labelW), White)); + sw.Write(C(BoxV, Cyan)); + sw.Write(' '); + sw.Write(C(value, Gray)); + + var used = 4 + labelW + VisibleLength(value); + var remain = width - 2 - used; + if (remain > 0) + { + sw.Write(new string(' ', remain)); + } + + sw.WriteLine(C(BoxV, Cyan)); + } + + private static void Center(StringWriter sw, int width, string text) + { + var vis = VisibleLength(text); + var pad = (width - vis - 2) / 2; + sw.Write(C(BoxV, Cyan)); + if (pad > 0) + { + sw.Write(new string(' ', pad)); + } + + sw.Write(text); + var right = width - vis - pad - 2; + if (right > 0) + { + sw.Write(new string(' ', right)); + } + + sw.WriteLine(C(BoxV, Cyan)); + } + + private static string C(string text, string ansi) => + UseColor ? $"{ansi}{text}{Reset}" : text; + + private static int VisibleLength(string text) + { + if (!UseColor) + { + return text.Length; + } + + var len = 0; + var inEscape = false; + foreach (var ch in text) + { + if (ch == '\x1b') { inEscape = true; continue; } + if (inEscape) { if (ch == 'm') { inEscape = false; } continue; } + len++; + } + + return len; + } + + private static string PadRight(string text, int width) + { + var vis = VisibleLength(text); + return text + new string(' ', Math.Max(0, width - vis)); + } + + private static string TrimPath(string path, int maxLen) + { + if (maxLen < 5) + { + return path; + } + + if (path.Length <= maxLen) + { + return path; + } + + return "…" + path[^(maxLen - 1)..]; + } +} diff --git a/src/NextFast.Hazel-New/Next.Hazel/Dtls/DtlsConnectionListener.cs b/src/NextFast.Hazel-New/Next.Hazel/Dtls/DtlsConnectionListener.cs index 1e78199..023b8ac 100644 --- a/src/NextFast.Hazel-New/Next.Hazel/Dtls/DtlsConnectionListener.cs +++ b/src/NextFast.Hazel-New/Next.Hazel/Dtls/DtlsConnectionListener.cs @@ -786,7 +786,7 @@ public class DtlsConnectionListener : UdpConnectionListener await SendHelloVerifyRequest(peerAddress, outgoingSequence, record.Epoch, recordProtection, peer.ProtocolVersion); return true; } - + // Client is initiating a brand new connection. We need // to destroy the existing connection and establish a // new session. @@ -881,7 +881,7 @@ public class DtlsConnectionListener : UdpConnectionListener serverHelloHandshake.FragmentLength = serverHelloHandshake.Length; var maxCertFragmentSize = peer.Session.Version == 0 ? MaxCertFragmentSizeV0 : MaxCertFragmentSizeV1; - + /*var certificateData = encodedCertificate; var initialCertPadding = Record.Size + Handshake.Handshake.Size + serverHello.Size + Handshake.Handshake.Size; var certInitialFragmentSize = Math.Min(certificateData.Length, maxCertFragmentSize - initialCertPadding); diff --git a/src/NextFast.Hazel-New/Next.Hazel/UPnP/UPnPHelper.cs b/src/NextFast.Hazel-New/Next.Hazel/UPnP/UPnPHelper.cs index 513b314..b101148 100644 --- a/src/NextFast.Hazel-New/Next.Hazel/UPnP/UPnPHelper.cs +++ b/src/NextFast.Hazel-New/Next.Hazel/UPnP/UPnPHelper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; using System.Net.Http; using System.Net.Sockets; @@ -165,7 +165,7 @@ public class UPnPHelper : IDisposable var typeNode = desc.SelectSingleNode("//tns:device/tns:deviceType/text()", nsMgr); if (typeNode?.Value == null) return false; - + if (!typeNode.Value.Contains("InternetGatewayDevice")) return false; diff --git a/src/NextFast.Hazel-New/Next.Hazel/Udp/UdpConnectionListener.cs b/src/NextFast.Hazel-New/Next.Hazel/Udp/UdpConnectionListener.cs index 63e884b..446e615 100644 --- a/src/NextFast.Hazel-New/Next.Hazel/Udp/UdpConnectionListener.cs +++ b/src/NextFast.Hazel-New/Next.Hazel/Udp/UdpConnectionListener.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; @@ -34,7 +34,7 @@ public class UdpConnectionListener : NetworkConnectionListener /// public AcceptConnectionCheck AcceptConnection; - #nullable enable +#nullable enable /// /// Creates a new UdpConnectionListener for the given , port and . /// diff --git a/src/NextFast.Hazel-New/Next.Hazel/Udp/UdpConnectionRateLimit.cs b/src/NextFast.Hazel-New/Next.Hazel/Udp/UdpConnectionRateLimit.cs index 788910e..d1c2d22 100644 --- a/src/NextFast.Hazel-New/Next.Hazel/Udp/UdpConnectionRateLimit.cs +++ b/src/NextFast.Hazel-New/Next.Hazel/Udp/UdpConnectionRateLimit.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Net; @@ -11,7 +11,7 @@ public class UdpConnectionRateLimit : IDisposable { // Allow burst to 5 connections. // Decrease by 1 every second. - public int MaxConnections { get; private set; } = 5; + public int MaxConnections { get; private set; } = 5; public int FalloffMs { get; private set; } = 1000; private static readonly ILogger Logger = Log.ForContext(); @@ -65,7 +65,7 @@ public class UdpConnectionRateLimit : IDisposable public bool IsAllowed(IPAddress key) { - if (_connectionCount.TryGetValue(key, out var value) && value >= MaxConnections) + if (_connectionCount.TryGetValue(key, out var value) && value >= MaxConnections) return false; _connectionCount.AddOrUpdate(key, _ => 1, (_, i) => i + 1);