Harden fishing and mining action validation
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 12:18:48 +02:00
parent 819bf0b823
commit f79d5134c4
3 changed files with 171 additions and 1 deletions

View File

@@ -140,6 +140,12 @@ void CHARACTER::Initialize()
LastDropTime = 0;
m_dwPickupWindowStart = 0;
m_iPickupWindowCount = 0;
m_dwFishingWindowStart = 0;
m_iFishingWindowCount = 0;
m_lFishingStartX = 0;
m_lFishingStartY = 0;
m_dwMiningWindowStart = 0;
m_iMiningWindowCount = 0;
m_iLastPMPulse = 0;
m_iPMCounter = 0;
@@ -4064,6 +4070,17 @@ void CHARACTER::ItemGetPacket(DWORD dwItemVnum, BYTE bCount, const char* szName,
d->Packet(&pack, sizeof(pack));
}
namespace
{
constexpr DWORD kActionWindowMs = 1000;
constexpr int kFishingRateWarnThreshold = 8;
constexpr int kFishingRateBlockThreshold = 18;
constexpr int kFishingTakeMaxMoveDistance = 1200;
constexpr int kMiningRateWarnThreshold = 6;
constexpr int kMiningRateBlockThreshold = 15;
constexpr int kMiningStartMaxDistance = 1000;
}
// MINING
void CHARACTER::mining_take()
{
@@ -4082,18 +4099,72 @@ void CHARACTER::mining_cancel()
void CHARACTER::mining(LPCHARACTER chLoad)
{
const DWORD dwNow = get_dword_time();
if (0 == m_dwMiningWindowStart || dwNow - m_dwMiningWindowStart >= kActionWindowMs)
{
m_dwMiningWindowStart = dwNow;
m_iMiningWindowCount = 0;
}
++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_pkMiningEvent)
{
mining_cancel();
return;
}
if (IsDead() || !CanMove() || !CanHandleItem())
{
char szDetail[128];
snprintf(szDetail,
sizeof(szDetail),
"dead=%d can_move=%d can_item=%d target=%u",
IsDead() ? 1 : 0,
CanMove() ? 1 : 0,
CanHandleItem() ? 1 : 0,
chLoad ? chLoad->GetVID() : 0);
RecordAntiCheatViolation("MINING_CONTEXT", 4, szDetail);
return;
}
if (!chLoad)
return;
if (mining::GetRawOreFromLoad(chLoad->GetRaceNum()) == 0)
return;
if (chLoad == this || chLoad->GetMapIndex() != GetMapIndex())
{
char szDetail[128];
snprintf(szDetail,
sizeof(szDetail),
"target=%u map=%ld target_map=%ld",
chLoad->GetVID(),
static_cast<long>(GetMapIndex()),
static_cast<long>(chLoad->GetMapIndex()));
RecordAntiCheatViolation("MINING_TARGET", 10, szDetail, true);
return;
}
const int iDistance = DISTANCE_APPROX(GetX() - chLoad->GetX(), GetY() - chLoad->GetY());
if (iDistance > kMiningStartMaxDistance)
{
char szDetail[128];
snprintf(szDetail, sizeof(szDetail), "target=%u distance=%d max=%d", chLoad->GetVID(), iDistance, kMiningStartMaxDistance);
RecordAntiCheatViolation("MINING_TARGET", iDistance > kMiningStartMaxDistance + 800 ? 12 : 4, szDetail, iDistance > kMiningStartMaxDistance + 800);
return;
}
LPITEM pick = GetWear(WEAR_WEAPON);
if (!pick || pick->GetType() != ITEM_PICK)
@@ -4120,12 +4191,43 @@ void CHARACTER::mining(LPCHARACTER chLoad)
void CHARACTER::fishing()
{
const DWORD dwNow = get_dword_time();
if (0 == m_dwFishingWindowStart || dwNow - m_dwFishingWindowStart >= kActionWindowMs)
{
m_dwFishingWindowStart = dwNow;
m_iFishingWindowCount = 0;
}
++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_pkFishingEvent)
{
fishing_take();
return;
}
if (IsDead() || !CanMove() || !CanHandleItem())
{
char szDetail[128];
snprintf(szDetail,
sizeof(szDetail),
"dead=%d can_move=%d can_item=%d",
IsDead() ? 1 : 0,
CanMove() ? 1 : 0,
CanHandleItem() ? 1 : 0);
RecordAntiCheatViolation("FISHING_CONTEXT", 4, szDetail);
return;
}
// 못감 속성에서 낚시를 시도한다?
{
LPSECTREE_MAP pkSectreeMap = SECTREE_MANAGER::instance().GetMap(GetMapIndex());
@@ -4161,11 +4263,36 @@ void CHARACTER::fishing()
float fx, fy;
GetDeltaByDegree(GetRotation(), 400.0f, &fx, &fy);
m_lFishingStartX = GetX();
m_lFishingStartY = GetY();
m_pkFishingEvent = fishing::CreateFishingEvent(this);
}
void CHARACTER::fishing_take()
{
if (!CanMove() || !CanHandleItem())
{
char szDetail[128];
snprintf(szDetail,
sizeof(szDetail),
"can_move=%d can_item=%d",
CanMove() ? 1 : 0,
CanHandleItem() ? 1 : 0);
RecordAntiCheatViolation("FISHING_CONTEXT", 4, szDetail);
event_cancel(&m_pkFishingEvent);
return;
}
const int iMovedDistance = DISTANCE_APPROX(GetX() - m_lFishingStartX, GetY() - m_lFishingStartY);
if (iMovedDistance > kFishingTakeMaxMoveDistance)
{
char szDetail[128];
snprintf(szDetail, sizeof(szDetail), "distance=%d max=%d", iMovedDistance, kFishingTakeMaxMoveDistance);
RecordAntiCheatViolation("FISHING_CONTEXT", iMovedDistance > kFishingTakeMaxMoveDistance + 800 ? 10 : 4, szDetail, iMovedDistance > kFishingTakeMaxMoveDistance + 800);
event_cancel(&m_pkFishingEvent);
return;
}
LPITEM rod = GetWear(WEAR_WEAPON);
if (rod && rod->GetType() == ITEM_ROD)
{