diff --git a/src/Empostor.Api/Config/AntiCheatConfig.cs b/src/Empostor.Api/Config/AntiCheatConfig.cs index 82f5f2d..c7544c7 100644 --- a/src/Empostor.Api/Config/AntiCheatConfig.cs +++ b/src/Empostor.Api/Config/AntiCheatConfig.cs @@ -38,6 +38,8 @@ namespace Empostor.Api.Config public bool EnableRateLimits { get; set; } = true; + public bool EnableDuplicateLoginCheck { get; set; } = true; + public bool ForbidProtocolExtensions { get; set; } = true; public bool EnablePacketSizeChecks { get; set; } = true; diff --git a/src/Empostor.Api/Games/GameJoinError.cs b/src/Empostor.Api/Games/GameJoinError.cs index a516a07..f6842a4 100644 --- a/src/Empostor.Api/Games/GameJoinError.cs +++ b/src/Empostor.Api/Games/GameJoinError.cs @@ -54,5 +54,10 @@ /// A custom message can be set in . /// Custom, + + /// + /// The same account is already playing in this game. + /// + DuplicateConnection, } } diff --git a/src/Empostor.Server/Net/Client.cs b/src/Empostor.Server/Net/Client.cs index 209366e..c94888e 100644 --- a/src/Empostor.Server/Net/Client.cs +++ b/src/Empostor.Server/Net/Client.cs @@ -221,6 +221,9 @@ namespace Empostor.Server.Net case GameJoinError.Custom: await DisconnectAsync(DisconnectReason.Custom, result.Message); break; + case GameJoinError.DuplicateConnection: + await DisconnectAsync(DisconnectReason.DuplicateConnectionDetected); + break; default: await DisconnectAsync(DisconnectReason.Custom, "Unknown error."); break; diff --git a/src/Empostor.Server/Net/State/Game.Incoming.cs b/src/Empostor.Server/Net/State/Game.Incoming.cs index b7bde99..b1a8407 100644 --- a/src/Empostor.Server/Net/State/Game.Incoming.cs +++ b/src/Empostor.Server/Net/State/Game.Incoming.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Empostor.Api.Config; using Empostor.Api.Games; using Empostor.Api.Net; using Next.Hazel; @@ -118,8 +119,7 @@ namespace Empostor.Server.Net.State return GameJoinResult.FromError(GameJoinError.InvalidClient); } - private async ValueTask HandleJoinGameNew(ClientPlayer sender, bool isNew) - { + private async ValueTask HandleJoinGameNew(ClientPlayer sender, bool isNew) { var client = sender.Client; var authority = client.GameVersion.HasDisableServerAuthorityFlag ? "true" : "false"; var version = client.GameVersion.ToString(); @@ -155,6 +155,29 @@ namespace Empostor.Server.Net.State } } + private bool IsDuplicateAccount(ClientBase client) + { + if (string.IsNullOrEmpty(client.ProductUserId)) + { + return false; + } + + foreach (var other in _players.Values) + { + if (other.Client == client) + { + continue; + } + + if (string.Equals(other.Client.ProductUserId, client.ProductUserId, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + private async ValueTask AddClientSafeAsync(ClientBase client) { if (_bannedIps.Contains(client.Connection.EndPoint.Address)) @@ -163,6 +186,15 @@ namespace Empostor.Server.Net.State } var player = client.Player; + if (AntiCheat.Config.EnableDuplicateLoginCheck && IsDuplicateAccount(client)) + { + _logger.LogWarning( + "#{Id} {Name} rejected: account {Puid} is already playing in this game", + client.Id, client.Name, client.ProductUserId); + + return GameJoinResult.FromError(GameJoinError.DuplicateConnection); + } + if (_compatibilityConfig.AllowVersionMixing == false && this.Host != null && client.GameVersion != this.Host.Client.GameVersion) { diff --git a/src/Empostor.Server/config.json b/src/Empostor.Server/config.json index fa15008..4f8486a 100644 --- a/src/Empostor.Server/config.json +++ b/src/Empostor.Server/config.json @@ -32,6 +32,7 @@ "EnableMeetingChecks": true, "EnableVotingChecks": true, "EnableRateLimits": true, + "EnableDuplicateLoginCheck": true, "ForbidProtocolExtensions": true, "EnablePacketSizeChecks": true, "PacketSizeLimit": 1203