Files
Client/Server/RylServerProject/RylGameLibrary/Network/Dispatch/GameClient/CheckSpeedHack.cpp
LGram16 dd97ddec92 Restructure repository to include all source folders
Move git root from Client/ to src/ to track all source code:
- Client: Game client source (moved to Client/Client/)
- Server: Game server source
- GameTools: Development tools
- CryptoSource: Encryption utilities
- database: Database scripts
- Script: Game scripts
- rylCoder_16.02.2008_src: Legacy coder tools
- GMFont, Game: Additional resources

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 20:17:20 +09:00

417 lines
14 KiB
C++

#include "stdafx.h"
#include <Utility/Time/Pulse/Pulse.h>
#include <Creature/Character/Character.h>
#include <Skill/SkillMgr.h>
#include <Network/Packet/PacketStruct/CharMovePacket.h>
#include <Network/Packet/PacketStruct/CharAttackPacket.h>
#include <Network/Packet/PacketStruct/ServerInfo.h>
#include <Network/Dispatch/GameClient/GameClientDispatch.h>
#include <Utility/Setup/ServerSetup.h>
#include <mmsystem.h>
#include "CheckSpeedHack.h"
#include <DB/DBComponent.h>
#include <DB/GameDBComponent.h>
CSpeedHackCheck::CSpeedHackCheck()
: m_dwServerTotal(0), m_dwServerLast(0), m_dwClientTotal(0),
m_dwClinetLast(0),
m_dwLastServerAttackTime(0),
m_dwLastClientAttackTime(0),
m_dwHackFoundCount(0),
m_lpCharacter(NULL)
{
}
bool CSpeedHackCheck::CheckTimeUpdate(DWORD dwClientTime)
{
if (CServerSetup::GetInstance().GetHackCheck() &&
CheckSpeedHack(timeGetTime(), dwClientTime))
{
return true;
}
return true;
}
bool CSpeedHackCheck::CheckAttackReplay(PktAt* lpPktAt)
{
if (NULL == m_lpCharacter)
{
return false;
}
// 일단은 실패하더라도 로그만 찍는다.
CheckAttackHack(lpPktAt, timeGetTime());
const unsigned long dwMaxHackFoundCount = 10;
if (CServerSetup::GetInstance().GetHackCheck() && dwMaxHackFoundCount < m_dwHackFoundCount)
{
SOCKADDR_IN sockAddr;
CGameClientDispatch* lpDispatch = m_lpCharacter->GetDispatcher();
if (0 != lpDispatch)
{
sockAddr = lpDispatch->GetRemoteAddr().get_addr_in();
}
else
{
memset(&sockAddr, 0, sizeof(SOCKADDR_IN));
}
ERRLOG5(g_Log, "/ UID:%10d / CID:%10d / Name:%s / IP:%15s / %dNum Hack found. /",
m_lpCharacter->GetUID(), m_lpCharacter->GetCID(), m_lpCharacter->GetCharacterName(),
inet_ntoa(sockAddr.sin_addr), dwMaxHackFoundCount);
m_dwHackFoundCount = 0;
// DB에 핵이 검출됐다고 남긴다. (게임서버는 DB에 쓰는 방법이없다.)
// DBComponent::GameDB::InsertHackLog(CDBSingleObject::GetInstance(), m_lpCharacter->GetUID(), m_lpCharacter->GetCID(), m_lpCharacter->GetCharacterName());
// 접속 끊지 않는다.
return true;
// edith 2008.05.14 스피드핵 걸리면 종료. (랙으로 인한 오인을 줄이 위해서 제거)
// return false;
}
return true;
}
bool CSpeedHackCheck::CheckSpeedHack(unsigned long CurrentServer, unsigned long CurrentClient)
{
const int HackCheckNum = 2500; // 제한 범위 2.5초
const int HackCheckNum2 = -3000; // 제한 범위 3초
if (CPulse::GetInstance().GetTPPOverTwoTime() ||
0 == m_dwServerLast || 0 == m_dwClinetLast)
{
// 처음 로그인, 서버 부하로 측정 불가
m_dwServerLast = CurrentServer;
m_dwClinetLast = CurrentClient;
return false;
}
int ServerInterval = CurrentServer - m_dwServerLast;
int ClientInterval = CurrentClient - m_dwClinetLast;
if (0 == ServerInterval)
{
// 패킷 부하로 체크 불가
m_dwServerTotal = 0;
m_dwClientTotal = 0;
m_dwServerLast = CurrentServer;
m_dwClinetLast = CurrentClient;
return false;
}
// 누적 타임 계산
m_dwServerTotal += ServerInterval;
m_dwClientTotal += ClientInterval;
int SCTotal = m_dwClientTotal - m_dwServerTotal;
// 스핵 양성 체크
if (SCTotal > HackCheckNum)
{
/////////////////////////////////////////////////////////////////////////////////////////
// @ 스핵 양성 반응자
// 1. 100% 스핵 사용자
//
unsigned long dwCID = (NULL != m_lpCharacter) ? m_lpCharacter->GetCID() : 0;
ERRLOG6(g_Log, "CID:%10u/Plus/S:%d/C:%d/ST:%d/CT:%d/CK:%d/SpeedHack found",
dwCID, ServerInterval, ClientInterval, m_dwServerTotal, m_dwClientTotal, SCTotal);
m_dwServerTotal = 0;
m_dwClientTotal = 0;
m_dwServerLast = CurrentServer;
m_dwClinetLast = CurrentClient;
return true;
}
// 스핵 음성 체크
if (SCTotal < HackCheckNum2)
{
/////////////////////////////////////////////////////////////////////////////////////////
// @ 스핵 음성 반응자
// 1. 로그 만 찍고 다시 초기화
//
unsigned long dwCID = (NULL != m_lpCharacter) ? m_lpCharacter->GetCID() : 0;
ERRLOG6(g_Log, "CID:%10u/Minus/S:%d/C:%d/ST:%d/CT:%d/CK:%d/SpeedHack found",
dwCID, ServerInterval, ClientInterval, m_dwServerTotal, m_dwClientTotal, SCTotal);
m_dwServerTotal = 0;
m_dwClientTotal = 0;
m_dwServerLast = 0;
m_dwClinetLast = 0;
return false;
}
// 누적 타임 크기 체크
if (m_dwServerTotal > 0xFFFFFFF0 || m_dwClientTotal > 0xFFFFFFF0)
{
m_dwServerTotal = 0;
m_dwClientTotal = 0;
m_dwServerLast = 0;
m_dwClinetLast = 0;
}
m_dwServerLast = CurrentServer;
m_dwClinetLast = CurrentClient;
return false;
}
bool CSpeedHackCheck::CheckAttackHack(PktAt* lpPktAt, unsigned long dwCurrentServerTime)
{
static const unsigned long dwAverageAttackTime = 500;
static const unsigned long dwMaxAttackInterval = 1500;
static const unsigned long dwMaxAttackPerInterval = 5; // dwMaxAttackInterval동안 가능한 공격 수
static const unsigned long dwMaxSkillHistorySize = 5;
static const long nAdditionalCoolDownTimeInterval = 1000; // 쿨다운 타임 오차. (1초까지 가능)
unsigned short usSkill_ID = lpPktAt->m_AtType.m_wType;
unsigned short usSkill_LockCount = lpPktAt->m_AtType.m_cSkillLockCount;
if (0 == (usSkill_ID & AtType::SKILL_BIT))
{
const long nDiffServerTime = (0 != m_dwLastServerAttackTime) ?
dwCurrentServerTime - m_dwLastServerAttackTime : dwAverageAttackTime;
const long nDiffClientTime = (0 != m_dwLastClientAttackTime) ?
lpPktAt->GetServerInfo() - m_dwLastClientAttackTime : dwAverageAttackTime;
m_dwLastServerAttackTime = dwCurrentServerTime;
m_dwLastClientAttackTime = lpPktAt->GetServerInfo();
m_AttackTimeList.push_back(AttackTime(nDiffServerTime, nDiffClientTime));
m_SumAttackTime.add(nDiffServerTime, nDiffClientTime);
if (dwMaxAttackPerInterval < m_AttackTimeList.size())
{
// 개수를 항상 dwMaxAttackPerInterval개로 유지한다.
m_SumAttackTime.minus(m_AttackTimeList.front());
m_AttackTimeList.pop_front();
}
// 일반 공격 체크
if (nDiffClientTime <= 120)
{
++m_dwHackFoundCount;
LogAttackHack("Adjusted Client or Attack Hack found");
m_SumAttackTime.clear();
m_AttackTimeList.clear();
return false;
}
else if (dwMaxAttackPerInterval == m_AttackTimeList.size())
{
if (m_SumAttackTime.m_nSumServerAttackTime < dwMaxAttackInterval ||
m_SumAttackTime.m_nSumClientAttackTime < dwMaxAttackInterval)
{
++m_dwHackFoundCount;
LogAttackHack("Attack Hack found");
m_SumAttackTime.clear();
m_AttackTimeList.clear();
return false;
}
}
}
else if (0 == lpPktAt->m_AtType.m_cCasting)
{
// 스킬 사용함.
CoolDownInfoList::iterator itr =
std::lower_bound(m_CoolDownInfo.begin(), m_CoolDownInfo.end(), usSkill_ID);
if (itr != m_CoolDownInfo.end() &&
usSkill_ID == itr->m_attackType.m_wType &&
usSkill_LockCount < CSkillMgr::MAX_SKILL_LOCKCOUNT)
{
// 한번 사용했던 주문임
CoolDownInfo& coolDownInfo = *itr;
++coolDownInfo.m_dwSkillUsedCount;
const long nServerDiffCoolDownTime = dwCurrentServerTime -
coolDownInfo.m_dwServerLastCastingTime;
const long nClientDiffCoolDownTime = lpPktAt->GetServerInfo() -
coolDownInfo.m_dwClientLastCastingTime;
const long nMinCoolDownTime =
static_cast<unsigned long>(coolDownInfo.m_lpSkillProtoType[coolDownInfo.m_attackType.m_cSkillLockCount].m_dwCoolDownTime *
m_lpCharacter->GetStatus().m_StatusInfo.m_fCoolDownRevisionRate / 100.0f);
m_SkillHistoryList.push_back(SkillHistory(lpPktAt->m_AtType, nMinCoolDownTime,
nServerDiffCoolDownTime, nClientDiffCoolDownTime));
if (dwMaxSkillHistorySize < m_SkillHistoryList.size())
{
m_SkillHistoryList.pop_front();
}
if (nServerDiffCoolDownTime + nAdditionalCoolDownTimeInterval < nMinCoolDownTime
|| nClientDiffCoolDownTime + nAdditionalCoolDownTimeInterval < nMinCoolDownTime)
{
++m_dwHackFoundCount;
// 마지막으로 캐스팅 한 주문의 시간에서 현재 시간을 뺀 값이 쿨다운 시간보다 작다.
LogSkillCoolDownTime(*itr, nMinCoolDownTime,
nServerDiffCoolDownTime, nClientDiffCoolDownTime);
coolDownInfo.m_attackType = lpPktAt->m_AtType;
coolDownInfo.m_dwServerLastCastingTime = dwCurrentServerTime;
coolDownInfo.m_dwClientLastCastingTime = lpPktAt->GetServerInfo();
return false;
}
coolDownInfo.m_attackType = lpPktAt->m_AtType;
coolDownInfo.m_dwServerLastCastingTime = dwCurrentServerTime;
coolDownInfo.m_dwClientLastCastingTime = lpPktAt->GetServerInfo();
}
else
{
// 새 주문을 사용했음.
const Skill::ProtoType* lpSkillProtoType =
CSkillMgr::GetInstance().GetSkillProtoType(usSkill_ID);
if (NULL != lpSkillProtoType && usSkill_LockCount < CSkillMgr::MAX_SKILL_LOCKCOUNT)
{
// 0x9305는 FastHit이다. (연속공격스킬)
if (0 != lpSkillProtoType->m_dwCoolDownTime &&
Skill::Type::CHANT != lpSkillProtoType->m_eSkillType &&
0x9305 != usSkill_ID)
{
// 쿨다운이 있는 주문이고, 한번도 사용하지 않았던 주문임
// FastHit스킬인 경우 제외함.
m_CoolDownInfo.insert(itr, CoolDownInfo(lpSkillProtoType,
lpPktAt->m_AtType, dwCurrentServerTime, lpPktAt->GetServerInfo()));
}
}
}
}
return true;
}
void CSpeedHackCheck::LogAttackHack(const char* szDetailText)
{
SOCKADDR_IN sockAddr;
CGameClientDispatch* lpDispatch = m_lpCharacter->GetDispatcher();
if (0 != lpDispatch)
{
sockAddr = lpDispatch->GetRemoteAddr().get_addr_in();
}
else
{
memset(&sockAddr, 0, sizeof(SOCKADDR_IN));
}
const int MAX_BUFFER = 1023;
char szBuffer[MAX_BUFFER + 1];
szBuffer[MAX_BUFFER] = 0;
int nLength = _snprintf(szBuffer, MAX_BUFFER,
"/UID:%d/CID:0x%08x/Name:%s/IP:%15s/%s/ServerDiff(ClientDiff):",
m_lpCharacter->GetUID(), m_lpCharacter->GetCID(), m_lpCharacter->GetCharacterName(),
inet_ntoa(sockAddr.sin_addr), szDetailText);
AttackTimeList::iterator pos = m_AttackTimeList.begin();
AttackTimeList::iterator end = m_AttackTimeList.end();
for (;pos != end; ++pos)
{
AttackTime& atTime = *pos;
nLength += _snprintf(szBuffer + nLength, MAX_BUFFER - nLength, "%dms(%dms),",
atTime.m_nDiffServerInterval, atTime.m_nDiffClientInterval);
}
nLength += _snprintf(szBuffer + nLength, MAX_BUFFER - nLength,
"/ServerSum: %dms/ClientSum: %dms/AttackCount: %d/",
m_SumAttackTime.m_nSumServerAttackTime,
m_SumAttackTime.m_nSumClientAttackTime,
m_AttackTimeList.size());
ERRLOG0(g_Log, szBuffer);
// DB에 핵 로그를 남긴다.
}
void CSpeedHackCheck::LogSkillCoolDownTime(const CoolDownInfo& coolDownInfo,
long nMinCoolDownTime,
long nServerCoolDownTimeInterval,
long nClientCoolDownTimeInterval)
{
SOCKADDR_IN sockAddr;
CGameClientDispatch* lpDispatch = m_lpCharacter->GetDispatcher();
if (0 != lpDispatch)
{
sockAddr = lpDispatch->GetRemoteAddr().get_addr_in();
}
else
{
memset(&sockAddr, 0, sizeof(SOCKADDR_IN));
}
const int MAX_BUFFER = 4096;
char szBuffer[MAX_BUFFER + 1];
szBuffer[MAX_BUFFER] = 0;
int nLength = _snprintf(szBuffer, MAX_BUFFER,
"/UID:%d/CID:0x%08x/Name:%s/IP:%15s/Cooldown Hack found. /"
"Skill_ID:0x%04x/Lc:%d/Lv:%d/CharLevel:%d/Dex:%d/"
"ServerInterval:%dms/ClientInterval:%dms/MinCoolDownTime:%dms/SkillUsedCount:%d/SkillHistory-",
m_lpCharacter->GetUID(), m_lpCharacter->GetCID(), m_lpCharacter->GetCharacterName(),
inet_ntoa(sockAddr.sin_addr),
coolDownInfo.m_attackType.m_wType, coolDownInfo.m_attackType.m_cSkillLockCount,
coolDownInfo.m_attackType.m_cSkillLevel,
m_lpCharacter->GetStatus().m_nLevel, m_lpCharacter->GetCharStatus().m_nDEX,
nServerCoolDownTimeInterval, nClientCoolDownTimeInterval,
nMinCoolDownTime, coolDownInfo.m_dwSkillUsedCount);
SkillHistoryList::iterator pos = m_SkillHistoryList.begin();
SkillHistoryList::iterator end = m_SkillHistoryList.end();
for (;pos != end; ++pos)
{
SkillHistory& skillHistory = *pos;
nLength += _snprintf(szBuffer + nLength, MAX_BUFFER - nLength,
"ID:0x%04x Lc:%2d Lv:%2d SInterval:%d CInterval:%d MinCoolDownTime:%d/",
skillHistory.m_attackType.m_wType, skillHistory.m_attackType.m_cSkillLockCount,
skillHistory.m_attackType.m_cSkillLevel, skillHistory.m_nServerInterval,
skillHistory.m_nClientInterval, skillHistory.m_nMinCoolDownTime);
}
ERRLOG0(g_Log, szBuffer);
}