Add configurable anti-cheat gates
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 15:05:03 +02:00
parent acf7038abb
commit 571e10a122
12 changed files with 213 additions and 65 deletions

View File

@@ -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(),

View File

@@ -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);

View File

@@ -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())
{

View File

@@ -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))
{

View File

@@ -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;

View File

@@ -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<int>::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);

View File

@@ -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__ */

View File

@@ -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)
{

View File

@@ -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();

View File

@@ -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<const TPacketCGSyncPosition*>( 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<long>(p->x), static_cast<long>(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,

View File

@@ -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

View File

@@ -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);