From 571e10a1225c4349527562e70331f74be3d918a9 Mon Sep 17 00:00:00 2001 From: server Date: Thu, 16 Apr 2026 15:05:03 +0200 Subject: [PATCH] Add configurable anti-cheat gates --- src/game/battle.cpp | 10 ++++-- src/game/char.cpp | 65 ++++++++++++++++++++++------------- src/game/char_battle.cpp | 7 ++-- src/game/char_dragonsoul.cpp | 4 +++ src/game/char_item.cpp | 46 +++++++++++++++---------- src/game/config.cpp | 66 ++++++++++++++++++++++++++++++++++++ src/game/config.h | 14 +++++++- src/game/cube.cpp | 4 +++ src/game/fishing.cpp | 2 +- src/game/input_main.cpp | 44 ++++++++++++++++-------- src/game/main.cpp | 10 ++++++ src/game/mining.cpp | 6 ++-- 12 files changed, 213 insertions(+), 65 deletions(-) diff --git a/src/game/battle.cpp b/src/game/battle.cpp index 11a83a1..07bbb6b 100644 --- a/src/game/battle.cpp +++ b/src/game/battle.cpp @@ -190,6 +190,7 @@ int battle_melee_attack(LPCHARACTER ch, LPCHARACTER victim) // 거리 체크 int distance = DISTANCE_APPROX(ch->GetX() - victim->GetX(), ch->GetY() - victim->GetY()); + const bool enforceCombatValidation = AntiCheatCombatValidationEnabled(); if (!victim->IsBuilding()) { @@ -207,7 +208,7 @@ int battle_melee_attack(LPCHARACTER ch, LPCHARACTER victim) max = MAX(300, (int) (victim->GetMobAttackRange() * 1.15f)); } - if (distance > max) + if (enforceCombatValidation && distance > max) { if (ch->IsPC() && distance > max + 200) { @@ -229,7 +230,7 @@ int battle_melee_attack(LPCHARACTER ch, LPCHARACTER victim) } float rotDelta = 0.0f; - if (!battle_melee_angle_valid(ch, victim, distance, max, &rotDelta)) + if (enforceCombatValidation && !battle_melee_angle_valid(ch, victim, distance, max, &rotDelta)) { char szDetail[160]; snprintf(szDetail, @@ -263,7 +264,7 @@ int battle_melee_attack(LPCHARACTER ch, LPCHARACTER victim) int dam; int ret = battle_hit(ch, victim, dam); - if ((ret == BATTLE_DAMAGE || ret == BATTLE_DEAD) && ch->IsPC() && victim->IsPC()) + if (enforceCombatValidation && (ret == BATTLE_DAMAGE || ret == BATTLE_DEAD) && ch->IsPC() && victim->IsPC()) victim->LockSyncOwner(450); return (ret); @@ -882,6 +883,9 @@ void SET_ATTACKED_TIME(LPCHARACTER ch, LPCHARACTER victim, DWORD current_time) bool IS_SPEED_HACK(LPCHARACTER ch, LPCHARACTER victim, DWORD current_time) { + if (!AntiCheatCombatValidationEnabled()) + return false; + // 2013 09 11 CYH debugging log /*sys_log(0, "%s attack test log! time (delta, limit)=(%u, %u). ch->m_kAttackLog.dwvID(%u) victim->GetVID(%u)", ch->GetName(), diff --git a/src/game/char.cpp b/src/game/char.cpp index 031ed31..25e9ce7 100644 --- a/src/game/char.cpp +++ b/src/game/char.cpp @@ -4125,21 +4125,24 @@ void CHARACTER::mining_cancel() void CHARACTER::mining(LPCHARACTER chLoad) { const DWORD dwNow = get_dword_time(); - if (0 == m_dwMiningWindowStart || dwNow - m_dwMiningWindowStart >= kActionWindowMs) + if (AntiCheatActionValidationEnabled() && (0 == m_dwMiningWindowStart || dwNow - m_dwMiningWindowStart >= kActionWindowMs)) { m_dwMiningWindowStart = dwNow; m_iMiningWindowCount = 0; } - ++m_iMiningWindowCount; - if (m_iMiningWindowCount > kMiningRateWarnThreshold) + if (AntiCheatActionValidationEnabled()) { - char szDetail[128]; - snprintf(szDetail, sizeof(szDetail), "window=1s count=%d target=%u", m_iMiningWindowCount, chLoad ? chLoad->GetVID() : 0); - RecordAntiCheatViolation("MINING_RATE", MIN(8, 1 + (m_iMiningWindowCount - kMiningRateWarnThreshold) / 2), szDetail, m_iMiningWindowCount > kMiningRateBlockThreshold); + ++m_iMiningWindowCount; + if (m_iMiningWindowCount > kMiningRateWarnThreshold) + { + char szDetail[128]; + snprintf(szDetail, sizeof(szDetail), "window=1s count=%d target=%u", m_iMiningWindowCount, chLoad ? chLoad->GetVID() : 0); + RecordAntiCheatViolation("MINING_RATE", MIN(8, 1 + (m_iMiningWindowCount - kMiningRateWarnThreshold) / 2), szDetail, m_iMiningWindowCount > kMiningRateBlockThreshold); - if (m_iMiningWindowCount > kMiningRateBlockThreshold) - return; + if (m_iMiningWindowCount > kMiningRateBlockThreshold) + return; + } } if (m_pkMiningEvent) @@ -4148,7 +4151,7 @@ void CHARACTER::mining(LPCHARACTER chLoad) return; } - if (IsDead() || !CanMove() || !CanHandleItem()) + if (AntiCheatActionValidationEnabled() && (IsDead() || !CanMove() || !CanHandleItem())) { char szDetail[128]; snprintf(szDetail, @@ -4168,7 +4171,7 @@ void CHARACTER::mining(LPCHARACTER chLoad) if (mining::GetRawOreFromLoad(chLoad->GetRaceNum()) == 0) return; - if (chLoad == this || chLoad->GetMapIndex() != GetMapIndex()) + if (AntiCheatActionValidationEnabled() && (chLoad == this || chLoad->GetMapIndex() != GetMapIndex())) { char szDetail[128]; snprintf(szDetail, @@ -4182,7 +4185,7 @@ void CHARACTER::mining(LPCHARACTER chLoad) } const int iDistance = DISTANCE_APPROX(GetX() - chLoad->GetX(), GetY() - chLoad->GetY()); - if (iDistance > kMiningStartMaxDistance) + if (AntiCheatActionValidationEnabled() && iDistance > kMiningStartMaxDistance) { char szDetail[128]; snprintf(szDetail, sizeof(szDetail), "target=%u distance=%d max=%d", chLoad->GetVID(), iDistance, kMiningStartMaxDistance); @@ -4217,21 +4220,24 @@ void CHARACTER::mining(LPCHARACTER chLoad) void CHARACTER::fishing() { const DWORD dwNow = get_dword_time(); - if (0 == m_dwFishingWindowStart || dwNow - m_dwFishingWindowStart >= kActionWindowMs) + if (AntiCheatActionValidationEnabled() && (0 == m_dwFishingWindowStart || dwNow - m_dwFishingWindowStart >= kActionWindowMs)) { m_dwFishingWindowStart = dwNow; m_iFishingWindowCount = 0; } - ++m_iFishingWindowCount; - if (m_iFishingWindowCount > kFishingRateWarnThreshold) + if (AntiCheatActionValidationEnabled()) { - char szDetail[128]; - snprintf(szDetail, sizeof(szDetail), "window=1s count=%d active=%d", m_iFishingWindowCount, m_pkFishingEvent ? 1 : 0); - RecordAntiCheatViolation("FISHING_RATE", MIN(8, 1 + (m_iFishingWindowCount - kFishingRateWarnThreshold) / 2), szDetail, m_iFishingWindowCount > kFishingRateBlockThreshold); + ++m_iFishingWindowCount; + if (m_iFishingWindowCount > kFishingRateWarnThreshold) + { + char szDetail[128]; + snprintf(szDetail, sizeof(szDetail), "window=1s count=%d active=%d", m_iFishingWindowCount, m_pkFishingEvent ? 1 : 0); + RecordAntiCheatViolation("FISHING_RATE", MIN(8, 1 + (m_iFishingWindowCount - kFishingRateWarnThreshold) / 2), szDetail, m_iFishingWindowCount > kFishingRateBlockThreshold); - if (m_iFishingWindowCount > kFishingRateBlockThreshold) - return; + if (m_iFishingWindowCount > kFishingRateBlockThreshold) + return; + } } if (m_pkFishingEvent) @@ -4240,7 +4246,7 @@ void CHARACTER::fishing() return; } - if (IsDead() || !CanMove() || !CanHandleItem()) + if (AntiCheatActionValidationEnabled() && (IsDead() || !CanMove() || !CanHandleItem())) { char szDetail[128]; snprintf(szDetail, @@ -4295,7 +4301,7 @@ void CHARACTER::fishing() void CHARACTER::fishing_take() { - if (!CanMove() || !CanHandleItem()) + if (AntiCheatActionValidationEnabled() && (!CanMove() || !CanHandleItem())) { char szDetail[128]; snprintf(szDetail, @@ -4309,7 +4315,7 @@ void CHARACTER::fishing_take() } const int iMovedDistance = DISTANCE_APPROX(GetX() - m_lFishingStartX, GetY() - m_lFishingStartY); - if (iMovedDistance > kFishingTakeMaxMoveDistance) + if (AntiCheatActionValidationEnabled() && iMovedDistance > kFishingTakeMaxMoveDistance) { char szDetail[128]; snprintf(szDetail, sizeof(szDetail), "distance=%d max=%d", iMovedDistance, kFishingTakeMaxMoveDistance); @@ -7258,6 +7264,13 @@ EVENTFUNC(check_speedhack_event) if (NULL == ch || ch->IsNPC()) return 0; + if (!AntiCheatCombatValidationEnabled()) + { + ch->m_speed_hack_count = 0; + ch->ResetComboHackCount(); + return PASSES_PER_SEC(60); + } + if (IS_SPEED_HACK_PLAYER(ch)) { // write hack log @@ -7402,6 +7415,9 @@ BYTE CHARACTER::GetComboIndex() const void CHARACTER::IncreaseComboHackCount(int k) { + if (!AntiCheatCombatValidationEnabled()) + return; + m_iComboHackCount += k; if (m_iComboHackCount >= 10) @@ -7447,6 +7463,9 @@ void CHARACTER::DecayAntiCheatScore(DWORD dwNow) void CHARACTER::RecordAntiCheatViolation(const char* category, int score, const char* detail, bool forcePersist) { + if (!AntiCheatEnabled()) + return; + const DWORD dwNow = get_dword_time(); DecayAntiCheatScore(dwNow); @@ -7475,7 +7494,7 @@ void CHARACTER::RecordAntiCheatViolation(const char* category, int score, const m_dwLastAntiCheatPersistTime = dwNow; } - if (GetDesc() && m_iAntiCheatScore >= kAntiCheatDisconnectScore) + if (AntiCheatDisconnectEnabled() && GetDesc() && m_iAntiCheatScore >= kAntiCheatDisconnectScore) { if (GetDesc()->DelayedDisconnect(number(3, 8))) sys_log(0, "ANTI_CHEAT_DISCONNECT: %s score=%d", GetName(), m_iAntiCheatScore); diff --git a/src/game/char_battle.cpp b/src/game/char_battle.cpp index 313b0c6..1f99ed5 100644 --- a/src/game/char_battle.cpp +++ b/src/game/char_battle.cpp @@ -69,7 +69,7 @@ namespace void ObserveCombatTargetPattern(LPCHARACTER ch, LPCHARACTER victim, DWORD now) { - if (!ch || !victim || !ch->IsPC()) + if (!AntiCheatBehaviorTelemetryEnabled() || !ch || !victim || !ch->IsPC()) return; if (!victim->IsMonster() && !victim->IsStone()) @@ -365,7 +365,7 @@ bool CHARACTER::Attack(LPCHARACTER pkVictim, BYTE bType) { if (IsPC() == true) { - if (dwCurrentTime - m_dwLastSkillTime > 1500) + if (AntiCheatCombatValidationEnabled() && dwCurrentTime - m_dwLastSkillTime > 1500) { char szDetail[128]; snprintf(szDetail, sizeof(szDetail), "skill=%u delta=%u victim=%s", bType, dwCurrentTime - m_dwLastSkillTime, pkVictim ? pkVictim->GetName() : "-"); @@ -3108,7 +3108,8 @@ class CFuncShoot int rangedDistance = 0; int rangedLimit = 0; - if (!battle_ranged_target_valid(m_me, pkVictim, m_bType, &rangedDistance, &rangedLimit)) + if (AntiCheatCombatValidationEnabled() && + !battle_ranged_target_valid(m_me, pkVictim, m_bType, &rangedDistance, &rangedLimit)) { if (m_me->IsPC()) { diff --git a/src/game/char_dragonsoul.cpp b/src/game/char_dragonsoul.cpp index cf64323..f286ca6 100644 --- a/src/game/char_dragonsoul.cpp +++ b/src/game/char_dragonsoul.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "char.h" #include "utils.h" +#include "config.h" #include "item.h" #include "desc.h" #include "DragonSoul.h" @@ -154,6 +155,9 @@ bool CHARACTER::DragonSoul_RefineWindow_CanRefine() if (NULL == m_pointsInstant.m_pDragonSoulRefineWindowOpener) return false; + if (!AntiCheatActionValidationEnabled()) + return true; + LPENTITY pOpener = m_pointsInstant.m_pDragonSoulRefineWindowOpener; if (!pOpener->IsType(ENTITY_CHARACTER)) { diff --git a/src/game/char_item.cpp b/src/game/char_item.cpp index 4e18ff9..23c8ed9 100644 --- a/src/game/char_item.cpp +++ b/src/game/char_item.cpp @@ -58,6 +58,9 @@ namespace if (!ch) return false; + if (!AntiCheatActionValidationEnabled()) + return true; + if (0 == dwRefineNpcVID) { char szDetail[128]; @@ -114,7 +117,7 @@ namespace void ObservePickupPattern(LPCHARACTER ch, DWORD now, long x, long y) { - if (!ch) + if (!AntiCheatBehaviorTelemetryEnabled() || !ch) return; if (0 == ch->m_dwPickupPatternWindowStart || now - ch->m_dwPickupPatternWindowStart >= kPickupPatternWindowMs) @@ -5995,22 +5998,25 @@ bool CHARACTER::PickupItem(DWORD dwVID) const long lPickupX = GetX(); const long lPickupY = GetY(); - if (m_dwPickupWindowStart == 0 || dwNow - m_dwPickupWindowStart >= 1000) + if (AntiCheatActionValidationEnabled() && (m_dwPickupWindowStart == 0 || dwNow - m_dwPickupWindowStart >= 1000)) { m_dwPickupWindowStart = dwNow; m_iPickupWindowCount = 0; } - ++m_iPickupWindowCount; - - if (m_iPickupWindowCount > 25) + if (AntiCheatActionValidationEnabled()) { - char szDetail[96]; - snprintf(szDetail, sizeof(szDetail), "count=%d window_ms=%u", m_iPickupWindowCount, dwNow - m_dwPickupWindowStart); - RecordAntiCheatViolation("PICKUP_RATE", MIN(8, 1 + (m_iPickupWindowCount - 25) / 5), szDetail, m_iPickupWindowCount > 45); + ++m_iPickupWindowCount; - if (m_iPickupWindowCount > 45) - return false; + if (m_iPickupWindowCount > 25) + { + char szDetail[96]; + snprintf(szDetail, sizeof(szDetail), "count=%d window_ms=%u", m_iPickupWindowCount, dwNow - m_dwPickupWindowStart); + RecordAntiCheatViolation("PICKUP_RATE", MIN(8, 1 + (m_iPickupWindowCount - 25) / 5), szDetail, m_iPickupWindowCount > 45); + + if (m_iPickupWindowCount > 45) + return false; + } } LPITEM item = ITEM_MANAGER::instance().FindByVID(dwVID); @@ -6023,7 +6029,7 @@ bool CHARACTER::PickupItem(DWORD dwVID) const int iDist = DISTANCE_APPROX(item->GetX() - GetX(), item->GetY() - GetY()); - if (!item->DistanceValid(this)) + if (AntiCheatActionValidationEnabled() && !item->DistanceValid(this)) { char szDetail[128]; snprintf(szDetail, sizeof(szDetail), "item=%u dist=%d owner=%u", item->GetVID(), iDist, item->GetOwnershipPID()); @@ -6141,9 +6147,12 @@ bool CHARACTER::PickupItem(DWORD dwVID) if (!owner) { - char szDetail[160]; - snprintf(szDetail, sizeof(szDetail), "item=%u owner_pid=%u party=%d", item->GetVID(), item->GetOwnershipPID(), GetParty() ? 1 : 0); - RecordAntiCheatViolation("PICKUP_OWNERSHIP", 6, szDetail, true); + if (AntiCheatActionValidationEnabled()) + { + char szDetail[160]; + snprintf(szDetail, sizeof(szDetail), "item=%u owner_pid=%u party=%d", item->GetVID(), item->GetOwnershipPID(), GetParty() ? 1 : 0); + RecordAntiCheatViolation("PICKUP_OWNERSHIP", 6, szDetail, true); + } return false; } @@ -6202,9 +6211,12 @@ bool CHARACTER::PickupItem(DWORD dwVID) return true; } - char szDetail[160]; - snprintf(szDetail, sizeof(szDetail), "item=%u owner_pid=%u party=%d", item->GetVID(), item->GetOwnershipPID(), GetParty() ? 1 : 0); - RecordAntiCheatViolation("PICKUP_OWNERSHIP", 4, szDetail); + if (AntiCheatActionValidationEnabled()) + { + char szDetail[160]; + snprintf(szDetail, sizeof(szDetail), "item=%u owner_pid=%u party=%d", item->GetVID(), item->GetOwnershipPID(), GetParty() ? 1 : 0); + RecordAntiCheatViolation("PICKUP_OWNERSHIP", 4, szDetail); + } } return false; diff --git a/src/game/config.cpp b/src/game/config.cpp index cb84184..04f0248 100644 --- a/src/game/config.cpp +++ b/src/game/config.cpp @@ -98,6 +98,12 @@ unsigned int g_uiSpamBlockScore = 100; // 기본 100점 unsigned int g_uiSpamReloadCycle = 60 * 10; // 기본 10분 bool g_bCheckMultiHack = true; +bool g_bAntiCheatEnable = true; +bool g_bAntiCheatDisconnect = true; +bool g_bAntiCheatCombatValidation = true; +bool g_bAntiCheatMovementValidation = true; +bool g_bAntiCheatActionValidation = true; +bool g_bAntiCheatBehaviorTelemetry = true; int g_iSpamBlockMaxLevel = 10; @@ -137,6 +143,36 @@ bool map_allow_find(int index) return true; } +bool AntiCheatEnabled() +{ + return g_bAntiCheatEnable; +} + +bool AntiCheatDisconnectEnabled() +{ + return g_bAntiCheatEnable && g_bAntiCheatDisconnect; +} + +bool AntiCheatCombatValidationEnabled() +{ + return g_bAntiCheatEnable && g_bAntiCheatCombatValidation; +} + +bool AntiCheatMovementValidationEnabled() +{ + return g_bAntiCheatEnable && g_bAntiCheatMovementValidation; +} + +bool AntiCheatActionValidationEnabled() +{ + return g_bAntiCheatEnable && g_bAntiCheatActionValidation; +} + +bool AntiCheatBehaviorTelemetryEnabled() +{ + return g_bAntiCheatEnable && g_bAntiCheatBehaviorTelemetry; +} + void map_allow_log() { std::set::iterator i; @@ -782,6 +818,36 @@ void config_init(const string& st_localeServiceName) str_to_number(g_bCheckMultiHack, value_string); } + TOKEN("anti_cheat_enable") + { + g_bAntiCheatEnable = is_string_true(value_string); + } + + TOKEN("anti_cheat_disconnect") + { + g_bAntiCheatDisconnect = is_string_true(value_string); + } + + TOKEN("anti_cheat_combat_validation") + { + g_bAntiCheatCombatValidation = is_string_true(value_string); + } + + TOKEN("anti_cheat_movement_validation") + { + g_bAntiCheatMovementValidation = is_string_true(value_string); + } + + TOKEN("anti_cheat_action_validation") + { + g_bAntiCheatActionValidation = is_string_true(value_string); + } + + TOKEN("anti_cheat_behavior_telemetry") + { + g_bAntiCheatBehaviorTelemetry = is_string_true(value_string); + } + 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 ce8d3fe..7f88904 100644 --- a/src/game/config.h +++ b/src/game/config.h @@ -98,6 +98,12 @@ extern int VIEW_BONUS_RANGE; extern bool g_bCheckMultiHack; extern bool g_protectNormalPlayer; // 범법자가 "평화모드" 인 일반유저를 공격하지 못함 extern bool g_noticeBattleZone; // 중립지대에 입장하면 안내메세지를 알려줌 +extern bool g_bAntiCheatEnable; +extern bool g_bAntiCheatDisconnect; +extern bool g_bAntiCheatCombatValidation; +extern bool g_bAntiCheatMovementValidation; +extern bool g_bAntiCheatActionValidation; +extern bool g_bAntiCheatBehaviorTelemetry; extern DWORD g_GoldDropTimeLimitValue; @@ -105,5 +111,11 @@ extern int gPlayerMaxLevel; extern bool g_BlockCharCreation; -#endif /* __INC_METIN_II_GAME_CONFIG_H__ */ +extern bool AntiCheatEnabled(); +extern bool AntiCheatDisconnectEnabled(); +extern bool AntiCheatCombatValidationEnabled(); +extern bool AntiCheatMovementValidationEnabled(); +extern bool AntiCheatActionValidationEnabled(); +extern bool AntiCheatBehaviorTelemetryEnabled(); +#endif /* __INC_METIN_II_GAME_CONFIG_H__ */ diff --git a/src/game/cube.cpp b/src/game/cube.cpp index 96f2d6b..c96efe4 100644 --- a/src/game/cube.cpp +++ b/src/game/cube.cpp @@ -11,6 +11,7 @@ #include "constants.h" #include "utils.h" #include "log.h" +#include "config.h" #include "char.h" #include "locale_service.h" #include "item.h" @@ -188,6 +189,9 @@ static bool FN_validate_cube_npc(LPCHARACTER ch, const char* action) if (!ch || !ch->IsCubeOpen()) return false; + if (!AntiCheatActionValidationEnabled()) + return true; + LPCHARACTER npc = ch->GetCubeNpc(); if (NULL == npc) { diff --git a/src/game/fishing.cpp b/src/game/fishing.cpp index b909895..af754be 100644 --- a/src/game/fishing.cpp +++ b/src/game/fishing.cpp @@ -88,7 +88,7 @@ namespace fishing void ObserveFishingReaction(LPCHARACTER ch, int reactionMs, bool success) { - if (!ch || reactionMs <= 0 || reactionMs > 6000) + if (!AntiCheatBehaviorTelemetryEnabled() || !ch || reactionMs <= 0 || reactionMs > 6000) return; const DWORD now = get_dword_time(); diff --git a/src/game/input_main.cpp b/src/game/input_main.cpp index 6d70044..ccdca98 100644 --- a/src/game/input_main.cpp +++ b/src/game/input_main.cpp @@ -1340,6 +1340,9 @@ DWORD ClacValidComboInterval( LPCHARACTER ch, BYTE bArg ) bool CheckComboHack(LPCHARACTER ch, BYTE bArg, DWORD dwTime, bool CheckSpeedHack) { + if (!AntiCheatCombatValidationEnabled()) + return false; + // 죽거나 기절 상태에서는 공격할 수 없으므로, skip한다. // 이렇게 하지 말고, CHRACTER::CanMove()에 // if (IsStun() || IsDead()) return false; @@ -1558,8 +1561,10 @@ void CInputMain::Move(LPCHARACTER ch, const char * data) return; struct command_move * pinfo = (struct command_move *) data; + const bool enforceMovementValidation = AntiCheatMovementValidationEnabled(); + const bool enforceCombatValidation = AntiCheatCombatValidationEnabled(); - if (pinfo->bFunc >= FUNC_MAX_NUM && !(pinfo->bFunc & 0x80)) + if (enforceMovementValidation && pinfo->bFunc >= FUNC_MAX_NUM && !(pinfo->bFunc & 0x80)) { char szDetail[64]; snprintf(szDetail, sizeof(szDetail), "func=%u arg=%u", pinfo->bFunc, pinfo->bArg); @@ -1582,7 +1587,12 @@ void CInputMain::Move(LPCHARACTER ch, const char * data) const float fDist = DISTANCE_SQRT((ch->GetX() - pinfo->lX) / 100, (ch->GetY() - pinfo->lY) / 100); - if (ch->IsPC() && ch->IsSyncOwnerLocked() && ch->GetSyncOwner() && ch->GetSyncOwner() != ch && fDist > 2.5f) + if (enforceMovementValidation && + ch->IsPC() && + ch->IsSyncOwnerLocked() && + ch->GetSyncOwner() && + ch->GetSyncOwner() != ch && + fDist > 2.5f) { char szDetail[192]; snprintf(szDetail, @@ -1609,7 +1619,8 @@ void CInputMain::Move(LPCHARACTER ch, const char * data) // if (!test_server) //2012.05.15 김용욱 : 테섭에서 (무적상태로) 다수 몬스터 상대로 다운되면서 공격시 콤보핵으로 죽는 문제가 있었다. { - if (((false == ch->IsRiding() && fDist > 25) || fDist > 40) && OXEVENT_MAP_INDEX != ch->GetMapIndex()) + if (enforceMovementValidation && + (((false == ch->IsRiding() && fDist > 25) || fDist > 40) && OXEVENT_MAP_INDEX != ch->GetMapIndex())) { char szDetail[160]; snprintf(szDetail, @@ -1661,7 +1672,7 @@ void CInputMain::Move(LPCHARACTER ch, const char * data) // // 콤보핵 및 스피드핵 체크 // - if (pinfo->bFunc == FUNC_COMBO && g_bCheckMultiHack) + if (pinfo->bFunc == FUNC_COMBO && g_bCheckMultiHack && enforceCombatValidation) { CheckComboHack(ch, pinfo->bArg, pinfo->dwTime, CheckSpeedHack); // 콤보 체크 } @@ -1686,7 +1697,7 @@ void CInputMain::Move(LPCHARACTER ch, const char * data) const int MASK_SKILL_MOTION = 0x7F; unsigned int motion = pinfo->bFunc & MASK_SKILL_MOTION; - if (!ch->IsUsableSkillMotion(motion)) + if (enforceCombatValidation && !ch->IsUsableSkillMotion(motion)) { const char* name = ch->GetName(); unsigned int job = ch->GetJob(); @@ -1875,6 +1886,7 @@ void CInputMain::Attack(LPCHARACTER ch, const uint16_t header, const char* data) int CInputMain::SyncPosition(LPCHARACTER ch, const char * c_pcData, size_t uiBytes) { const TPacketCGSyncPosition* pinfo = reinterpret_cast( c_pcData ); + const bool enforceMovementValidation = AntiCheatMovementValidationEnabled(); if (uiBytes < pinfo->length) return -1; @@ -1901,7 +1913,7 @@ int CInputMain::SyncPosition(LPCHARACTER ch, const char * c_pcData, size_t uiByt static const int nCountLimit = 16; - if( iCount > nCountLimit ) + if( enforceMovementValidation && iCount > nCountLimit ) { //LogManager::instance().HackLog( "SYNC_POSITION_HACK", ch ); char szDetail[64]; @@ -1939,7 +1951,7 @@ int CInputMain::SyncPosition(LPCHARACTER ch, const char * c_pcData, size_t uiByt continue; } - if (victim->IsSyncOwnerLocked() && !victim->IsSyncOwnerLockedFor(ch)) + if (enforceMovementValidation && victim->IsSyncOwnerLocked() && !victim->IsSyncOwnerLockedFor(ch)) { char szDetail[192]; snprintf(szDetail, @@ -1965,7 +1977,7 @@ int CInputMain::SyncPosition(LPCHARACTER ch, const char * c_pcData, size_t uiByt // 2500 : 스킬 proto에서 가장 사거리가 긴 스킬의 사거리, 또는 활의 사거리 // a = POINT_BOW_DISTANCE 값... 인데 실제로 사용하는 값인지는 잘 모르겠음. 아이템이나 포션, 스킬, 퀘스트에는 없는데... // 그래도 혹시나 하는 마음에 버퍼로 사용할 겸해서 1000.f 로 둠... - if (fDistWithSyncOwner > fLimitDistWithSyncOwner) + if (enforceMovementValidation && fDistWithSyncOwner > fLimitDistWithSyncOwner) { // g_iSyncHackLimitCount번 까지는 봐줌. if (ch->GetSyncHackCount() < g_iSyncHackLimitCount) @@ -2011,7 +2023,7 @@ int CInputMain::SyncPosition(LPCHARACTER ch, const char * c_pcData, size_t uiByt // SyncPosition을 악용하여 타유저를 이상한 곳으로 보내는 핵 방어하기 위하여, // 같은 유저를 g_lValidSyncInterval ms 이내에 다시 SyncPosition하려고 하면 핵으로 간주. - if (tvDiff->tv_sec == 0 && tvDiff->tv_usec < g_lValidSyncInterval) + if (enforceMovementValidation && tvDiff->tv_sec == 0 && tvDiff->tv_usec < g_lValidSyncInterval) { // g_iSyncHackLimitCount번 까지는 봐줌. if (ch->GetSyncHackCount() < g_iSyncHackLimitCount) @@ -2049,7 +2061,7 @@ int CInputMain::SyncPosition(LPCHARACTER ch, const char * c_pcData, size_t uiByt return -1; } } - else if( fDist > 25.0f ) + else if( enforceMovementValidation && fDist > 25.0f ) { char szDetail[192]; snprintf(szDetail, @@ -2103,7 +2115,7 @@ void CInputMain::FlyTarget(LPCHARACTER ch, const char * pcData, uint16_t wHeader return; } - if (ch == victim) + if (AntiCheatCombatValidationEnabled() && ch == victim) { char szDetail[128]; snprintf(szDetail, sizeof(szDetail), "target=%u x=%ld y=%ld", p->dwTargetVID, static_cast(p->x), static_cast(p->y)); @@ -2115,13 +2127,17 @@ void CInputMain::FlyTarget(LPCHARACTER ch, const char * pcData, uint16_t wHeader { case CHAR_TYPE_WARP: case CHAR_TYPE_GOTO: - ch->RecordAntiCheatViolation("FLY_TARGET_INVALID", 6, victim->GetName(), true); - return; + if (AntiCheatCombatValidationEnabled()) + { + ch->RecordAntiCheatViolation("FLY_TARGET_INVALID", 6, victim->GetName(), true); + return; + } + break; } int distance = 0; int maxDistance = 0; - if (!battle_ranged_target_valid(ch, victim, 0, &distance, &maxDistance)) + if (AntiCheatCombatValidationEnabled() && !battle_ranged_target_valid(ch, victim, 0, &distance, &maxDistance)) { char szDetail[160]; snprintf(szDetail, diff --git a/src/game/main.cpp b/src/game/main.cpp index 76c4955..9bae4e0 100644 --- a/src/game/main.cpp +++ b/src/game/main.cpp @@ -223,6 +223,16 @@ namespace g_wAuthMasterPort, test_server ); + + sys_log(0, + "[STARTUP] anti_cheat enabled=%s disconnect=%s combat=%s movement=%s action=%s telemetry=%s", + BoolState(AntiCheatEnabled()), + BoolState(AntiCheatDisconnectEnabled()), + BoolState(AntiCheatCombatValidationEnabled()), + BoolState(AntiCheatMovementValidationEnabled()), + BoolState(AntiCheatActionValidationEnabled()), + BoolState(AntiCheatBehaviorTelemetryEnabled()) + ); } struct SendDisconnectFunc diff --git a/src/game/mining.cpp b/src/game/mining.cpp index c8e8375..dd58306 100644 --- a/src/game/mining.cpp +++ b/src/game/mining.cpp @@ -368,7 +368,7 @@ namespace mining return 0; } - if (load->GetMapIndex() != ch->GetMapIndex()) + if (AntiCheatActionValidationEnabled() && load->GetMapIndex() != ch->GetMapIndex()) { char szDetail[128]; snprintf(szDetail, @@ -381,7 +381,7 @@ namespace mining return 0; } - if (!ch->CanMove() || !ch->CanHandleItem()) + if (AntiCheatActionValidationEnabled() && (!ch->CanMove() || !ch->CanHandleItem())) { char szDetail[128]; snprintf(szDetail, @@ -395,7 +395,7 @@ namespace mining } const int iDistance = DISTANCE_APPROX(ch->GetX() - load->GetX(), ch->GetY() - load->GetY()); - if (iDistance > MINING_COMPLETE_MAX_DISTANCE) + if (AntiCheatActionValidationEnabled() && iDistance > MINING_COMPLETE_MAX_DISTANCE) { char szDetail[128]; snprintf(szDetail, sizeof(szDetail), "target=%u distance=%d max=%d", load->GetVID(), iDistance, MINING_COMPLETE_MAX_DISTANCE);