mirror of
https://github.com/Empostor/Empostor.git
synced 2026-10-19 15:51:39 +00:00
Change Log out pit style
This commit is contained in:
1 parent
6ee0a117ce
commit
7dfcec19cf
11 files changed
+212
-17
No files matched your search
@@ -150,6 +150,7 @@
|
||||
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
".NETStandard,Version=v2.0/win-x64": {}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,12 @@
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0"
|
||||
}
|
||||
},
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.28, )",
|
||||
"resolved": "8.0.28",
|
||||
"contentHash": "XMqgVjlLxLqWmEh3c49haXLQwsMNtvo6YscUaqfvEGfg1iA8hnYgkUVq3i9Zu9gKeNKMWiiZKVwZExc/qyEAsQ=="
|
||||
},
|
||||
"StyleCop.Analyzers": {
|
||||
"type": "Direct",
|
||||
"requested": "[1.2.0-beta.556, )",
|
||||
@@ -139,6 +145,7 @@
|
||||
"NextFast.Hazel.Abstractions": {
|
||||
"type": "Project"
|
||||
}
|
||||
}
|
||||
},
|
||||
"net8.0/win-x64": {}
|
||||
}
|
||||
}
|
||||
@@ -37,12 +37,12 @@ namespace Empostor.Server.Http
|
||||
_http = http;
|
||||
_config = config.Value;
|
||||
_pluginLoaderService = pluginLoaderService;
|
||||
_passwordHash = AdminAuthHelper.ComputeHash(_config.Password);
|
||||
_passwordHash = AdminController.ComputeHash(_config.Password);
|
||||
}
|
||||
|
||||
private bool IsAuthenticated()
|
||||
=> Request.Cookies.TryGetValue("empostor_admin", out var v)
|
||||
&& AdminAuthHelper.ConstantTimeEquals(v, _passwordHash);
|
||||
&& AdminController.ConstantTimeEquals(v, _passwordHash);
|
||||
|
||||
[HttpGet("/api/admin/marketplace/plugins")]
|
||||
public async Task<IActionResult> ListPlugins()
|
||||
|
||||
@@ -1146,6 +1146,10 @@ namespace Empostor.Server.Net.Inner.Objects
|
||||
|
||||
private async ValueTask<bool> HandleSendChat(ClientPlayer sender, string message)
|
||||
{
|
||||
var clientId = sender.Client.Id;
|
||||
var playerName = sender.Client.Name;
|
||||
var slot = sender.Character?.PlayerId ?? 0;
|
||||
|
||||
if (message.StartsWith("#"))
|
||||
{
|
||||
var ctx = new CommandContext
|
||||
@@ -1162,7 +1166,10 @@ namespace Empostor.Server.Net.Inner.Objects
|
||||
};
|
||||
var handled = await _commandService.TryHandleAsync(ctx);
|
||||
if (handled)
|
||||
{
|
||||
_logger.LogInformation("[{ClientId}][{Name}][{Slot}][{Name}] => [255]Command:\n [{Message}]", clientId, playerName, slot, message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var @event = new PlayerChatEvent(Game, sender, this, message);
|
||||
@@ -1174,10 +1181,12 @@ namespace Empostor.Server.Net.Inner.Objects
|
||||
await _eventManager.CallAsync(@event);
|
||||
if (@event.IsCancelled)
|
||||
{
|
||||
_logger.LogInformation("[{ClientId}][{Name}][{Slot}][{Name}] => [0]Canceled:\n [{Message}]", clientId, playerName, slot, message);
|
||||
return false;
|
||||
}
|
||||
else if (@event.SendToAllPlayers == false)
|
||||
{
|
||||
_logger.LogInformation("[{ClientId}][{Name}][{Slot}][{Name}] => [255]Command:\n [{Message}]", clientId, playerName, slot, message);
|
||||
if (Game.Host != null)
|
||||
{
|
||||
await SendChatToPlayerAsync(message, Game.Host.Character);
|
||||
@@ -1187,6 +1196,7 @@ namespace Empostor.Server.Net.Inner.Objects
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("[{ClientId}][{Name}][{Slot}][{Name}] => [-1]Public:\n [{Message}]", clientId, playerName, slot, message);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ using Empostor.Server.Events.Client;
|
||||
using Empostor.Server.Net.Factories;
|
||||
using Empostor.Api.Service;
|
||||
using Empostor.Server.Service.Auth;
|
||||
using Empostor.Server.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Next.Hazel;
|
||||
@@ -33,6 +34,7 @@ namespace Empostor.Server.Net.Manager
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly AuthApiConfig _authApiConfig;
|
||||
private readonly PortPoolService _portPool;
|
||||
private readonly IpGeolocationService _ipGeo;
|
||||
private int _idLast;
|
||||
|
||||
public ClientManager(
|
||||
@@ -45,7 +47,8 @@ namespace Empostor.Server.Net.Manager
|
||||
PlayerConnectStore playerConnectStore,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IOptions<AuthApiConfig> authApiConfig,
|
||||
PortPoolService portPool)
|
||||
PortPoolService portPool,
|
||||
IpGeolocationService ipGeo)
|
||||
{
|
||||
_logger = logger;
|
||||
_eventManager = eventManager;
|
||||
@@ -58,6 +61,7 @@ namespace Empostor.Server.Net.Manager
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_authApiConfig = authApiConfig.Value;
|
||||
_portPool = portPool;
|
||||
_ipGeo = ipGeo;
|
||||
|
||||
if (_compatibilityConfig.AllowFutureGameVersions)
|
||||
{
|
||||
@@ -138,6 +142,7 @@ namespace Empostor.Server.Net.Manager
|
||||
string? friendCode = null;
|
||||
UserAuthInfo? authInfo = null;
|
||||
var clientIp = connection.EndPoint?.Address;
|
||||
var location = await _ipGeo.GetLocationAsync(clientIp);
|
||||
|
||||
// Primary: match by delta port (nonce)
|
||||
if (deltaPort > 0)
|
||||
@@ -172,8 +177,8 @@ namespace Empostor.Server.Net.Manager
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Client [{Id}]{Name} from port {Port} is authorized as {FriendCode} {HashPuid}",
|
||||
id, name, deltaPort, friendCode ?? "unknown", HashPuid(authInfo.ProductUserId));
|
||||
"Client [{Id}]{Name} from port {Port} {Location}is authorized as {FriendCode} {HashPuid}",
|
||||
id, name, deltaPort, location, friendCode ?? "unknown", HashPuid(authInfo.ProductUserId));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -191,14 +196,14 @@ namespace Empostor.Server.Net.Manager
|
||||
{
|
||||
friendCode = authInfo.FriendCode;
|
||||
_logger.LogInformation(
|
||||
"Client [{Id}]{Name} from {Ip} is authorized as {FriendCode} {HashPuid}",
|
||||
id, name, NormalizeIp(clientIp), friendCode ?? "unknown", HashPuid(authInfo.ProductUserId));
|
||||
"Client [{Id}]{Name} from {Ip} {Location}is authorized as {FriendCode} {HashPuid}",
|
||||
id, name, NormalizeIp(clientIp), location, friendCode ?? "unknown", HashPuid(authInfo.ProductUserId));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Client [{Id}]{Name}: no auth info found (port={Port}, ip={Ip})",
|
||||
id, name, deltaPort, NormalizeIp(clientIp));
|
||||
"Client [{Id}]{Name}: no auth info found (port={Port}, ip={Ip} {Location})",
|
||||
id, name, deltaPort, NormalizeIp(clientIp), location);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -126,9 +126,10 @@ namespace Empostor.Server.Net.State
|
||||
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);
|
||||
|
||||
_logger.LogInformation("{Code} - Player {Name} ({HashPuid}) ({Id}) is joining from ({Ip}:{RemotePort}) with v{Version}, Authority:{Authority}, port: {DeltaPort}",
|
||||
Code, sender.Client.Name, hashPuid, sender.Client.Id, ipStr, remotePort, version, authority, deltaPort);
|
||||
_logger.LogInformation("{Code} - Player {Name} ({HashPuid}) ({Id}) is joining from ({Ip} {Location}:{RemotePort}) with v{Version}, Authority:{Authority}, port: {DeltaPort}",
|
||||
Code, sender.Client.Name, hashPuid, sender.Client.Id, ipStr, location, remotePort, version, authority, deltaPort);
|
||||
|
||||
if (isNew)
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ 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;
|
||||
|
||||
@@ -33,6 +34,7 @@ namespace Empostor.Server.Net.State
|
||||
private readonly ICompatibilityManager _compatibilityManager;
|
||||
private readonly CompatibilityConfig _compatibilityConfig;
|
||||
private readonly TimeoutConfig _timeoutConfig;
|
||||
private readonly IpGeolocationService _ipGeo;
|
||||
|
||||
public Game(
|
||||
ILogger<Game> logger,
|
||||
@@ -46,7 +48,8 @@ namespace Empostor.Server.Net.State
|
||||
IEventManager eventManager,
|
||||
ICompatibilityManager compatibilityManager,
|
||||
IOptions<CompatibilityConfig> compatibilityConfig,
|
||||
IOptions<TimeoutConfig> timeoutConfig)
|
||||
IOptions<TimeoutConfig> timeoutConfig,
|
||||
IpGeolocationService ipGeo)
|
||||
{
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
@@ -65,6 +68,7 @@ namespace Empostor.Server.Net.State
|
||||
_compatibilityManager = compatibilityManager;
|
||||
_compatibilityConfig = compatibilityConfig.Value;
|
||||
_timeoutConfig = timeoutConfig.Value;
|
||||
_ipGeo = ipGeo;
|
||||
Items = new ConcurrentDictionary<object, object>();
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,8 @@ 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}] {Message:lj}{NewLine}{Exception}")
|
||||
.WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Source} - {Message:lj}{NewLine}{Exception}")
|
||||
.Enrich.With<ShortSourceEnricher>()
|
||||
.CreateBootstrapLogger();
|
||||
try
|
||||
{
|
||||
@@ -156,7 +157,12 @@ namespace Empostor.Server
|
||||
{
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
});
|
||||
services.AddHttpClient("ipgeo", client =>
|
||||
{
|
||||
client.Timeout = TimeSpan.FromSeconds(5);
|
||||
});
|
||||
|
||||
services.AddSingleton<IpGeolocationService>();
|
||||
services.AddSingleton<ICompatibilityManager, CompatibilityManager>();
|
||||
services.AddSingleton<ClientManager>();
|
||||
services.AddSingleton<IClientManager>(p => p.GetRequiredService<ClientManager>());
|
||||
@@ -248,12 +254,13 @@ namespace Empostor.Server
|
||||
#endif
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
|
||||
.Enrich.With<ShortSourceEnricher>()
|
||||
.WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Source} - {Message:lj}{NewLine}{Exception}")
|
||||
.WriteTo.File(
|
||||
"Log/empostor-.log",
|
||||
rollingInterval: RollingInterval.Day,
|
||||
retainedFileCountLimit: 31,
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Source} - {Message:lj}{NewLine}{Exception}")
|
||||
.ReadFrom.Configuration(context.Configuration,
|
||||
new ConfigurationReaderOptions(ConfigurationAssemblySource.AlwaysScanDllFiles));
|
||||
AssemblyLoadContext.Default.Resolving -= TryLoad;
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Empostor.Server.Utils
|
||||
{
|
||||
internal class IpGeolocationService
|
||||
{
|
||||
private const string ApiUrl = "http://ip-api.com/json/{0}?lang=zh-CN&fields=status,country,regionName,city";
|
||||
private static readonly TimeSpan CacheTtl = TimeSpan.FromHours(24);
|
||||
|
||||
private readonly ConcurrentDictionary<IPAddress, (string Location, DateTime Expiry)> _cache = new();
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILogger<IpGeolocationService> _logger;
|
||||
|
||||
public IpGeolocationService(IHttpClientFactory httpClientFactory, ILogger<IpGeolocationService> logger)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async ValueTask<string> GetLocationAsync(IPAddress? ip)
|
||||
{
|
||||
if (ip == null || IsPrivateIp(ip))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Check cache
|
||||
if (_cache.TryGetValue(ip, out var cached) && cached.Expiry > DateTime.UtcNow)
|
||||
{
|
||||
return cached.Location;
|
||||
}
|
||||
|
||||
var location = await QueryApiAsync(ip);
|
||||
_cache[ip] = (location, DateTime.UtcNow.Add(CacheTtl));
|
||||
return location;
|
||||
}
|
||||
|
||||
private async Task<string> QueryApiAsync(IPAddress ip)
|
||||
{
|
||||
try
|
||||
{
|
||||
var normalizedIp = ip.IsIPv4MappedToIPv6 ? ip.MapToIPv4() : ip;
|
||||
var url = string.Format(ApiUrl, normalizedIp);
|
||||
|
||||
using var client = _httpClientFactory.CreateClient("ipgeo");
|
||||
using var cts = new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(3));
|
||||
var json = await client.GetStringAsync(url, cts.Token);
|
||||
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
var root = doc.RootElement;
|
||||
|
||||
if (root.TryGetProperty("status", out var status) && status.GetString() == "success")
|
||||
{
|
||||
var country = root.TryGetProperty("country", out var c) ? c.GetString() ?? string.Empty : string.Empty;
|
||||
var region = root.TryGetProperty("regionName", out var r) ? r.GetString() ?? string.Empty : string.Empty;
|
||||
var city = root.TryGetProperty("city", out var ct) ? ct.GetString() ?? string.Empty : string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(country) && string.IsNullOrEmpty(city))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var parts = country != city
|
||||
? (region != city ? $"{country} {region} {city}" : $"{country} {city}")
|
||||
: country;
|
||||
|
||||
return parts.Trim();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "IP geolocation lookup failed for {Ip}", ip);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsPrivateIp(IPAddress ip)
|
||||
{
|
||||
if (ip.IsIPv4MappedToIPv6)
|
||||
{
|
||||
ip = ip.MapToIPv4();
|
||||
}
|
||||
|
||||
if (ip.AddressFamily != AddressFamily.InterNetwork)
|
||||
{
|
||||
return true; // Don't query non-IPv4 for now
|
||||
}
|
||||
|
||||
var bytes = ip.GetAddressBytes();
|
||||
if (bytes.Length != 4)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 127.0.0.0/8
|
||||
if (bytes[0] == 127)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 10.0.0.0/8
|
||||
if (bytes[0] == 10)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 172.16.0.0/12
|
||||
if (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 192.168.0.0/16
|
||||
if (bytes[0] == 192 && bytes[1] == 168)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Empostor.Server.Utils
|
||||
{
|
||||
internal class ShortSourceEnricher : ILogEventEnricher
|
||||
{
|
||||
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||
{
|
||||
if (logEvent.Properties.TryGetValue("SourceContext", out var sourceContextValue) &&
|
||||
sourceContextValue is ScalarValue scalar &&
|
||||
scalar.Value is string fullName)
|
||||
{
|
||||
var shortName = fullName[(fullName.LastIndexOf('.') + 1)..];
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Source", shortName));
|
||||
}
|
||||
else
|
||||
{
|
||||
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Source", "Empostor"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,12 @@
|
||||
"resolved": "8.0.1",
|
||||
"contentHash": "X21b8D6z58EG34OMLH955750mFaEaGs5xLGe9Vyi3059Cgb3sk+p2k/JlQXaieEBRrk+SVlFzDei3Q6PdVzJOA=="
|
||||
},
|
||||
"Microsoft.NET.ILLink.Tasks": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.28, )",
|
||||
"resolved": "8.0.28",
|
||||
"contentHash": "XMqgVjlLxLqWmEh3c49haXLQwsMNtvo6YscUaqfvEGfg1iA8hnYgkUVq3i9Zu9gKeNKMWiiZKVwZExc/qyEAsQ=="
|
||||
},
|
||||
"Serilog.Extensions.Hosting": {
|
||||
"type": "Direct",
|
||||
"requested": "[8.0.0, )",
|
||||
|
||||
Reference in new issue
Block a user