Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 52 additions & 3 deletions src/game/Warden/Warden.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,35 @@ void Warden::SetNewState(WardenState::Value state)
_clientResponseTimer = 0;
}

bool Warden::IsValidIncomingOpcode(uint8 opcode) const
{
switch (opcode)
{
case WARDEN_CMSG_MODULE_MISSING:
case WARDEN_CMSG_MODULE_OK:
// The client tells us whether it has the module after we sent
// WARDEN_SMSG_MODULE_USE.
return _state == WardenState::STATE_REQUESTED_MODULE;

case WARDEN_CMSG_MODULE_FAILED:
// Sent if loading the module we transferred fails on the client.
return _state == WardenState::STATE_REQUESTED_MODULE
|| _state == WardenState::STATE_SENT_MODULE;

case WARDEN_CMSG_HASH_RESULT:
// Reply to WARDEN_SMSG_HASH_REQUEST.
return _state == WardenState::STATE_REQUESTED_HASH;

case WARDEN_CMSG_CHEAT_CHECKS_RESULT:
case WARDEN_CMSG_MEM_CHECKS_RESULT:
// Reply to WARDEN_SMSG_CHEAT_CHECKS_REQUEST.
return _state == WardenState::STATE_REQUESTED_DATA;

default:
return false;
}
}

