mirror of
https://github.com/Empostor/Empostor.git
synced 2026-10-19 15:51:39 +00:00
Update Anticheat
This commit is contained in:
1 parent
4609653438
commit
4988b11fd2
11 files changed
+204
-127
No files matched your search
@@ -47,8 +47,8 @@ public enum CheatCategory
|
||||
/// <summary>A vote cast outside a meeting, or by a player who is already dead.</summary>
|
||||
Voting,
|
||||
|
||||
/// <summary>Vent and pipe use the current game state cannot produce.</summary>
|
||||
Venting,
|
||||
/// <summary>A client sent more messages in a window than the room can legitimately produce.</summary>
|
||||
RateLimit,
|
||||
|
||||
/// <summary>Legacy category for unsorted anticheat checks.</summary>
|
||||
Other,
|
||||
|
||||
@@ -36,18 +36,12 @@ namespace Empostor.Api.Config
|
||||
|
||||
public bool EnableVotingChecks { get; set; } = true;
|
||||
|
||||
public bool EnableVentExploitCheck { get; set; } = false;
|
||||
public bool EnableRateLimits { get; set; } = true;
|
||||
|
||||
public bool ForbidProtocolExtensions { get; set; } = true;
|
||||
|
||||
public bool EnablePacketSizeChecks { get; set; } = true;
|
||||
|
||||
public int PacketSizeLimit { get; set; } = 1203;
|
||||
|
||||
public bool EnableIpRateLimit { get; set; } = true;
|
||||
|
||||
public int IpRateLimitRequests { get; set; } = 30;
|
||||
|
||||
public int IpRateLimitWindowMinutes { get; set; } = 1;
|
||||
}
|
||||
}
|
||||
@@ -5,16 +5,15 @@ using Empostor.Api.Config;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Empostor.Server.Http;
|
||||
|
||||
// Per-IP request counter used by the anticheat "Ip request rate limit".
|
||||
// Every real IP gets a fixed window (default 5 minutes); when the IP sends
|
||||
// more TCP (HTTP) requests than configured (default 30) it is blocked until
|
||||
// the window slides away.
|
||||
public sealed class IpRateLimitService
|
||||
{
|
||||
// Language key of the localized hint, see Languages/*.json.
|
||||
public const string MessageKey = "ratelimit.too_frequent";
|
||||
|
||||
public const int MaxRequestsPerWindow = 30;
|
||||
|
||||
public const int WindowMinutes = 1;
|
||||
|
||||
private static readonly TimeSpan CleanupInterval = TimeSpan.FromSeconds(60);
|
||||
|
||||
private readonly IOptions<AntiCheatConfig> _options;
|
||||
@@ -34,7 +33,7 @@ public sealed class IpRateLimitService
|
||||
|
||||
var config = _options.Value;
|
||||
var window = GetWindow(config);
|
||||
if (ip == null || window == null || config.IpRateLimitRequests <= 0)
|
||||
if (ip == null || window == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -54,7 +53,7 @@ public sealed class IpRateLimitService
|
||||
}
|
||||
|
||||
entry.Count++;
|
||||
if (entry.Count <= config.IpRateLimitRequests)
|
||||
if (entry.Count <= MaxRequestsPerWindow)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -79,7 +78,7 @@ public sealed class IpRateLimitService
|
||||
|
||||
var config = _options.Value;
|
||||
var window = GetWindow(config);
|
||||
if (ip == null || window == null || config.IpRateLimitRequests <= 0)
|
||||
if (ip == null || window == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -92,7 +91,7 @@ public sealed class IpRateLimitService
|
||||
var now = DateTime.UtcNow;
|
||||
lock (entry)
|
||||
{
|
||||
if (now - entry.StartUtc >= window.Value || entry.Count <= config.IpRateLimitRequests)
|
||||
if (now - entry.StartUtc >= window.Value || entry.Count <= MaxRequestsPerWindow)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -109,12 +108,12 @@ public sealed class IpRateLimitService
|
||||
|
||||
private static TimeSpan? GetWindow(AntiCheatConfig config)
|
||||
{
|
||||
if (!config.EnableIpRateLimit)
|
||||
if (!config.EnableRateLimits)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return TimeSpan.FromMinutes(Math.Max(1, config.IpRateLimitWindowMinutes));
|
||||
return TimeSpan.FromMinutes(WindowMinutes);
|
||||
}
|
||||
|
||||
// Whole minutes to wait, always at least one so the hint never says
|
||||
|
||||
@@ -20,8 +20,19 @@ namespace Empostor.Server.Net.Anticheat
|
||||
/// <summary>Gets or sets the system of the last sabotage this player triggered.</summary>
|
||||
public int LastSabotageSystem { get; set; } = -1;
|
||||
|
||||
/// <summary>Gets or sets the moment of the vent in of a possible vent kick exploit.</summary>
|
||||
public double VentExploitPendingAt { get; set; } = -1;
|
||||
private RateLimitWindow _rpcWindow;
|
||||
|
||||
private RateLimitWindow _taskWindow;
|
||||
|
||||
public bool CountRpc(double now, int maxPerSecond)
|
||||
{
|
||||
return _rpcWindow.Hit(now, maxPerSecond, 1);
|
||||
}
|
||||
|
||||
public bool CountTask(double now, int maxCount, double windowSeconds)
|
||||
{
|
||||
return _taskWindow.Hit(now, maxCount, windowSeconds);
|
||||
}
|
||||
|
||||
public void NoteKill(byte victimId, double now)
|
||||
{
|
||||
@@ -37,7 +48,6 @@ namespace Empostor.Server.Net.Anticheat
|
||||
DeadTargetKills = 0;
|
||||
LastSabotageAt = -1;
|
||||
LastSabotageSystem = -1;
|
||||
VentExploitPendingAt = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
namespace Empostor.Server.Net.Anticheat
|
||||
{
|
||||
internal struct RateLimitWindow
|
||||
{
|
||||
private const int WindowSize = 64;
|
||||
|
||||
private readonly double[] _stamps;
|
||||
|
||||
private int _next;
|
||||
|
||||
public RateLimitWindow()
|
||||
{
|
||||
_stamps = new double[WindowSize];
|
||||
}
|
||||
|
||||
public bool Hit(double now, int maxCount, double windowSeconds)
|
||||
{
|
||||
if (maxCount <= 0 || windowSeconds <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var used = 0;
|
||||
for (var i = 0; i < WindowSize; i++)
|
||||
{
|
||||
if (_stamps[i] > 0 && now - _stamps[i] < windowSeconds && ++used >= maxCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_stamps[_next] = now;
|
||||
_next = (_next + 1) % WindowSize;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ namespace Empostor.Server.Net
|
||||
CheatCategory.Sabotage => _antiCheatConfig.EnableSabotageChecks,
|
||||
CheatCategory.Meeting => _antiCheatConfig.EnableMeetingChecks,
|
||||
CheatCategory.Voting => _antiCheatConfig.EnableVotingChecks,
|
||||
CheatCategory.Venting => _antiCheatConfig.EnableVentExploitCheck,
|
||||
CheatCategory.RateLimit => _antiCheatConfig.EnableRateLimits,
|
||||
CheatCategory.Other => true,
|
||||
_ => LogUnknownCategory(category),
|
||||
};
|
||||
|
||||
@@ -7,11 +7,18 @@ using Empostor.Api.Net.Inner;
|
||||
using Empostor.Api.Net.Inner.Objects;
|
||||
using Empostor.Server.Net.Anticheat;
|
||||
using Empostor.Server.Net.Inner.Objects;
|
||||
using Empostor.Server.Net.State;
|
||||
|
||||
namespace Empostor.Server.Net.Inner
|
||||
{
|
||||
internal abstract partial class InnerNetObject
|
||||
{
|
||||
private const int RpcRateLimitPerSecond = 20;
|
||||
|
||||
private const int TaskRateLimitCount = 3;
|
||||
|
||||
private const double TaskRateLimitWindowSeconds = 2;
|
||||
|
||||
protected async ValueTask<bool> ValidateOwnership(CheatContext context, IClientPlayer sender)
|
||||
{
|
||||
if (!sender.IsOwner(this))
|
||||
@@ -137,7 +144,7 @@ namespace Empostor.Server.Net.Inner
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async ValueTask<bool> ValidateMurderTiming(CheatContext context, IClientPlayer sender, byte killerPlayerId, IInnerPlayerControl? target)
|
||||
protected async ValueTask<bool> ValidateMurder(CheatContext context, IClientPlayer sender, byte killerPlayerId, IInnerPlayerControl? target)
|
||||
{
|
||||
if (!Game.AntiCheat.Config.EnableMurderChecks || target == null)
|
||||
{
|
||||
@@ -170,34 +177,23 @@ namespace Empostor.Server.Net.Inner
|
||||
return true;
|
||||
}
|
||||
|
||||
var now = AntiCheatState.Now;
|
||||
var window = options.KillCooldown / 2;
|
||||
|
||||
if (IsFastKill(timeline, target.PlayerId, window, AntiCheatState.Now, out var reason))
|
||||
if (window > 0 &&
|
||||
timeline.LastKillAt >= 0 &&
|
||||
timeline.LastKillTarget != target.PlayerId &&
|
||||
now - timeline.LastKillAt < window)
|
||||
{
|
||||
return await sender.Client.ReportCheatAsync(context, CheatCategory.Murder, reason!);
|
||||
return await sender.Client.ReportCheatAsync(
|
||||
context,
|
||||
CheatCategory.Murder,
|
||||
$"Killed {target.PlayerId} only {now - timeline.LastKillAt:0.###}s after killing {timeline.LastKillTarget}");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsFastKill(PlayerTimeline timeline, byte victimId, double windowSeconds, double now, out string? reason)
|
||||
{
|
||||
reason = null;
|
||||
|
||||
if (windowSeconds <= 0 || timeline.LastKillAt < 0 || timeline.LastKillTarget == victimId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (now - timeline.LastKillAt >= windowSeconds)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
reason = $"Killed {victimId} only {now - timeline.LastKillAt:0.###}s after killing {timeline.LastKillTarget}";
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async ValueTask<bool> ValidateMeetingTiming(CheatContext context, IClientPlayer sender)
|
||||
{
|
||||
if (!Game.AntiCheat.ShipLoaded)
|
||||
@@ -231,9 +227,14 @@ namespace Empostor.Server.Net.Inner
|
||||
|
||||
try
|
||||
{
|
||||
if (!TryReadUpdateSystem(reader, out var systemType, out var playerControl, out var isVent, out var sequenceId, out var state, out var ventId))
|
||||
if (!TryReadUpdateSystem(reader, out var systemType, out var playerControl, out var isSabotageRoute, out var state))
|
||||
{
|
||||
return await sender.Client.ReportCheatAsync(context, CheatCategory.ProtocolExtension, "Client sent a malformed UpdateSystem RPC");
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.ProtocolExtension, "Client sent a malformed UpdateSystem RPC"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!sender.IsHost && !playerControl.IsOwnedBy(sender))
|
||||
@@ -246,22 +247,22 @@ namespace Empostor.Server.Net.Inner
|
||||
|
||||
var actorId = ResolveActorId(playerControl, sender);
|
||||
|
||||
if (isVent)
|
||||
{
|
||||
return await ValidateVentExploit(context, sender, actorId, sequenceId, state, ventId);
|
||||
}
|
||||
|
||||
if (!Game.AntiCheat.ShipGraceOver)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!await ValidateSystemAmount(context, sender, systemType, state))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!await ValidateSabotageInMeeting(context, sender, systemType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!await ValidateRapidSabotage(context, sender, actorId, systemType))
|
||||
if (!await ValidateRapidSabotage(context, sender, actorId, systemType, state))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -275,38 +276,22 @@ namespace Empostor.Server.Net.Inner
|
||||
}
|
||||
}
|
||||
|
||||
protected async ValueTask<bool> ValidateVentExploit(CheatContext context, IClientPlayer sender, byte playerId, ushort sequenceId, byte state, byte ventId)
|
||||
protected async ValueTask<bool> ValidateSystemAmount(CheatContext context, IClientPlayer sender, SystemTypes system, byte amount)
|
||||
{
|
||||
if (!Game.AntiCheat.Config.EnableVentExploitCheck)
|
||||
if (system != SystemTypes.Electrical || (amount & 0x80) != 0 || amount <= 4)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const byte EnterVentState = 2;
|
||||
const byte BootFromVentState = 5;
|
||||
const ushort ExploitSequenceId = 1;
|
||||
|
||||
var timeline = Game.AntiCheat.For(playerId);
|
||||
var now = AntiCheatState.Now;
|
||||
|
||||
if (state == EnterVentState && sequenceId == 0 && ventId == 0)
|
||||
if (await sender.Client.ReportCheatAsync(
|
||||
context,
|
||||
CheatCategory.ProtocolExtension,
|
||||
$"Client sent an out of range state {amount} for {system}"))
|
||||
{
|
||||
timeline.VentExploitPendingAt = now;
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (state != BootFromVentState || sequenceId != ExploitSequenceId || ventId != 0 ||
|
||||
timeline.VentExploitPendingAt < 0 ||
|
||||
now - timeline.VentExploitPendingAt > 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
timeline.VentExploitPendingAt = -1;
|
||||
return await sender.Client.ReportCheatAsync(
|
||||
context,
|
||||
CheatCategory.Venting,
|
||||
"Client sent the vent kick exploit pair (vent 0, sequence id 0 then 1)");
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async ValueTask<bool> ValidateSabotageInMeeting(CheatContext context, IClientPlayer sender, SystemTypes system)
|
||||
@@ -316,15 +301,20 @@ namespace Empostor.Server.Net.Inner
|
||||
return true;
|
||||
}
|
||||
|
||||
return await sender.Client.ReportCheatAsync(
|
||||
context,
|
||||
CheatCategory.Sabotage,
|
||||
$"Client sabotaged {system} while a meeting is in progress");
|
||||
if (await sender.Client.ReportCheatAsync(
|
||||
context,
|
||||
CheatCategory.Sabotage,
|
||||
$"Client sabotaged {system} while a meeting is in progress"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async ValueTask<bool> ValidateRapidSabotage(CheatContext context, IClientPlayer sender, byte playerId, SystemTypes system)
|
||||
protected async ValueTask<bool> ValidateRapidSabotage(CheatContext context, IClientPlayer sender, byte playerId, SystemTypes system, byte state)
|
||||
{
|
||||
if ((int)system == 16)
|
||||
if ((int)system == 16 || (state & 0x80) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -333,13 +323,19 @@ namespace Empostor.Server.Net.Inner
|
||||
var now = AntiCheatState.Now;
|
||||
|
||||
if (timeline.LastSabotageAt >= 0 &&
|
||||
now - timeline.LastSabotageAt < 0.1 &&
|
||||
now - timeline.LastSabotageAt < 3 &&
|
||||
timeline.LastSabotageSystem != (int)system)
|
||||
{
|
||||
var reason = $"Sabotaged {system} {(now - timeline.LastSabotageAt) * 1000:0.###}ms after {timeline.LastSabotageSystem}";
|
||||
var reason = $"Sabotaged {system} {now - timeline.LastSabotageAt:0.###}s after {timeline.LastSabotageSystem}";
|
||||
timeline.LastSabotageAt = now;
|
||||
timeline.LastSabotageSystem = (int)system;
|
||||
return await sender.Client.ReportCheatAsync(context, CheatCategory.Sabotage, reason);
|
||||
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.Sabotage, reason))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
timeline.LastSabotageAt = now;
|
||||
@@ -364,9 +360,13 @@ namespace Empostor.Server.Net.Inner
|
||||
? $"Non-impostor started a sabotage of {system} (state 0x{state:X2})"
|
||||
: $"Non-impostor triggered {system}";
|
||||
|
||||
return await sender.Client.ReportCheatAsync(context, CheatCategory.Sabotage, reason);
|
||||
}
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.Sabotage, reason))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
protected async ValueTask<bool> ValidateVoteCast(CheatContext context, IClientPlayer sender)
|
||||
{
|
||||
if (!Game.AntiCheat.Config.EnableVotingChecks)
|
||||
@@ -419,6 +419,56 @@ namespace Empostor.Server.Net.Inner
|
||||
$"Client sent a VotingComplete with {voters} voters (max {limit})");
|
||||
}
|
||||
|
||||
protected async ValueTask<bool> ValidateTaskCompletion(CheatContext context, IClientPlayer sender, byte playerId)
|
||||
{
|
||||
if (!Game.AntiCheat.Config.EnableRateLimits)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var config = Game.AntiCheat.Config;
|
||||
if (!Game.AntiCheat.For(playerId).CountTask(AntiCheatState.Now, TaskRateLimitCount, TaskRateLimitWindowSeconds))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return await sender.Client.ReportCheatAsync(
|
||||
context,
|
||||
CheatCategory.RateLimit,
|
||||
$"Client completed more than {TaskRateLimitCount} tasks in {TaskRateLimitWindowSeconds}s");
|
||||
}
|
||||
|
||||
internal static async ValueTask<bool> ValidateRpcRate(Game game, CheatContext context, IClientPlayer sender, InnerNetObject obj, bool toPlayer)
|
||||
{
|
||||
if (!game.AntiCheat.Config.EnableRateLimits)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var ownerId = obj.OwnerId >= 0 ? obj.OwnerId : sender.Client.Id;
|
||||
|
||||
if (!toPlayer && ownerId != sender.Client.Id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var playerId = game.GetClientPlayer(ownerId)?.Character?.PlayerId;
|
||||
if (playerId == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!game.AntiCheat.For(playerId.Value).CountRpc(AntiCheatState.Now, RpcRateLimitPerSecond))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return await sender.Client.ReportCheatAsync(
|
||||
context,
|
||||
CheatCategory.RateLimit,
|
||||
$"Client sent more than {RpcRateLimitPerSecond} RPCs in one second");
|
||||
}
|
||||
|
||||
internal static async ValueTask<bool> ValidateGameDataTag(CheatContext context, IClientPlayer sender, byte tag)
|
||||
{
|
||||
if (await sender.Client.ReportCheatAsync(context, CheatCategory.ProtocolExtension, $"Client sent an unknown game data tag {tag}"))
|
||||
@@ -433,17 +483,13 @@ namespace Empostor.Server.Net.Inner
|
||||
IMessageReader reader,
|
||||
out SystemTypes systemType,
|
||||
out InnerPlayerControl playerControl,
|
||||
out bool isVent,
|
||||
out ushort sequenceId,
|
||||
out byte state,
|
||||
out byte ventId)
|
||||
out bool isSabotageRoute,
|
||||
out byte state)
|
||||
{
|
||||
systemType = SystemTypes.Hallway;
|
||||
systemType = default;
|
||||
playerControl = null!;
|
||||
isVent = false;
|
||||
sequenceId = 0;
|
||||
isSabotageRoute = false;
|
||||
state = 0;
|
||||
ventId = byte.MaxValue;
|
||||
|
||||
if (reader.Position >= reader.Length)
|
||||
{
|
||||
@@ -460,36 +506,20 @@ namespace Empostor.Server.Net.Inner
|
||||
|
||||
playerControl = control;
|
||||
|
||||
if (systemType == SystemTypes.Ventilation)
|
||||
{
|
||||
if (reader.Length - reader.Position < 4)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
isVent = true;
|
||||
sequenceId = reader.ReadUInt16();
|
||||
state = reader.ReadByte();
|
||||
ventId = reader.ReadByte();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (reader.Position >= reader.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
state = reader.ReadByte();
|
||||
|
||||
if (systemType == SystemTypes.Sabotage)
|
||||
{
|
||||
if (reader.Length - reader.Position < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
systemType = (SystemTypes)reader.ReadByte();
|
||||
isSabotageRoute = true;
|
||||
systemType = (SystemTypes)state;
|
||||
state = 0x80;
|
||||
}
|
||||
|
||||
state = reader.ReadByte();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Empostor.Api;
|
||||
@@ -616,6 +616,11 @@ namespace Empostor.Server.Net.Inner.Objects
|
||||
|
||||
private async ValueTask HandleCompleteTask(ClientPlayer sender, uint taskId)
|
||||
{
|
||||
if (!await ValidateTaskCompletion(RpcCalls.CompleteTask, sender, PlayerId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TaskInfo? task = null;
|
||||
if (PlayerInfo == null)
|
||||
{
|
||||
@@ -1055,7 +1060,7 @@ namespace Empostor.Server.Net.Inner.Objects
|
||||
IsMurdering = target;
|
||||
}
|
||||
|
||||
if (!await ValidateMurderTiming(RpcCalls.CheckMurder, sender, PlayerId, target))
|
||||
if (!await ValidateMurder(RpcCalls.CheckMurder, sender, PlayerId, target))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -338,7 +338,7 @@ namespace Empostor.Server.Net
|
||||
// requests were counted with).
|
||||
private string BuildRateLimitMessage(IPAddress? ip, Language language)
|
||||
{
|
||||
var minutes = Math.Max(1, _antiCheatOptions.Value.IpRateLimitWindowMinutes);
|
||||
var minutes = IpRateLimitService.WindowMinutes;
|
||||
|
||||
if (ip != null && _rateLimit.IsLimited(ip, out var retryAfter))
|
||||
{
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace Empostor.Server.Net.State
|
||||
|
||||
try
|
||||
{
|
||||
result = await HandleGameDataInnerAsync(reader, sender, target);
|
||||
result = await HandleGameDataInnerAsync(reader, sender, target, toPlayer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -150,7 +150,7 @@ namespace Empostor.Server.Net.State
|
||||
return parent.Length > startPosition;
|
||||
}
|
||||
|
||||
private async ValueTask<GameDataResult> HandleGameDataInnerAsync(IMessageReader reader, ClientPlayer sender, ClientPlayer? target)
|
||||
private async ValueTask<GameDataResult> HandleGameDataInnerAsync(IMessageReader reader, ClientPlayer sender, ClientPlayer? target, bool toPlayer)
|
||||
{
|
||||
switch (reader.Tag)
|
||||
{
|
||||
@@ -174,6 +174,11 @@ namespace Empostor.Server.Net.State
|
||||
var netId = reader.ReadPackedUInt32();
|
||||
if (_allObjects.TryGetValue(netId, out var obj))
|
||||
{
|
||||
if (!await InnerNetObject.ValidateRpcRate(this, new CheatContext(nameof(GameDataTag.RpcFlag)), sender, obj, toPlayer))
|
||||
{
|
||||
return GameDataResult.Abort;
|
||||
}
|
||||
|
||||
if (!await obj.HandleRpcAsync(sender, target, (RpcCalls)reader.ReadByte(), reader))
|
||||
{
|
||||
return GameDataResult.Remove;
|
||||
|
||||
@@ -31,13 +31,10 @@
|
||||
"EnableSabotageChecks": true,
|
||||
"EnableMeetingChecks": true,
|
||||
"EnableVotingChecks": true,
|
||||
"EnableVentExploitCheck": false,
|
||||
"EnableRateLimits": true,
|
||||
"ForbidProtocolExtensions": true,
|
||||
"EnablePacketSizeChecks": true,
|
||||
"PacketSizeLimit": 1203,
|
||||
"EnableIpRateLimit": true,
|
||||
"IpRateLimitRequests": 30,
|
||||
"IpRateLimitWindowMinutes": 1
|
||||
"PacketSizeLimit": 1203
|
||||
},
|
||||
"Timeout": {
|
||||
"SpawnTimeout": 5000,
|
||||
|
||||
Reference in new issue
Block a user