mirror of
https://github.com/Empostor/Empostor.git
synced 2026-10-20 01:46:07 +00:00
Enhanced Log Output Style
This commit is contained in:
1 parent
6248d3c0cc
commit
0e70cafd23
32 files changed
+574
-87
No files matched your search
@@ -15,13 +15,13 @@ public class AuthApiConfig
|
|||||||
|
|
||||||
public AuthApiMode Mode { get; set; } = AuthApiMode.Innersloth;
|
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 NikoApiBaseUrl { get; set; } = "https://au-verify.niko233.top";
|
||||||
|
|
||||||
public string RelayApiBaseUrl { get; set; } = "http://localhost:5100";
|
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";
|
public string UmeApiBaseUrl { get; set; } = "https://auverify.hayashiume.top";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Empostor.Api.Innersloth
|
namespace Empostor.Api.Innersloth
|
||||||
{
|
{
|
||||||
public static class TextBox
|
public static class TextBox
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
namespace Empostor.Api.Utils;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows plugins to register a custom module tag for coloured
|
||||||
|
/// console output. Inject via DI and call <see cref="Register" />
|
||||||
|
/// during plugin construction.
|
||||||
|
/// </summary>
|
||||||
|
public interface IModuleTagRegistry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Register a namespace → tag mapping.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="namespacePrefix">
|
||||||
|
/// Full or partial namespace prefix, e.g. "Empostor.Plugin.MyMod".
|
||||||
|
/// </param>
|
||||||
|
/// <param name="tag">
|
||||||
|
/// Short tag shown in console brackets, e.g. "MyMod".
|
||||||
|
/// </param>
|
||||||
|
/// <param name="ansiColor">
|
||||||
|
/// Optional ANSI SGR colour, e.g. "\x1b[92m" for bright green.
|
||||||
|
/// Omit for auto-assignment from the palette.
|
||||||
|
/// </param>
|
||||||
|
void Register(string namespacePrefix, string tag, string? ansiColor = null);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Empostor.Api.Plugins;
|
using Empostor.Api.Plugins;
|
||||||
|
using Empostor.Api.Utils;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Empostor.Plugin.Chat;
|
namespace Empostor.Plugin.Chat;
|
||||||
@@ -9,14 +10,15 @@ public sealed class ChatPlugin : PluginBase
|
|||||||
{
|
{
|
||||||
private readonly ILogger<ChatPlugin> _logger;
|
private readonly ILogger<ChatPlugin> _logger;
|
||||||
|
|
||||||
public ChatPlugin(ILogger<ChatPlugin> logger)
|
public ChatPlugin(ILogger<ChatPlugin> logger, IModuleTagRegistry tags)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
tags.Register("Empostor.Plugin.Chat", "Chat", "\x1b[92m");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ValueTask EnableAsync()
|
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;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Empostor.Api;
|
|
||||||
using Empostor.Api.Events.Player;
|
using Empostor.Api.Events.Player;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -22,33 +21,26 @@ public sealed class ChatService
|
|||||||
|
|
||||||
public void HandleChatMessage(IPlayerChatEvent e)
|
public void HandleChatMessage(IPlayerChatEvent e)
|
||||||
{
|
{
|
||||||
var client = e.ClientPlayer.Client;
|
var playerName = e.ClientPlayer.Client.Name;
|
||||||
var clientId = client.Id;
|
|
||||||
var playerName = client.Name;
|
|
||||||
var slot = e.ClientPlayer.Character?.PlayerId ?? 0;
|
var slot = e.ClientPlayer.Character?.PlayerId ?? 0;
|
||||||
var gameCode = Api.Innersloth.GameCodeParser.IntToGameName(e.Game.Code);
|
|
||||||
|
|
||||||
int channel;
|
|
||||||
string channelName;
|
string channelName;
|
||||||
if (e.IsCancelled)
|
if (e.IsCancelled)
|
||||||
{
|
{
|
||||||
channel = 0;
|
|
||||||
channelName = "Canceled";
|
channelName = "Canceled";
|
||||||
}
|
}
|
||||||
else if (!e.SendToAllPlayers)
|
else if (!e.SendToAllPlayers)
|
||||||
{
|
{
|
||||||
channel = 255;
|
|
||||||
channelName = "Command";
|
channelName = "Command";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
channel = -1;
|
|
||||||
channelName = "Public";
|
channelName = "Public";
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
"{Code} - [{ClientId}][{Name}][{Slot}] => [{Channel}]{ChannelName}: [{Message}]",
|
"✉ {Name} [{Slot}] → {ChannelName}: {Message}",
|
||||||
gameCode, clientId, playerName, slot, channel, channelName, e.Message);
|
playerName, slot, channelName, e.Message);
|
||||||
|
|
||||||
var isHost = e.ClientPlayer.IsHost;
|
var isHost = e.ClientPlayer.IsHost;
|
||||||
var maxLength = isHost ? _config.HostMaxMessageLength : _config.PlayerMaxMessageLength;
|
var maxLength = isHost ? _config.HostMaxMessageLength : _config.PlayerMaxMessageLength;
|
||||||
@@ -56,7 +48,7 @@ public sealed class ChatService
|
|||||||
if (e.Message.Length > maxLength)
|
if (e.Message.Length > maxLength)
|
||||||
{
|
{
|
||||||
_logger.LogWarning(
|
_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);
|
playerName, isHost ? "host" : "player", e.Message.Length, maxLength);
|
||||||
|
|
||||||
e.PlayerControl.SendChatToPlayerAsync(_config.TooLongMessage, e.PlayerControl);
|
e.PlayerControl.SendChatToPlayerAsync(_config.TooLongMessage, e.PlayerControl);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public sealed class FixedCodePlugin : PluginBase
|
|||||||
|
|
||||||
public FixedCodePlugin(ILogger<FixedCodePlugin> logger, FixedCodeListener listener)
|
public FixedCodePlugin(ILogger<FixedCodePlugin> logger, FixedCodeListener listener)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_listener = listener;
|
_listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Empostor.Plugins.FriendCodeValidator
|
|||||||
private static readonly Regex Pattern =
|
private static readonly Regex Pattern =
|
||||||
new(@"^([a-zA-Z]+)#(\d{4})$", RegexOptions.Compiled);
|
new(@"^([a-zA-Z]+)#(\d{4})$", RegexOptions.Compiled);
|
||||||
|
|
||||||
private static readonly HashSet<string> WordsA = new(StringComparer.OrdinalIgnoreCase)
|
private static readonly HashSet<string> WordsA = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
"abiding",
|
"abiding",
|
||||||
"abiotic",
|
"abiotic",
|
||||||
@@ -2420,7 +2420,7 @@ namespace Empostor.Plugins.FriendCodeValidator
|
|||||||
"zonal",
|
"zonal",
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly HashSet<string> WordsB = new(StringComparer.OrdinalIgnoreCase)
|
private static readonly HashSet<string> WordsB = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
"ability",
|
"ability",
|
||||||
"absence",
|
"absence",
|
||||||
@@ -5402,7 +5402,7 @@ namespace Empostor.Plugins.FriendCodeValidator
|
|||||||
// Try every split point: left+right must be (A,B) or (B,A)
|
// Try every split point: left+right must be (A,B) or (B,A)
|
||||||
for (var i = 1; i < name.Length; i++)
|
for (var i = 1; i < name.Length; i++)
|
||||||
{
|
{
|
||||||
var left = name[..i];
|
var left = name[..i];
|
||||||
var right = name[i..];
|
var right = name[i..];
|
||||||
if ((WordsA.Contains(left) && WordsB.Contains(right)) ||
|
if ((WordsA.Contains(left) && WordsB.Contains(right)) ||
|
||||||
(WordsB.Contains(left) && WordsA.Contains(right)))
|
(WordsB.Contains(left) && WordsA.Contains(right)))
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public sealed class TitleEventListener : IEventListener
|
|||||||
public TitleEventListener(ILogger<TitleEventListener> logger, TitleStore store)
|
public TitleEventListener(ILogger<TitleEventListener> logger, TitleStore store)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_store = store;
|
_store = store;
|
||||||
}
|
}
|
||||||
|
|
||||||
[EventListener]
|
[EventListener]
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public sealed class WelcomeMessageService
|
|||||||
if (colon < 1) continue;
|
if (colon < 1) continue;
|
||||||
|
|
||||||
var lang = line[..colon].Trim();
|
var lang = line[..colon].Trim();
|
||||||
var msg = line[(colon + 1)..].Trim();
|
var msg = line[(colon + 1)..].Trim();
|
||||||
if (!string.IsNullOrEmpty(msg))
|
if (!string.IsNullOrEmpty(msg))
|
||||||
_templates[lang] = msg;
|
_templates[lang] = msg;
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ public sealed class WelcomeMessageService
|
|||||||
return template
|
return template
|
||||||
.Replace("{Name}", name, StringComparison.OrdinalIgnoreCase)
|
.Replace("{Name}", name, StringComparison.OrdinalIgnoreCase)
|
||||||
.Replace("{FriendCode}", friendCode ?? "None", StringComparison.OrdinalIgnoreCase)
|
.Replace("{FriendCode}", friendCode ?? "None", StringComparison.OrdinalIgnoreCase)
|
||||||
.Replace("{GameCode}", gameCode, StringComparison.OrdinalIgnoreCase);
|
.Replace("{GameCode}", gameCode, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string DefaultContent = """
|
private const string DefaultContent = """
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ public sealed class CommandService
|
|||||||
public async ValueTask<bool> TryHandleAsync(CommandContext ctx)
|
public async ValueTask<bool> TryHandleAsync(CommandContext ctx)
|
||||||
{
|
{
|
||||||
if (!_commands.TryGetValue(ctx.Name, out var command))
|
if (!_commands.TryGetValue(ctx.Name, out var command))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ public sealed class MaxCommand : ICommand
|
|||||||
|
|
||||||
var msg = ctx.GetString("command.max.set").Format(count).Get();
|
var msg = ctx.GetString("command.max.set").Format(count).Get();
|
||||||
if (count > 15)
|
if (count > 15)
|
||||||
|
{
|
||||||
msg += "\n" + ctx.GetString("command.max.warning");
|
msg += "\n" + ctx.GetString("command.max.warning");
|
||||||
|
}
|
||||||
|
|
||||||
await ctx.PlayerControl.SendChatToPlayerAsync(msg, ctx.PlayerControl);
|
await ctx.PlayerControl.SendChatToPlayerAsync(msg, ctx.PlayerControl);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -827,7 +827,7 @@ namespace Empostor.Server.Http
|
|||||||
friendCode = c.FriendCode ?? "—",
|
friendCode = c.FriendCode ?? "—",
|
||||||
gameVersion = c.GameVersion.ToString(),
|
gameVersion = c.GameVersion.ToString(),
|
||||||
platform = c.PlatformSpecificData?.Platform.ToString() ?? "Unknown",
|
platform = c.PlatformSpecificData?.Platform.ToString() ?? "Unknown",
|
||||||
platformName = c.PlatformSpecificData?.PlatformName ?? "",
|
platformName = c.PlatformSpecificData?.PlatformName ?? string.Empty,
|
||||||
language = LanguageHelper.GetDisplayName(c.Language),
|
language = LanguageHelper.GetDisplayName(c.Language),
|
||||||
level = level is null or 0 or uint.MaxValue ? (int?)null : (int)level.Value,
|
level = level is null or 0 or uint.MaxValue ? (int?)null : (int)level.Value,
|
||||||
inGame = c.Player != null,
|
inGame = c.Player != null,
|
||||||
|
|||||||
@@ -18,9 +18,14 @@ internal static class AdminTemplateDefaults
|
|||||||
var loginPath = Path.Combine(PagesDir, "login.html");
|
var loginPath = Path.Combine(PagesDir, "login.html");
|
||||||
var adminPath = Path.Combine(PagesDir, "admin.html");
|
var adminPath = Path.Combine(PagesDir, "admin.html");
|
||||||
if (!File.Exists(loginPath))
|
if (!File.Exists(loginPath))
|
||||||
|
{
|
||||||
File.WriteAllText(loginPath, LoginHtml);
|
File.WriteAllText(loginPath, LoginHtml);
|
||||||
|
}
|
||||||
|
|
||||||
if (!File.Exists(adminPath))
|
if (!File.Exists(adminPath))
|
||||||
|
{
|
||||||
File.WriteAllText(adminPath, AdminHtml);
|
File.WriteAllText(adminPath, AdminHtml);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -85,25 +85,25 @@ public sealed class MonitorController : ControllerBase
|
|||||||
public sealed class MonitorStatus
|
public sealed class MonitorStatus
|
||||||
{
|
{
|
||||||
[JsonPropertyName("status")]
|
[JsonPropertyName("status")]
|
||||||
public string Status { get; set; } = "";
|
public string Status { get; set; } = string.Empty;
|
||||||
|
|
||||||
[JsonPropertyName("timestamp")]
|
[JsonPropertyName("timestamp")]
|
||||||
public string Timestamp { get; set; } = "";
|
public string Timestamp { get; set; } = string.Empty;
|
||||||
|
|
||||||
[JsonPropertyName("uptime_seconds")]
|
[JsonPropertyName("uptime_seconds")]
|
||||||
public long UptimeSeconds { get; set; }
|
public long UptimeSeconds { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("uptime_display")]
|
[JsonPropertyName("uptime_display")]
|
||||||
public string UptimeDisplay { get; set; } = "";
|
public string UptimeDisplay { get; set; } = string.Empty;
|
||||||
|
|
||||||
[JsonPropertyName("version")]
|
[JsonPropertyName("version")]
|
||||||
public string Version { get; set; } = "";
|
public string Version { get; set; } = string.Empty;
|
||||||
|
|
||||||
[JsonPropertyName("runtime")]
|
[JsonPropertyName("runtime")]
|
||||||
public string Runtime { get; set; } = "";
|
public string Runtime { get; set; } = string.Empty;
|
||||||
|
|
||||||
[JsonPropertyName("platform")]
|
[JsonPropertyName("platform")]
|
||||||
public string Platform { get; set; } = "";
|
public string Platform { get; set; } = string.Empty;
|
||||||
|
|
||||||
[JsonPropertyName("processors")]
|
[JsonPropertyName("processors")]
|
||||||
public int Processors { get; set; }
|
public int Processors { get; set; }
|
||||||
@@ -127,17 +127,17 @@ public sealed class MonitorStatus
|
|||||||
public sealed class MonitorGame
|
public sealed class MonitorGame
|
||||||
{
|
{
|
||||||
[JsonPropertyName("code")]
|
[JsonPropertyName("code")]
|
||||||
public string Code { get; set; } = "";
|
public string Code { get; set; } = string.Empty;
|
||||||
|
|
||||||
[JsonPropertyName("state")]
|
[JsonPropertyName("state")]
|
||||||
public string State { get; set; } = "";
|
public string State { get; set; } = string.Empty;
|
||||||
|
|
||||||
[JsonPropertyName("map")]
|
[JsonPropertyName("map")]
|
||||||
public string Map { get; set; } = "";
|
public string Map { get; set; } = string.Empty;
|
||||||
|
|
||||||
[JsonPropertyName("player_count")]
|
[JsonPropertyName("player_count")]
|
||||||
public int PlayerCount { get; set; }
|
public int PlayerCount { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("host")]
|
[JsonPropertyName("host")]
|
||||||
public string Host { get; set; } = "";
|
public string Host { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,9 @@ public sealed class PrivacyController : ControllerBase
|
|||||||
public IActionResult GetPrivacy()
|
public IActionResult GetPrivacy()
|
||||||
{
|
{
|
||||||
if (!System.IO.File.Exists(PrivacyFile))
|
if (!System.IO.File.Exists(PrivacyFile))
|
||||||
|
{
|
||||||
return Content(DefaultPrivacyHtml(), "text/html; charset=utf-8");
|
return Content(DefaultPrivacyHtml(), "text/html; charset=utf-8");
|
||||||
|
}
|
||||||
|
|
||||||
var html = System.IO.File.ReadAllText(PrivacyFile);
|
var html = System.IO.File.ReadAllText(PrivacyFile);
|
||||||
return Content(html, "text/html; charset=utf-8");
|
return Content(html, "text/html; charset=utf-8");
|
||||||
@@ -43,15 +45,17 @@ public sealed class PrivacyController : ControllerBase
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var doc = System.Text.Json.JsonDocument.Parse(body);
|
var doc = System.Text.Json.JsonDocument.Parse(body);
|
||||||
var content = doc.RootElement.GetProperty("content").GetString() ?? "";
|
var content = doc.RootElement.GetProperty("content").GetString() ?? string.Empty;
|
||||||
var token = doc.RootElement.GetProperty("token").GetString() ?? "";
|
var token = doc.RootElement.GetProperty("token").GetString() ?? string.Empty;
|
||||||
|
|
||||||
var adminToken = Environment.GetEnvironmentVariable("EMP_HTTP_TOKEN")
|
var adminToken = Environment.GetEnvironmentVariable("EMP_HTTP_TOKEN")
|
||||||
?? Environment.GetEnvironmentVariable("EMP_ADMIN_TOKEN")
|
?? Environment.GetEnvironmentVariable("EMP_ADMIN_TOKEN")
|
||||||
?? "empostor";
|
?? "empostor";
|
||||||
|
|
||||||
if (token != adminToken)
|
if (token != adminToken)
|
||||||
|
{
|
||||||
return Unauthorized(new { error = "Invalid token." });
|
return Unauthorized(new { error = "Invalid token." });
|
||||||
|
}
|
||||||
|
|
||||||
System.IO.File.WriteAllText(PrivacyFile, content);
|
System.IO.File.WriteAllText(PrivacyFile, content);
|
||||||
_logger.LogInformation("PrivacyControllerPrivacy policy updated.");
|
_logger.LogInformation("PrivacyControllerPrivacy policy updated.");
|
||||||
@@ -65,15 +69,24 @@ public sealed class PrivacyController : ControllerBase
|
|||||||
|
|
||||||
private void EnsureFile()
|
private void EnsureFile()
|
||||||
{
|
{
|
||||||
if (_initialized) return;
|
if (_initialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lock (_initLock)
|
lock (_initLock)
|
||||||
{
|
{
|
||||||
if (_initialized) return;
|
if (_initialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
|
|
||||||
if (!Directory.Exists(PagesDir))
|
if (!Directory.Exists(PagesDir))
|
||||||
|
{
|
||||||
Directory.CreateDirectory(PagesDir);
|
Directory.CreateDirectory(PagesDir);
|
||||||
|
}
|
||||||
|
|
||||||
if (!System.IO.File.Exists(PrivacyFile))
|
if (!System.IO.File.Exists(PrivacyFile))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -391,7 +391,7 @@ public sealed class TokenController : ControllerBase
|
|||||||
var putBody = JsonSerializer.SerializeToUtf8Bytes(new NikoPutRequest
|
var putBody = JsonSerializer.SerializeToUtf8Bytes(new NikoPutRequest
|
||||||
{
|
{
|
||||||
ApiKey = _authApiConfig.NikoApiKey,
|
ApiKey = _authApiConfig.NikoApiKey,
|
||||||
FriendCode = "",
|
FriendCode = string.Empty,
|
||||||
});
|
});
|
||||||
|
|
||||||
var putReq = new HttpRequestMessage(HttpMethod.Put, apiUrl)
|
var putReq = new HttpRequestMessage(HttpMethod.Put, apiUrl)
|
||||||
@@ -736,7 +736,7 @@ public sealed class TokenController : ControllerBase
|
|||||||
public required string ApiKey { get; init; }
|
public required string ApiKey { get; init; }
|
||||||
|
|
||||||
[JsonPropertyName("FriendCode")]
|
[JsonPropertyName("FriendCode")]
|
||||||
public string FriendCode { get; init; } = "";
|
public string FriendCode { get; init; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class NikoCreateResponse
|
private sealed class NikoCreateResponse
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ namespace Empostor.Server.Net
|
|||||||
|
|
||||||
if (Player != null && Player.IsHost)
|
if (Player != null && Player.IsHost)
|
||||||
{
|
{
|
||||||
var isHostCheatingAllowed = _antiCheatConfig.AllowCheatingHosts switch {
|
var isHostCheatingAllowed = _antiCheatConfig.AllowCheatingHosts switch
|
||||||
|
{
|
||||||
CheatingHostMode.Always => true,
|
CheatingHostMode.Always => true,
|
||||||
CheatingHostMode.IfRequested => GameVersion.HasDisableServerAuthorityFlag,
|
CheatingHostMode.IfRequested => GameVersion.HasDisableServerAuthorityFlag,
|
||||||
CheatingHostMode.Never => false,
|
CheatingHostMode.Never => false,
|
||||||
|
|||||||
@@ -1008,8 +1008,8 @@ namespace Empostor.Server.Net.Inner.Objects
|
|||||||
{
|
{
|
||||||
if (PlayerInfo.PlayerLevel != level)
|
if (PlayerInfo.PlayerLevel != level)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("{Code} - Player {Name} ({Id}) level changed {Old} -> {New}",
|
_logger.LogInformation("★ {Name} ({Id}) | Level {Old} → {New}",
|
||||||
_game.Code, sender.Client.Name, sender.Client.Id, PlayerInfo.PlayerLevel, level);
|
sender.Client.Name, sender.Client.Id, PlayerInfo.PlayerLevel, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerInfo.PlayerLevel = level;
|
PlayerInfo.PlayerLevel = level;
|
||||||
@@ -1168,7 +1168,9 @@ namespace Empostor.Server.Net.Inner.Objects
|
|||||||
};
|
};
|
||||||
var handled = await _commandService.TryHandleAsync(ctx);
|
var handled = await _commandService.TryHandleAsync(ctx);
|
||||||
if (handled)
|
if (handled)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var @event = new PlayerChatEvent(Game, sender, this, message);
|
var @event = new PlayerChatEvent(Game, sender, this, message);
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ namespace Empostor.Server.Net.Manager
|
|||||||
if (versionCompare == ICompatibilityManager.VersionCompareResult.ServerTooOld
|
if (versionCompare == ICompatibilityManager.VersionCompareResult.ServerTooOld
|
||||||
&& _compatibilityConfig.AllowFutureGameVersions && platformSpecificData != null)
|
&& _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
|
else if (versionCompare != ICompatibilityManager.VersionCompareResult.Compatible
|
||||||
|| platformSpecificData == null)
|
|| platformSpecificData == null)
|
||||||
@@ -134,7 +134,7 @@ namespace Empostor.Server.Net.Manager
|
|||||||
return;
|
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; }
|
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 platformStr = platformSpecificData?.Platform.ToString() ?? "Unknown";
|
||||||
var reactorMods = connection.GetReactorMods();
|
var reactorMods = connection.GetReactorMods();
|
||||||
var reactorStr = reactorMods?.Mods is { Count: > 0 } mods
|
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;
|
: string.Empty;
|
||||||
|
|
||||||
// Primary: match by delta port (nonce)
|
// Primary: match by delta port (nonce)
|
||||||
@@ -184,13 +184,13 @@ namespace Empostor.Server.Net.Manager
|
|||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation(
|
_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);
|
id, name, deltaPort, location, lang, platformStr, friendCode ?? "unknown", HashPuid(authInfo.ProductUserId), reactorStr);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogWarning(
|
_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);
|
id, name, deltaPort);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,13 +203,13 @@ namespace Empostor.Server.Net.Manager
|
|||||||
{
|
{
|
||||||
friendCode = authInfo.FriendCode;
|
friendCode = authInfo.FriendCode;
|
||||||
_logger.LogInformation(
|
_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);
|
id, name, NormalizeIp(clientIp), location, lang, platformStr, friendCode ?? "unknown", HashPuid(authInfo.ProductUserId), reactorStr);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogWarning(
|
_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);
|
id, name, deltaPort, NormalizeIp(clientIp), location, lang, platformStr, reactorStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,7 +220,7 @@ namespace Empostor.Server.Net.Manager
|
|||||||
client.DeltaPort = deltaPort;
|
client.DeltaPort = deltaPort;
|
||||||
|
|
||||||
client.Id = id;
|
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);
|
_clients.TryAdd(id, client);
|
||||||
await _eventManager.CallAsync(new ClientConnectedEvent(connection, client));
|
await _eventManager.CallAsync(new ClientConnectedEvent(connection, client));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ namespace Empostor.Server.Net.State
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogWarning("Couldn't find spawnable object {0}.", objectId);
|
_logger.LogWarning("Couldn't find spawnable object {ObjectId}", objectId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ namespace Empostor.Server.Net.State
|
|||||||
|
|
||||||
public async ValueTask HandleKickPlayer(int playerId, bool isBan)
|
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))
|
using (var kickMsg = MessageWriter.Get(MessageType.Reliable))
|
||||||
{
|
{
|
||||||
WriteKickPlayerMessage(kickMsg, false, playerId, isBan);
|
WriteKickPlayerMessage(kickMsg, false, playerId, isBan);
|
||||||
@@ -121,25 +121,20 @@ namespace Empostor.Server.Net.State
|
|||||||
private async ValueTask HandleJoinGameNew(ClientPlayer sender, bool isNew)
|
private async ValueTask HandleJoinGameNew(ClientPlayer sender, bool isNew)
|
||||||
{
|
{
|
||||||
var client = sender.Client;
|
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 authority = client.GameVersion.HasDisableServerAuthorityFlag ? "true" : "false";
|
||||||
var version = client.GameVersion.ToString();
|
var version = client.GameVersion.ToString();
|
||||||
var hashPuid = client.ProductUserId?.Length >= 9 ? client.ProductUserId[..9] : client.ProductUserId ?? "0";
|
|
||||||
var deltaPort = client.DeltaPort;
|
var deltaPort = client.DeltaPort;
|
||||||
var location = await _ipGeo.GetLocationAsync(clientIp);
|
|
||||||
var lang = LanguageHelper.GetDisplayName(client.Language);
|
var lang = LanguageHelper.GetDisplayName(client.Language);
|
||||||
var platform = client.PlatformSpecificData?.Platform.ToString() ?? "Unknown";
|
var platform = client.PlatformSpecificData?.Platform.ToString() ?? "Unknown";
|
||||||
var platformName = client.PlatformSpecificData?.PlatformName;
|
var platformName = client.PlatformSpecificData?.PlatformName;
|
||||||
var platformStr = string.IsNullOrEmpty(platformName) ? platform : $"{platform}/{platformName}";
|
var platformStr = string.IsNullOrEmpty(platformName) ? platform : $"{platform}/{platformName}";
|
||||||
var reactorMods = client.GetReactorMods();
|
var reactorMods = client.GetReactorMods();
|
||||||
var reactorStr = reactorMods?.Mods is { Count: > 0 } mods
|
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;
|
: 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}",
|
_logger.LogInformation("▶ {Name} ({Id}) joined | {Lang} | {Platform} | v{Version} | Authority:{Authority} | port {DeltaPort}{Reactor}",
|
||||||
Code, sender.Client.Name, hashPuid, sender.Client.Id, lang, platformStr, ipStr, location, remotePort, version, authority, deltaPort, reactorStr);
|
sender.Client.Name, sender.Client.Id, lang, platformStr, version, authority, deltaPort, reactorStr);
|
||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
{
|
{
|
||||||
@@ -229,8 +224,8 @@ namespace Empostor.Server.Net.State
|
|||||||
private async ValueTask HandleJoinGameNext(ClientPlayer sender, bool isNew)
|
private async ValueTask HandleJoinGameNext(ClientPlayer sender, bool isNew)
|
||||||
{
|
{
|
||||||
var authority = sender.Client.GameVersion.HasDisableServerAuthorityFlag ? "true" : "false";
|
var authority = sender.Client.GameVersion.HasDisableServerAuthorityFlag ? "true" : "false";
|
||||||
_logger.LogInformation("{Code} - Player {Name} ({Id}) is rejoining. Player Authority : {Authority}, port: {DeltaPort}",
|
_logger.LogInformation("↻ {Name} ({Id}) rejoined | Authority:{Authority} | port {DeltaPort}",
|
||||||
Code, sender.Client.Name, sender.Client.Id, authority, sender.Client.DeltaPort);
|
sender.Client.Name, sender.Client.Id, authority, sender.Client.DeltaPort);
|
||||||
|
|
||||||
if (isNew)
|
if (isNew)
|
||||||
{
|
{
|
||||||
@@ -239,7 +234,7 @@ namespace Empostor.Server.Net.State
|
|||||||
|
|
||||||
if (sender.Client.Id == HostId)
|
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;
|
GameState = GameStates.NotStarted;
|
||||||
await HandleJoinGameNew(sender, false);
|
await HandleJoinGameNew(sender, false);
|
||||||
await CheckLimboPlayers();
|
await CheckLimboPlayers();
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ namespace Empostor.Server.Net.State
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("{Code} - Player {Name} ({Id}) has left for \"{Reason}\" hashpuid : {HashPuid}",
|
_logger.LogInformation("◀ {Name} ({Id}) left | {Reason} | {HashPuid}",
|
||||||
Code, player.Client.Name, playerId, reason,
|
player.Client.Name, playerId, reason,
|
||||||
player.Client.ProductUserId?.Length >= 9 ? player.Client.ProductUserId[..9] : player.Client.ProductUserId ?? "0");
|
player.Client.ProductUserId?.Length >= 9 ? player.Client.ProductUserId[..9] : player.Client.ProductUserId ?? "0");
|
||||||
if (GameState == GameStates.Starting || GameState == GameStates.Started || GameState == GameStates.NotStarted)
|
if (GameState == GameStates.Starting || GameState == GameStates.Started || GameState == GameStates.NotStarted)
|
||||||
{
|
{
|
||||||
@@ -71,7 +71,7 @@ namespace Empostor.Server.Net.State
|
|||||||
await Task.Delay(_timeoutConfig.ConnectionTimeout);
|
await Task.Delay(_timeoutConfig.ConnectionTimeout);
|
||||||
if (player.Client.Connection.IsConnected && player.Client.Connection is HazelConnection hazel)
|
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);
|
await player.Client.DisconnectAsync(isBan ? DisconnectReason.Banned : DisconnectReason.Kicked);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -103,7 +103,7 @@ namespace Empostor.Server.Net.State
|
|||||||
}
|
}
|
||||||
|
|
||||||
HostId = host.Client.Id;
|
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)
|
if (GameState == GameStates.Ended && host.Limbo == LimboStates.WaitingForHost)
|
||||||
{
|
{
|
||||||
GameState = GameStates.NotStarted;
|
GameState = GameStates.NotStarted;
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ using Empostor.Api.Net.Manager;
|
|||||||
using Empostor.Api.Net.Messages.S2C;
|
using Empostor.Api.Net.Messages.S2C;
|
||||||
using Empostor.Server.Events;
|
using Empostor.Server.Events;
|
||||||
using Empostor.Server.Net.Manager;
|
using Empostor.Server.Net.Manager;
|
||||||
using Empostor.Server.Utils;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
@@ -34,7 +33,6 @@ namespace Empostor.Server.Net.State
|
|||||||
private readonly ICompatibilityManager _compatibilityManager;
|
private readonly ICompatibilityManager _compatibilityManager;
|
||||||
private readonly CompatibilityConfig _compatibilityConfig;
|
private readonly CompatibilityConfig _compatibilityConfig;
|
||||||
private readonly TimeoutConfig _timeoutConfig;
|
private readonly TimeoutConfig _timeoutConfig;
|
||||||
private readonly IpGeolocationService _ipGeo;
|
|
||||||
|
|
||||||
public Game(
|
public Game(
|
||||||
ILogger<Game> logger,
|
ILogger<Game> logger,
|
||||||
@@ -48,8 +46,7 @@ namespace Empostor.Server.Net.State
|
|||||||
IEventManager eventManager,
|
IEventManager eventManager,
|
||||||
ICompatibilityManager compatibilityManager,
|
ICompatibilityManager compatibilityManager,
|
||||||
IOptions<CompatibilityConfig> compatibilityConfig,
|
IOptions<CompatibilityConfig> compatibilityConfig,
|
||||||
IOptions<TimeoutConfig> timeoutConfig,
|
IOptions<TimeoutConfig> timeoutConfig)
|
||||||
IpGeolocationService ipGeo)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
@@ -68,7 +65,6 @@ namespace Empostor.Server.Net.State
|
|||||||
_compatibilityManager = compatibilityManager;
|
_compatibilityManager = compatibilityManager;
|
||||||
_compatibilityConfig = compatibilityConfig.Value;
|
_compatibilityConfig = compatibilityConfig.Value;
|
||||||
_timeoutConfig = timeoutConfig.Value;
|
_timeoutConfig = timeoutConfig.Value;
|
||||||
_ipGeo = ipGeo;
|
|
||||||
Items = new ConcurrentDictionary<object, object>();
|
Items = new ConcurrentDictionary<object, object>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,13 +54,15 @@ namespace Empostor.Server
|
|||||||
private static int Main(string[] args)
|
private static int Main(string[] args)
|
||||||
{
|
{
|
||||||
Log.Logger = new LoggerConfiguration()
|
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<ShortSourceEnricher>()
|
.Enrich.With<ShortSourceEnricher>()
|
||||||
.CreateBootstrapLogger();
|
.CreateBootstrapLogger();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Log.Information("Empostor v{Version} starting", DotnetUtils.Version);
|
Log.Information("Empostor v{Version} starting", DotnetUtils.Version);
|
||||||
CreateHostBuilder(args).Build().Run();
|
var host = CreateHostBuilder(args).Build();
|
||||||
|
StartupBanner.Print(host.Services.GetRequiredService<IConfiguration>());
|
||||||
|
host.Run();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -163,6 +165,7 @@ namespace Empostor.Server
|
|||||||
});
|
});
|
||||||
|
|
||||||
services.AddSingleton<IpGeolocationService>();
|
services.AddSingleton<IpGeolocationService>();
|
||||||
|
services.AddSingleton<IModuleTagRegistry>(ModuleTagRegistry.Instance);
|
||||||
services.AddSingleton<ICompatibilityManager, CompatibilityManager>();
|
services.AddSingleton<ICompatibilityManager, CompatibilityManager>();
|
||||||
services.AddSingleton<ClientManager>();
|
services.AddSingleton<ClientManager>();
|
||||||
services.AddSingleton<IClientManager>(p => p.GetRequiredService<ClientManager>());
|
services.AddSingleton<IClientManager>(p => p.GetRequiredService<ClientManager>());
|
||||||
@@ -255,12 +258,12 @@ namespace Empostor.Server
|
|||||||
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
|
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
|
||||||
.Enrich.FromLogContext()
|
.Enrich.FromLogContext()
|
||||||
.Enrich.With<ShortSourceEnricher>()
|
.Enrich.With<ShortSourceEnricher>()
|
||||||
.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(
|
.WriteTo.File(
|
||||||
"Log/empostor-.log",
|
"Log/empostor-.log",
|
||||||
rollingInterval: RollingInterval.Day,
|
rollingInterval: RollingInterval.Day,
|
||||||
retainedFileCountLimit: 31,
|
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,
|
.ReadFrom.Configuration(context.Configuration,
|
||||||
new ConfigurationReaderOptions(ConfigurationAssemblySource.AlwaysScanDllFiles));
|
new ConfigurationReaderOptions(ConfigurationAssemblySource.AlwaysScanDllFiles));
|
||||||
AssemblyLoadContext.Default.Resolving -= TryLoad;
|
AssemblyLoadContext.Default.Resolving -= TryLoad;
|
||||||
|
|||||||
@@ -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<string, string> 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"),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Thread-safe registry for plugin module tags.
|
||||||
|
/// Plugins inject <see cref="IModuleTagRegistry" /> and call <see cref="Register" /> to assign a short tag (and optional ANSI colour) to their namespace prefix.
|
||||||
|
/// The enricher and console formatter consult this registry at runtime.
|
||||||
|
/// </summary>
|
||||||
|
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<string, string> _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()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Register a namespace → tag mapping.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="namespacePrefix">Full or partial namespace prefix, e.g. "Empostor.Plugin.MyMod".</param>
|
||||||
|
/// <param name="tag">Short tag shown in brackets, e.g. "MyMod" (keep it short).</param>
|
||||||
|
/// <param name="ansiColor">
|
||||||
|
/// Optional ANSI SGR colour code (e.g. "\x1b[92m" for bright green).
|
||||||
|
/// When omitted a colour is auto-assigned from the palette.
|
||||||
|
/// </param>
|
||||||
|
public void Register(string namespacePrefix, string tag, string? ansiColor = null)
|
||||||
|
{
|
||||||
|
var color = ansiColor ?? NextAutoColor();
|
||||||
|
_entries.Add((namespacePrefix, tag, color));
|
||||||
|
_colorCache[tag] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolve a full <c>SourceContext</c> string to a (tag, colour) pair.
|
||||||
|
/// Returns ("Plugin", null) when no registered prefix matches.
|
||||||
|
/// </summary>
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,11 +13,49 @@ namespace Empostor.Server.Utils
|
|||||||
{
|
{
|
||||||
var shortName = fullName[(fullName.LastIndexOf('.') + 1)..];
|
var shortName = fullName[(fullName.LastIndexOf('.') + 1)..];
|
||||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Source", shortName));
|
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Source", shortName));
|
||||||
|
|
||||||
|
var module = GetModuleTag(fullName);
|
||||||
|
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Module", module));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Source", "Empostor"));
|
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";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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<ServerConfig>() ?? new ServerConfig();
|
||||||
|
var http = configuration.GetSection(HttpServerConfig.Section).Get<HttpServerConfig>() ?? new HttpServerConfig();
|
||||||
|
var anticheat = configuration.GetSection(AntiCheatConfig.Section).Get<AntiCheatConfig>() ?? new AntiCheatConfig();
|
||||||
|
var logLevel = configuration.GetValue<string>("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)..];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -786,7 +786,7 @@ public class DtlsConnectionListener : UdpConnectionListener
|
|||||||
await SendHelloVerifyRequest(peerAddress, outgoingSequence, record.Epoch, recordProtection, peer.ProtocolVersion);
|
await SendHelloVerifyRequest(peerAddress, outgoingSequence, record.Epoch, recordProtection, peer.ProtocolVersion);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client is initiating a brand new connection. We need
|
// Client is initiating a brand new connection. We need
|
||||||
// to destroy the existing connection and establish a
|
// to destroy the existing connection and establish a
|
||||||
// new session.
|
// new session.
|
||||||
@@ -881,7 +881,7 @@ public class DtlsConnectionListener : UdpConnectionListener
|
|||||||
serverHelloHandshake.FragmentLength = serverHelloHandshake.Length;
|
serverHelloHandshake.FragmentLength = serverHelloHandshake.Length;
|
||||||
|
|
||||||
var maxCertFragmentSize = peer.Session.Version == 0 ? MaxCertFragmentSizeV0 : MaxCertFragmentSizeV1;
|
var maxCertFragmentSize = peer.Session.Version == 0 ? MaxCertFragmentSizeV0 : MaxCertFragmentSizeV1;
|
||||||
|
|
||||||
/*var certificateData = encodedCertificate;
|
/*var certificateData = encodedCertificate;
|
||||||
var initialCertPadding = Record.Size + Handshake.Handshake.Size + serverHello.Size + Handshake.Handshake.Size;
|
var initialCertPadding = Record.Size + Handshake.Handshake.Size + serverHello.Size + Handshake.Handshake.Size;
|
||||||
var certInitialFragmentSize = Math.Min(certificateData.Length, maxCertFragmentSize - initialCertPadding);
|
var certInitialFragmentSize = Math.Min(certificateData.Length, maxCertFragmentSize - initialCertPadding);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
@@ -165,7 +165,7 @@ public class UPnPHelper : IDisposable
|
|||||||
var typeNode = desc.SelectSingleNode("//tns:device/tns:deviceType/text()", nsMgr);
|
var typeNode = desc.SelectSingleNode("//tns:device/tns:deviceType/text()", nsMgr);
|
||||||
if (typeNode?.Value == null)
|
if (typeNode?.Value == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!typeNode.Value.Contains("InternetGatewayDevice"))
|
if (!typeNode.Value.Contains("InternetGatewayDevice"))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
@@ -34,7 +34,7 @@ public class UdpConnectionListener : NetworkConnectionListener
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public AcceptConnectionCheck AcceptConnection;
|
public AcceptConnectionCheck AcceptConnection;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new UdpConnectionListener for the given <see cref="IPAddress" />, port and <see cref="IPMode" />.
|
/// Creates a new UdpConnectionListener for the given <see cref="IPAddress" />, port and <see cref="IPMode" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
@@ -11,7 +11,7 @@ public class UdpConnectionRateLimit : IDisposable
|
|||||||
{
|
{
|
||||||
// Allow burst to 5 connections.
|
// Allow burst to 5 connections.
|
||||||
// Decrease by 1 every second.
|
// 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;
|
public int FalloffMs { get; private set; } = 1000;
|
||||||
private static readonly ILogger Logger = Log.ForContext<UdpConnectionRateLimit>();
|
private static readonly ILogger Logger = Log.ForContext<UdpConnectionRateLimit>();
|
||||||
@@ -65,7 +65,7 @@ public class UdpConnectionRateLimit : IDisposable
|
|||||||
|
|
||||||
public bool IsAllowed(IPAddress key)
|
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;
|
return false;
|
||||||
|
|
||||||
_connectionCount.AddOrUpdate(key, _ => 1, (_, i) => i + 1);
|
_connectionCount.AddOrUpdate(key, _ => 1, (_, i) => i + 1);
|
||||||
|
|||||||
Reference in new issue
Block a user