feat: Adds robust speed hack detection and movement throttling (#2266)
## Summary
Server-side movement throttle that prevents speed hacking while accurately identifying cheaters with detection of lagging connections.
**Key features:**
- Credit buffer (200ms) absorbs timing jitter from legitimate players
- Movement queue handles larger bursts, draining at proper game-tick intervals
- RTT measurement distinguishes network lag from speed hacks
- Queue depth detection catches ACK-throttled speed hacks (going straight)
## How It Works
**Throttle** (prevention): Movements arriving too early either consume credit or get queued. The queue drains at
correct intervals, so speed hackers can't move faster regardless of what they send.
**Detection** (identification): Combines multiple signals to identify cheaters:
| Signal | What it catches |
|--------|-----------------|
| Queue depth ≥4 sustained | ACK-throttled speed hacks (client limits unacked moves to 5) |
| Movement rate >1.05x | Direction-change speed hacks where timing is visible |
| Stable RTT + high queue | Eliminates false positives from laggy players |
**RTT-Aware Logic:**
- Probes only sent to players actively moving (event-driven, not global loop)
- Stable low-latency + problems = suspicious
- Unstable/high-latency + problems = probably just lag, throttle handles it
## Configuration
```json
{
"movementThrottle.maxCredit": 200,
"movementThrottle.softQueueLimit": 6,
"movementThrottle.hardQueueLimit": 10,
"movementThrottle.debugLogging": false
}
```
This commit is contained in:
parent
6745cf2075
commit
04d438239d
17 changed files with 2474 additions and 74 deletions
|
|
@ -194,6 +194,9 @@ public static class IncomingAccountPackets
|
|||
{
|
||||
var version = state.Version = new ClientVersion(reader.ReadAscii());
|
||||
|
||||
// Record RTT if this is a response to our probe
|
||||
state.RecordRttMeasurement();
|
||||
|
||||
ClientVerification.ClientVersionReceived(state, version);
|
||||
}
|
||||
|
||||
|
|
@ -204,6 +207,9 @@ public static class IncomingAccountPackets
|
|||
int type = reader.ReadUInt16();
|
||||
var version = state.Version = new ClientVersion(reader.ReadAscii());
|
||||
|
||||
// Record RTT if this is a response to our probe
|
||||
state.RecordRttMeasurement();
|
||||
|
||||
ClientVerification.ClientVersionReceived(state, version);
|
||||
}
|
||||
|
||||
|
|
@ -270,7 +276,7 @@ public static class IncomingAccountPackets
|
|||
|
||||
state.SendSupportedFeature();
|
||||
|
||||
state.Sequence = 0;
|
||||
state.ResetMovementState();
|
||||
|
||||
state.SendMobileUpdate(m);
|
||||
state.SendMobileUpdate(m);
|
||||
|
|
|
|||
|
|
@ -91,23 +91,9 @@ public static class IncomingMovementPackets
|
|||
|
||||
var dir = (Direction)reader.ReadByte();
|
||||
int seq = reader.ReadByte();
|
||||
var key = reader.ReadUInt32();
|
||||
var key = reader.ReadUInt32(); // FastWalkStack key - not used (not on EA servers)
|
||||
|
||||
if (state.Sequence == 0 && seq != 0 || !from.Move(dir))
|
||||
{
|
||||
state.SendMovementRej(seq, from);
|
||||
state.Sequence = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
++seq;
|
||||
|
||||
if (seq == 256)
|
||||
{
|
||||
seq = 1;
|
||||
}
|
||||
|
||||
state.Sequence = seq;
|
||||
}
|
||||
// Delegate to MovementThrottle which has full context for timing validation
|
||||
MovementThrottle.ValidateAndQueueMovement(state, from, dir, seq);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -360,7 +360,7 @@ public static class IncomingPlayerPackets
|
|||
|
||||
from.SendEverything();
|
||||
|
||||
state.Sequence = 0;
|
||||
state.ResetMovementState();
|
||||
}
|
||||
|
||||
public static void PingReq(NetState state, SpanReader reader)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue