Add server-side anti-cheat evidence scoring
Some checks failed
build / Linux asan (push) Has been cancelled
build / Linux release (push) Has been cancelled
build / FreeBSD build (push) Has been cancelled

This commit is contained in:
server
2026-04-16 11:18:39 +02:00
parent b4ee8aa5d7
commit e8bcfe06f0
5 changed files with 160 additions and 17 deletions

View File

@@ -347,6 +347,9 @@ void CHARACTER::Initialize()
m_dwLastComboTime = 0;
m_bComboIndex = 0;
m_iComboHackCount = 0;
m_iAntiCheatScore = 0;
m_dwLastAntiCheatScoreTime = 0;
m_dwLastAntiCheatPersistTime = 0;
m_dwSkipComboAttackByTime = 0;
m_dwMountTime = 0;
@@ -7238,6 +7241,68 @@ void CHARACTER::ResetComboHackCount()
m_iComboHackCount = 0;
}
namespace
{
constexpr int kAntiCheatMaxScore = 1000;
constexpr int kAntiCheatDisconnectScore = 100;
constexpr DWORD kAntiCheatDecayIntervalMs = 15000;
constexpr DWORD kAntiCheatPersistIntervalMs = 5000;
}
void CHARACTER::DecayAntiCheatScore(DWORD dwNow)
{
if (0 == m_dwLastAntiCheatScoreTime)
{
m_dwLastAntiCheatScoreTime = dwNow;
return;
}
const DWORD dwElapsed = dwNow - m_dwLastAntiCheatScoreTime;
if (dwElapsed < kAntiCheatDecayIntervalMs)
return;
const int iDecaySteps = dwElapsed / kAntiCheatDecayIntervalMs;
m_iAntiCheatScore = MAX(0, m_iAntiCheatScore - iDecaySteps);
m_dwLastAntiCheatScoreTime += iDecaySteps * kAntiCheatDecayIntervalMs;
}
void CHARACTER::RecordAntiCheatViolation(const char* category, int score, const char* detail, bool forcePersist)
{
const DWORD dwNow = get_dword_time();
DecayAntiCheatScore(dwNow);
if (score <= 0)
score = 1;
m_iAntiCheatScore = MIN(kAntiCheatMaxScore, m_iAntiCheatScore + score);
char szReason[512];
snprintf(szReason,
sizeof(szReason),
"ANTI_CHEAT[%s] score=%d map=%ld pos=(%ld,%ld) detail=%s",
category ? category : "UNKNOWN",
m_iAntiCheatScore,
static_cast<long>(GetMapIndex()),
static_cast<long>(GetX()),
static_cast<long>(GetY()),
detail ? detail : "-");
sys_log(0, "%s pid=%u name=%s", szReason, GetPlayerID(), GetName());
const bool shouldPersist = forcePersist || (GetDesc() && dwNow - m_dwLastAntiCheatPersistTime >= kAntiCheatPersistIntervalMs);
if (shouldPersist && GetDesc())
{
LogManager::instance().HackLog(szReason, this);
m_dwLastAntiCheatPersistTime = dwNow;
}
if (GetDesc() && m_iAntiCheatScore >= kAntiCheatDisconnectScore)
{
if (GetDesc()->DelayedDisconnect(number(3, 8)))
sys_log(0, "ANTI_CHEAT_DISCONNECT: %s score=%d", GetName(), m_iAntiCheatScore);
}
}
void CHARACTER::SkipComboAttackByTime(int interval)
{
m_dwSkipComboAttackByTime = get_dword_time() + interval;