From c2dc3b72ff99a81ff2583dde10bb98800714b70d Mon Sep 17 00:00:00 2001 From: server Date: Thu, 16 Apr 2026 15:13:26 +0200 Subject: [PATCH] Add anti-cheat review queue logging --- src/game/char.cpp | 54 +++++++++++++++++++++++++++++++++++++++++++++ src/game/char.h | 5 +++++ src/game/config.cpp | 32 +++++++++++++++++++++++++++ src/game/config.h | 5 +++++ src/game/main.cpp | 8 +++++++ 5 files changed, 104 insertions(+) diff --git a/src/game/char.cpp b/src/game/char.cpp index 25e9ce7..c70b5e7 100644 --- a/src/game/char.cpp +++ b/src/game/char.cpp @@ -382,8 +382,12 @@ void CHARACTER::Initialize() m_bComboIndex = 0; m_iComboHackCount = 0; m_iAntiCheatScore = 0; + m_iAntiCheatPeakScore = 0; + m_iAntiCheatLastReviewScore = 0; m_dwLastAntiCheatScoreTime = 0; m_dwLastAntiCheatPersistTime = 0; + m_dwLastAntiCheatReviewTime = 0; + m_iAntiCheatReviewCount = 0; m_dwSkipComboAttackByTime = 0; m_dwMountTime = 0; @@ -7473,6 +7477,7 @@ void CHARACTER::RecordAntiCheatViolation(const char* category, int score, const score = 1; m_iAntiCheatScore = MIN(kAntiCheatMaxScore, m_iAntiCheatScore + score); + m_iAntiCheatPeakScore = MAX(m_iAntiCheatPeakScore, m_iAntiCheatScore); char szReason[512]; snprintf(szReason, @@ -7494,6 +7499,8 @@ void CHARACTER::RecordAntiCheatViolation(const char* category, int score, const m_dwLastAntiCheatPersistTime = dwNow; } + MaybeQueueAntiCheatReview(dwNow, category, detail); + if (AntiCheatDisconnectEnabled() && GetDesc() && m_iAntiCheatScore >= kAntiCheatDisconnectScore) { if (GetDesc()->DelayedDisconnect(number(3, 8))) @@ -7501,6 +7508,53 @@ void CHARACTER::RecordAntiCheatViolation(const char* category, int score, const } } +void CHARACTER::MaybeQueueAntiCheatReview(DWORD dwNow, const char* category, const char* detail) +{ + if (!AntiCheatReviewQueueEnabled() || !GetDesc() || !IsPC()) + return; + + if (m_iAntiCheatScore < g_iAntiCheatReviewScore) + return; + + const DWORD dwCooldownMs = static_cast(g_iAntiCheatReviewCooldownSec) * 1000; + const bool bHasPreviousReview = m_dwLastAntiCheatReviewTime != 0; + const bool bCooldownExpired = !bHasPreviousReview || dwNow - m_dwLastAntiCheatReviewTime >= dwCooldownMs; + const bool bScoreJumped = bHasPreviousReview && m_iAntiCheatScore >= m_iAntiCheatLastReviewScore + g_iAntiCheatReviewRepeatDelta; + + if (!bCooldownExpired && !bScoreJumped) + return; + + ++m_iAntiCheatReviewCount; + m_dwLastAntiCheatReviewTime = dwNow; + m_iAntiCheatLastReviewScore = m_iAntiCheatScore; + + char szDetail[512]; + snprintf(szDetail, + sizeof(szDetail), + "score=%d peak=%d reviews=%d category=%s disconnect=%d detail=%s", + m_iAntiCheatScore, + m_iAntiCheatPeakScore, + m_iAntiCheatReviewCount, + category ? category : "UNKNOWN", + AntiCheatDisconnectEnabled() ? 1 : 0, + detail ? detail : "-"); + + LogManager::instance().CharLog(this, m_iAntiCheatScore, "ANTI_CHEAT_REVIEW", szDetail); + + char szReason[768]; + snprintf(szReason, + sizeof(szReason), + "ANTI_CHEAT_REVIEW score=%d peak=%d reviews=%d category=%s detail=%s", + m_iAntiCheatScore, + m_iAntiCheatPeakScore, + m_iAntiCheatReviewCount, + category ? category : "UNKNOWN", + detail ? detail : "-"); + LogManager::instance().HackLog(szReason, this); + + sys_log(0, "ANTI_CHEAT_REVIEW: pid=%u name=%s %s", GetPlayerID(), GetName(), szDetail); +} + void CHARACTER::SkipComboAttackByTime(int interval) { m_dwSkipComboAttackByTime = get_dword_time() + interval; diff --git a/src/game/char.h b/src/game/char.h index 763b935..46d8cda 100644 --- a/src/game/char.h +++ b/src/game/char.h @@ -1314,6 +1314,7 @@ class CHARACTER : public CEntity, public CFSM, public CHorseRider protected: void DecayAntiCheatScore(DWORD dwNow); + void MaybeQueueAntiCheatReview(DWORD dwNow, const char* category, const char* detail); BYTE m_bComboSequence; DWORD m_dwLastComboTime; int m_iValidComboInterval; @@ -1321,8 +1322,12 @@ class CHARACTER : public CEntity, public CFSM, public CHorseRider int m_iComboHackCount; DWORD m_dwSkipComboAttackByTime; int m_iAntiCheatScore; + int m_iAntiCheatPeakScore; + int m_iAntiCheatLastReviewScore; DWORD m_dwLastAntiCheatScoreTime; DWORD m_dwLastAntiCheatPersistTime; + DWORD m_dwLastAntiCheatReviewTime; + int m_iAntiCheatReviewCount; protected: void UpdateAggrPointEx(LPCHARACTER ch, EDamageType type, int dam, TBattleInfo & info); diff --git a/src/game/config.cpp b/src/game/config.cpp index 04f0248..2d0004a 100644 --- a/src/game/config.cpp +++ b/src/game/config.cpp @@ -104,6 +104,10 @@ bool g_bAntiCheatCombatValidation = true; bool g_bAntiCheatMovementValidation = true; bool g_bAntiCheatActionValidation = true; bool g_bAntiCheatBehaviorTelemetry = true; +bool g_bAntiCheatReviewQueue = true; +int g_iAntiCheatReviewScore = 40; +int g_iAntiCheatReviewCooldownSec = 300; +int g_iAntiCheatReviewRepeatDelta = 20; int g_iSpamBlockMaxLevel = 10; @@ -173,6 +177,11 @@ bool AntiCheatBehaviorTelemetryEnabled() return g_bAntiCheatEnable && g_bAntiCheatBehaviorTelemetry; } +bool AntiCheatReviewQueueEnabled() +{ + return g_bAntiCheatEnable && g_bAntiCheatReviewQueue; +} + void map_allow_log() { std::set::iterator i; @@ -848,6 +857,29 @@ void config_init(const string& st_localeServiceName) g_bAntiCheatBehaviorTelemetry = is_string_true(value_string); } + TOKEN("anti_cheat_review_queue") + { + g_bAntiCheatReviewQueue = is_string_true(value_string); + } + + TOKEN("anti_cheat_review_score") + { + str_to_number(g_iAntiCheatReviewScore, value_string); + g_iAntiCheatReviewScore = MAX(1, g_iAntiCheatReviewScore); + } + + TOKEN("anti_cheat_review_cooldown_sec") + { + str_to_number(g_iAntiCheatReviewCooldownSec, value_string); + g_iAntiCheatReviewCooldownSec = MAX(0, g_iAntiCheatReviewCooldownSec); + } + + TOKEN("anti_cheat_review_repeat_delta") + { + str_to_number(g_iAntiCheatReviewRepeatDelta, value_string); + g_iAntiCheatReviewRepeatDelta = MAX(1, g_iAntiCheatReviewRepeatDelta); + } + TOKEN("spam_block_max_level") { str_to_number(g_iSpamBlockMaxLevel, value_string); diff --git a/src/game/config.h b/src/game/config.h index 7f88904..1a2e4c2 100644 --- a/src/game/config.h +++ b/src/game/config.h @@ -104,6 +104,10 @@ extern bool g_bAntiCheatCombatValidation; extern bool g_bAntiCheatMovementValidation; extern bool g_bAntiCheatActionValidation; extern bool g_bAntiCheatBehaviorTelemetry; +extern bool g_bAntiCheatReviewQueue; +extern int g_iAntiCheatReviewScore; +extern int g_iAntiCheatReviewCooldownSec; +extern int g_iAntiCheatReviewRepeatDelta; extern DWORD g_GoldDropTimeLimitValue; @@ -117,5 +121,6 @@ extern bool AntiCheatCombatValidationEnabled(); extern bool AntiCheatMovementValidationEnabled(); extern bool AntiCheatActionValidationEnabled(); extern bool AntiCheatBehaviorTelemetryEnabled(); +extern bool AntiCheatReviewQueueEnabled(); #endif /* __INC_METIN_II_GAME_CONFIG_H__ */ diff --git a/src/game/main.cpp b/src/game/main.cpp index 9bae4e0..295f9c0 100644 --- a/src/game/main.cpp +++ b/src/game/main.cpp @@ -233,6 +233,14 @@ namespace BoolState(AntiCheatActionValidationEnabled()), BoolState(AntiCheatBehaviorTelemetryEnabled()) ); + + sys_log(0, + "[STARTUP] anti_cheat_review queue=%s score=%d cooldown_sec=%d repeat_delta=%d", + BoolState(AntiCheatReviewQueueEnabled()), + g_iAntiCheatReviewScore, + g_iAntiCheatReviewCooldownSec, + g_iAntiCheatReviewRepeatDelta + ); } struct SendDisconnectFunc