Harden melee sync authority
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 11:39:07 +02:00
parent 763d7a99b7
commit eea1875d62
4 changed files with 114 additions and 2 deletions

View File

@@ -24,6 +24,27 @@
int battle_hit(LPCHARACTER ch, LPCHARACTER victim, int & iRetDam);
namespace
{
bool battle_melee_angle_valid(LPCHARACTER ch, LPCHARACTER victim, int distance, int maxDistance, float* outRotDelta)
{
if (!ch || !victim || !ch->IsPC() || victim->IsBuilding())
return true;
if (distance <= 170)
return true;
const float desiredRotation = GetDegreeFromPositionXY(ch->GetX(), ch->GetY(), victim->GetX(), victim->GetY());
const float rotDelta = fabs(GetDegreeDelta(ch->GetRotation(), desiredRotation));
if (outRotDelta)
*outRotDelta = rotDelta;
const float allowedDelta = distance >= MAX(220, maxDistance - 40) ? 95.0f : 120.0f;
return rotDelta <= allowedDelta;
}
}
bool battle_distance_valid_by_xy(long x, long y, long tx, long ty)
{
long distance = DISTANCE_APPROX(x - tx, y - ty);
@@ -160,6 +181,25 @@ int battle_melee_attack(LPCHARACTER ch, LPCHARACTER victim)
return BATTLE_NONE;
}
float rotDelta = 0.0f;
if (!battle_melee_angle_valid(ch, victim, distance, max, &rotDelta))
{
char szDetail[160];
snprintf(szDetail,
sizeof(szDetail),
"rot_delta=%.1f distance=%d max=%d victim=%u",
rotDelta,
distance,
max,
victim->GetVID());
ch->RecordAntiCheatViolation("MELEE_ANGLE", rotDelta > 140.0f ? 18 : 8, szDetail, rotDelta > 140.0f);
if (test_server)
sys_log(0, "MELEE_ANGLE: %s rot_delta=%.1f distance=%d max=%d", ch->GetName(), rotDelta, distance, max);
return BATTLE_NONE;
}
}
if (timed_event_cancel(ch))
@@ -176,6 +216,10 @@ 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())
victim->LockSyncOwner(450);
return (ret);
}

View File