bool Warden::IsValidCheckSum(uint32 checksum, const uint8* data, const uint16 length)
{
uint32 newChecksum = BuildChecksum(data, length);
Expand Down Expand Up @@ -314,6 +343,16 @@ void WorldSession::HandleWardenDataOpcode(WorldPacket& recvData)
sLog.outWarden("Got packet, opcode %02X, size %u", opcode, uint32(recvData.size()));
recvData.hexlike();

if (!_warden->IsValidIncomingOpcode(opcode))
{
sLog.outWarden("Account %u sent Warden opcode %02X in unexpected state %s (latency %u, IP %s)",
GetAccountId(), opcode, WardenState::to_string(_warden->GetState()),
GetLatency(), GetRemoteAddress().c_str());
// Drain the packet so partial reads don't bleed into later handlers.
recvData.rpos(recvData.wpos());
return;
}

switch (opcode)
{
case WARDEN_CMSG_MODULE_MISSING:
Expand All @@ -326,17 +365,27 @@ void WorldSession::HandleWardenDataOpcode(WorldPacket& recvData)
_warden->HandleData(recvData);
break;
case WARDEN_CMSG_MEM_CHECKS_RESULT:
sLog.outWarden("NYI WARDEN_CMSG_MEM_CHECKS_RESULT received!");
// Sent by the client when a MEM_CHECK byte sequence does not match.
// We treat that as a failed check and apply the configured penalty.
sLog.outWarden("Account %u (%s) reported MEM_CHECK mismatch via WARDEN_CMSG_MEM_CHECKS_RESULT. Action: %s",
GetAccountId(), GetPlayerName(), _warden->Penalty().c_str());
recvData.rpos(recvData.wpos());
break;
case WARDEN_CMSG_HASH_RESULT:
_warden->HandleHashResult(recvData);
_warden->InitializeModule();
break;
case WARDEN_CMSG_MODULE_FAILED:
sLog.outWarden("NYI WARDEN_CMSG_MODULE_FAILED received!");
// Module failed to load on the client side - usually a cache fail.
// Log explicitly and apply penalty if configured.
sLog.outWarden("Account %u (%s) reported MODULE_FAILED. Action: %s",
GetAccountId(), GetPlayerName(), _warden->Penalty().c_str());
recvData.rpos(recvData.wpos());
break;
default:
sLog.outWarden("Got unknown warden opcode %02X of size %u.", opcode, uint32(recvData.size() - 1));
sLog.outWarden("Got unknown warden opcode %02X of size %u from account %u.",
opcode, uint32(recvData.size() - 1), GetAccountId());
recvData.rpos(recvData.wpos());
break;
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/game/Warden/Warden.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ class Warden
void EncryptData(uint8* buffer, uint32 length);

void SetNewState(WardenState::Value state);
WardenState::Value GetState() const { return _state; }

// Returns true if the given client opcode is valid given the current state.
bool IsValidIncomingOpcode(uint8 opcode) const;

static bool IsValidCheckSum(uint32 checksum, const uint8 *data, const uint16 length);
static uint32 BuildChecksum(const uint8 *data, uint32 length);
Expand Down
97 changes: 88 additions & 9 deletions src/game/Warden/WardenCheckMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,28 @@ void WardenCheckMgr::LoadWardenChecks()
sLog.outString(">> Warden disabled, loading checks skipped.");
return;
}
// 0 1 2 3 4 5 6 7 8
QueryResult *result = WorldDatabase.Query("SELECT `id`, `build`, `type`, `data`, `result`, `address`, `length`, `str`, `comment` FROM `warden` ORDER BY `build` ASC, `id` ASC");

// Detect whether the optional `groupid` column exists in this deployment's
// schema. If it does, include it in the SELECT so rotation can use it.
bool hasGroupId = false;
{
QueryResult* probe = WorldDatabase.Query("SHOW COLUMNS FROM `warden` LIKE 'groupid'");
if (probe)
{
hasGroupId = true;
delete probe;
}
}

QueryResult *result = NULL;
if (hasGroupId)
{ // 0 1 2 3 4 5 6 7 8 9
result = WorldDatabase.Query("SELECT `id`, `build`, `type`, `data`, `result`, `address`, `length`, `str`, `comment`, `groupid` FROM `warden` ORDER BY `build` ASC, `id` ASC");
}
else
{ // 0 1 2 3 4 5 6 7 8
result = WorldDatabase.Query("SELECT `id`, `build`, `type`, `data`, `result`, `address`, `length`, `str`, `comment` FROM `warden` ORDER BY `build` ASC, `id` ASC");
}

if (!result)
{
Expand All @@ -83,10 +103,12 @@ void WardenCheckMgr::LoadWardenChecks()
uint8 length = fields[6].GetUInt8();
std::string str = fields[7].GetString();
std::string comment = fields[8].GetString();
uint16 groupId = hasGroupId ? fields[9].GetUInt16() : 0;

WardenCheck* wardenCheck = new WardenCheck();
wardenCheck->Type = checkType;
wardenCheck->CheckId = id;
wardenCheck->GroupId = groupId;

// Initialize action with default action from config
wardenCheck->Action = WardenActions(sWorld.getConfig(CONFIG_UINT32_WARDEN_CLIENT_FAIL_ACTION));
Expand Down Expand Up @@ -242,23 +264,80 @@ WardenCheckResult* WardenCheckMgr::GetWardenResultById(uint16 build, uint16 id)
return result;
}

// MEM_CHECK and MODULE_CHECK go through the dedicated memory queue. Every other
// check type goes through the "other" queue. The two queues must be disjoint so
// the same check id is never sent twice in the same cycle.
static bool IsMemoryQueueCheck(uint8 type)
{
return type == MEM_CHECK || type == MODULE_CHECK;
}

void WardenCheckMgr::GetWardenCheckIds(bool isMemCheck, uint16 build, std::list<uint16>& idl)
{
idl.clear(); //just to be sure

ACE_READ_GUARD(LOCK, g, m_lock)
for (CheckMap::iterator it = CheckStore.lower_bound(build); it != CheckStore.upper_bound(build); ++it)
// Bucket by groupid so we can interleave when consuming. groupid 0 is treated
// as "no group" - one bucket per check.
typedef std::map<uint16, std::vector<uint16> > GroupBuckets;
GroupBuckets buckets;
std::vector<uint16> ungrouped;

{
if (isMemCheck)
ACE_READ_GUARD(LOCK, g, m_lock)
for (CheckMap::iterator it = CheckStore.lower_bound(build); it != CheckStore.upper_bound(build); ++it)
{
if ((it->second->Type == MEM_CHECK) || (it->second->Type == MODULE_CHECK))
const uint8 type = it->second->Type;
const bool isMem = IsMemoryQueueCheck(type);

if (isMemCheck)
{
if (!isMem)
{
continue;
}
}
else
{
idl.push_back(it->second->CheckId);
// Skip TIMING_CHECK - WardenWin::RequestData() appends a
// synthetic TIMING_CHECK per cycle.
if (isMem || type == TIMING_CHECK)
{
continue;
}
}

if (it->second->GroupId == 0)
{
ungrouped.push_back(it->second->CheckId);
}
else
{
buckets[it->second->GroupId].push_back(it->second->CheckId);
}
}
else
}

// Round-robin across grouped buckets so checks belonging to the same group
// do not all get consumed back-to-back in a single cycle. This minimizes
// repeated coverage of similar checks.
bool madeProgress;
do
{
madeProgress = false;
for (GroupBuckets::iterator it = buckets.begin(); it != buckets.end(); ++it)
{
idl.push_back(it->second->CheckId);
if (!it->second.empty())
{
idl.push_back(it->second.back());
it->second.pop_back();
madeProgress = true;
}
}
} while (madeProgress);

// Ungrouped checks tail (insertion order).
for (std::vector<uint16>::iterator it = ungrouped.begin(); it != ungrouped.end(); ++it)
{
idl.push_back(*it);
}
}
1 change: 1 addition & 0 deletions src/game/Warden/WardenCheckMgr.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ struct WardenCheck
std::string Str; // LUA, MPQ, DRIVER
std::string Comment;
uint16 CheckId;
uint16 GroupId; // Optional grouping for rotation; 0 means ungrouped
enum WardenActions Action;
};

Expand Down
Loading
Loading