@@ -189,6 +189,7 @@ void CHARACTER::Initialize()
m_pkDestroyWhenIdleEvent = NULL;
m_pkChrSyncOwner = NULL;
m_dwSyncOwnerLockExpire = 0;
memset(&m_points, 0, sizeof(m_points));
memset(&m_pointsInstant, 0, sizeof(m_pointsInstant));
@@ -4317,6 +4318,7 @@ bool CHARACTER::SetSyncOwner(LPCHARACTER ch, bool bRemoveFromList)
// 리스트에서 제거하지 않더라도 포인터는 NULL로 셋팅되어야 한다.
m_pkChrSyncOwner = NULL;
m_dwSyncOwnerLockExpire = 0;
}
else
{
@@ -4345,6 +4347,7 @@ bool CHARACTER::SetSyncOwner(LPCHARACTER ch, bool bRemoveFromList)
m_pkChrSyncOwner = ch;
m_pkChrSyncOwner->m_kLst_pkChrSyncOwned.push_back(this);
m_dwSyncOwnerLockExpire = 0;
// SyncOwner가 바뀌면 LastSyncTime을 초기화한다.
static const timeval zero_tv = {0, 0};
@@ -4401,6 +4404,28 @@ bool CHARACTER::IsSyncOwner(LPCHARACTER ch) const
return false;
}
void CHARACTER::LockSyncOwner(DWORD dwDurationMs)
{
if (!m_pkChrSyncOwner || !dwDurationMs)
return;
const DWORD dwNow = get_dword_time();
const DWORD dwExpire = dwNow + dwDurationMs;
if (m_dwSyncOwnerLockExpire < dwExpire)
m_dwSyncOwnerLockExpire = dwExpire;
}
bool CHARACTER::IsSyncOwnerLocked() const
{
return m_pkChrSyncOwner && get_dword_time() < m_dwSyncOwnerLockExpire;
}
bool CHARACTER::IsSyncOwnerLockedFor(LPCHARACTER ch) const
{
return ch && m_pkChrSyncOwner == ch && get_dword_time() < m_dwSyncOwnerLockExpire;
}
void CHARACTER::SetParty(LPPARTY pkParty)
{
if (pkParty == m_pkParty)

View File

@@ -829,6 +829,10 @@ class CHARACTER : public CEntity, public CFSM, public CHorseRider
bool SetSyncOwner(LPCHARACTER ch, bool bRemoveFromList = true);
bool IsSyncOwner(LPCHARACTER ch) const;
LPCHARACTER GetSyncOwner() const { return m_pkChrSyncOwner; }
void LockSyncOwner(DWORD dwDurationMs);
bool IsSyncOwnerLocked() const;
bool IsSyncOwnerLockedFor(LPCHARACTER ch) const;
bool WarpSet(long x, long y, long lRealMapIndex = 0);
void SetWarpLocation(long lMapIndex, long x, long y);
@@ -852,6 +856,7 @@ class CHARACTER : public CEntity, public CFSM, public CHorseRider
float m_fSyncTime;
LPCHARACTER m_pkChrSyncOwner;
DWORD m_dwSyncOwnerLockExpire;
CHARACTER_LIST m_kLst_pkChrSyncOwned; // 내가 SyncOwner인 자들
PIXEL_POSITION m_posDest;

View File

@@ -1580,12 +1580,35 @@ void CInputMain::Move(LPCHARACTER ch, const char * data)
// FUNC_SKILL = 0x80,
//};
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)
{
char szDetail[192];
snprintf(szDetail,
sizeof(szDetail),
"func=%u dist=%.1f owner=%s cur=(%ld,%ld) dst=(%ld,%ld)",
pinfo->bFunc,
fDist,
ch->GetSyncOwner()->GetName(),
static_cast<long>(ch->GetX()),
static_cast<long>(ch->GetY()),
static_cast<long>(pinfo->lX),
static_cast<long>(pinfo->lY));
if (fDist > 8.0f)
ch->RecordAntiCheatViolation("SYNC_LOCK_MOVE", 8, szDetail, true);
else
sys_log(1, "SYNC_LOCK_MOVE: %s %s", ch->GetName(), szDetail);
ch->Show(ch->GetMapIndex(), ch->GetX(), ch->GetY(), ch->GetZ());
ch->Stop();
return;
}
// 텔레포트 핵 체크
// if (!test_server) //2012.05.15 김용욱 : 테섭에서 (무적상태로) 다수 몬스터 상대로 다운되면서 공격시 콤보핵으로 죽는 문제가 있었다.
{
const float fDist = DISTANCE_SQRT((ch->GetX() - pinfo->lX) / 100, (ch->GetY() - pinfo->lY) / 100);
if (((false == ch->IsRiding() && fDist > 25) || fDist > 40) && OXEVENT_MAP_INDEX != ch->GetMapIndex())
{
char szDetail[160];
@@ -1916,6 +1939,21 @@ int CInputMain::SyncPosition(LPCHARACTER ch, const char * c_pcData, size_t uiByt
continue;
}
if (victim->IsSyncOwnerLocked() && !victim->IsSyncOwnerLockedFor(ch))
{
char szDetail[192];
snprintf(szDetail,
sizeof(szDetail),
"victim=%s owner=%s requester=%s sync=(%ld,%ld)",
victim->GetName(),
victim->GetSyncOwner() ? victim->GetSyncOwner()->GetName() : "-",
ch->GetName(),
static_cast<long>(e->lX),
static_cast<long>(e->lY));
ch->RecordAntiCheatViolation("SYNC_OWNER_STEAL", 4, szDetail);
continue;
}
// 소유권 검사
if (!victim->SetSyncOwner(ch))
continue;