mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-02 15:53:51 +00:00
fix: harden turtle movement parsing and warden fallback
This commit is contained in:
parent
f44ef7b9ea
commit
eea3784976
3 changed files with 91 additions and 29 deletions
|
|
@ -2434,6 +2434,8 @@ private:
|
||||||
std::chrono::steady_clock::time_point movementClockStart_ = std::chrono::steady_clock::now();
|
std::chrono::steady_clock::time_point movementClockStart_ = std::chrono::steady_clock::now();
|
||||||
uint32_t lastMovementTimestampMs_ = 0;
|
uint32_t lastMovementTimestampMs_ = 0;
|
||||||
bool serverMovementAllowed_ = true;
|
bool serverMovementAllowed_ = true;
|
||||||
|
uint32_t monsterMovePacketsThisTick_ = 0;
|
||||||
|
uint32_t monsterMovePacketsDroppedThisTick_ = 0;
|
||||||
|
|
||||||
// Fall/jump tracking for movement packet correctness.
|
// Fall/jump tracking for movement packet correctness.
|
||||||
// fallTime must be the elapsed ms since the FALLING flag was set; the server
|
// fallTime must be the elapsed ms since the FALLING flag was set; the server
|
||||||
|
|
|
||||||
|
|
@ -757,6 +757,10 @@ void GameHandler::update(float deltaTime) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset per-tick monster-move budget tracking (Classic/Turtle flood protection).
|
||||||
|
monsterMovePacketsThisTick_ = 0;
|
||||||
|
monsterMovePacketsDroppedThisTick_ = 0;
|
||||||
|
|
||||||
// Update socket (processes incoming data and triggers callbacks)
|
// Update socket (processes incoming data and triggers callbacks)
|
||||||
if (socket) {
|
if (socket) {
|
||||||
auto socketStart = std::chrono::steady_clock::now();
|
auto socketStart = std::chrono::steady_clock::now();
|
||||||
|
|
@ -7960,7 +7964,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
uint64_t kickerGuid = packet.readUInt64();
|
uint64_t kickerGuid = packet.readUInt64();
|
||||||
uint32_t reasonType = packet.readUInt32();
|
uint32_t reasonType = packet.readUInt32();
|
||||||
std::string reason;
|
std::string reason;
|
||||||
if (packet.getSize() - packet.getReadPos() > 0)
|
if (packet.getReadPos() < packet.getSize())
|
||||||
reason = packet.readString();
|
reason = packet.readString();
|
||||||
(void)kickerGuid;
|
(void)kickerGuid;
|
||||||
(void)reasonType;
|
(void)reasonType;
|
||||||
|
|
@ -8006,14 +8010,14 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
uint32_t ticketId = packet.readUInt32();
|
uint32_t ticketId = packet.readUInt32();
|
||||||
std::string subject;
|
std::string subject;
|
||||||
std::string body;
|
std::string body;
|
||||||
if (packet.getSize() - packet.getReadPos() > 0) subject = packet.readString();
|
if (packet.getReadPos() < packet.getSize()) subject = packet.readString();
|
||||||
if (packet.getSize() - packet.getReadPos() > 0) body = packet.readString();
|
if (packet.getReadPos() < packet.getSize()) body = packet.readString();
|
||||||
uint32_t responseCount = 0;
|
uint32_t responseCount = 0;
|
||||||
if (packet.getSize() - packet.getReadPos() >= 4)
|
if (packet.getSize() - packet.getReadPos() >= 4)
|
||||||
responseCount = packet.readUInt32();
|
responseCount = packet.readUInt32();
|
||||||
std::string responseText;
|
std::string responseText;
|
||||||
for (uint32_t i = 0; i < responseCount && i < 10; ++i) {
|
for (uint32_t i = 0; i < responseCount && i < 10; ++i) {
|
||||||
if (packet.getSize() - packet.getReadPos() > 0) {
|
if (packet.getReadPos() < packet.getSize()) {
|
||||||
std::string t = packet.readString();
|
std::string t = packet.readString();
|
||||||
if (i == 0) responseText = t;
|
if (i == 0) responseText = t;
|
||||||
}
|
}
|
||||||
|
|
@ -9034,6 +9038,18 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> seed(decrypted.begin() + 1, decrypted.begin() + 17);
|
std::vector<uint8_t> seed(decrypted.begin() + 1, decrypted.begin() + 17);
|
||||||
|
auto applyWardenSeedRekey = [&](const std::vector<uint8_t>& rekeySeed) {
|
||||||
|
// Derive new RC4 keys from the seed using SHA1Randx.
|
||||||
|
uint8_t newEncryptKey[16], newDecryptKey[16];
|
||||||
|
WardenCrypto::sha1RandxGenerate(rekeySeed, newEncryptKey, newDecryptKey);
|
||||||
|
|
||||||
|
std::vector<uint8_t> ek(newEncryptKey, newEncryptKey + 16);
|
||||||
|
std::vector<uint8_t> dk(newDecryptKey, newDecryptKey + 16);
|
||||||
|
wardenCrypto_->replaceKeys(ek, dk);
|
||||||
|
for (auto& b : newEncryptKey) b = 0;
|
||||||
|
for (auto& b : newDecryptKey) b = 0;
|
||||||
|
LOG_DEBUG("Warden: Derived and applied key update from seed");
|
||||||
|
};
|
||||||
|
|
||||||
// --- Try CR lookup (pre-computed challenge/response entries) ---
|
// --- Try CR lookup (pre-computed challenge/response entries) ---
|
||||||
if (!wardenCREntries_.empty()) {
|
if (!wardenCREntries_.empty()) {
|
||||||
|
|
@ -9082,7 +9098,24 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
||||||
LOG_WARNING("Warden: No CR match, computing hash from loaded module");
|
LOG_WARNING("Warden: No CR match, computing hash from loaded module");
|
||||||
|
|
||||||
if (!wardenLoadedModule_ || !wardenLoadedModule_->isLoaded()) {
|
if (!wardenLoadedModule_ || !wardenLoadedModule_->isLoaded()) {
|
||||||
LOG_ERROR("Warden: No loaded module and no CR match — cannot compute hash");
|
LOG_WARNING("Warden: No loaded module and no CR match — using raw module fallback hash");
|
||||||
|
|
||||||
|
// Never skip HASH_RESULT: some realms disconnect quickly if this response is missing.
|
||||||
|
std::vector<uint8_t> fallbackReply;
|
||||||
|
if (!wardenModuleData_.empty()) {
|
||||||
|
fallbackReply = auth::Crypto::sha1(wardenModuleData_);
|
||||||
|
} else if (!wardenModuleHash_.empty()) {
|
||||||
|
fallbackReply = auth::Crypto::sha1(wardenModuleHash_);
|
||||||
|
} else {
|
||||||
|
fallbackReply.assign(20, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> resp;
|
||||||
|
resp.push_back(0x04); // WARDEN_CMSG_HASH_RESULT
|
||||||
|
resp.insert(resp.end(), fallbackReply.begin(), fallbackReply.end());
|
||||||
|
sendWardenResponse(resp);
|
||||||
|
|
||||||
|
applyWardenSeedRekey(seed);
|
||||||
wardenState_ = WardenState::WAIT_CHECKS;
|
wardenState_ = WardenState::WAIT_CHECKS;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -9171,19 +9204,7 @@ void GameHandler::handleWardenData(network::Packet& packet) {
|
||||||
resp.insert(resp.end(), reply.begin(), reply.end());
|
resp.insert(resp.end(), reply.begin(), reply.end());
|
||||||
sendWardenResponse(resp);
|
sendWardenResponse(resp);
|
||||||
|
|
||||||
// Derive new RC4 keys from the seed using SHA1Randx
|
applyWardenSeedRekey(seed);
|
||||||
std::vector<uint8_t> seedVec(seed.begin(), seed.end());
|
|
||||||
// Pad seed to at least 2 bytes for SHA1Randx split
|
|
||||||
// SHA1Randx splits input in half: first_half and second_half
|
|
||||||
uint8_t newEncryptKey[16], newDecryptKey[16];
|
|
||||||
WardenCrypto::sha1RandxGenerate(seedVec, newEncryptKey, newDecryptKey);
|
|
||||||
|
|
||||||
std::vector<uint8_t> ek(newEncryptKey, newEncryptKey + 16);
|
|
||||||
std::vector<uint8_t> dk(newDecryptKey, newDecryptKey + 16);
|
|
||||||
wardenCrypto_->replaceKeys(ek, dk);
|
|
||||||
for (auto& b : newEncryptKey) b = 0;
|
|
||||||
for (auto& b : newDecryptKey) b = 0;
|
|
||||||
LOG_DEBUG("Warden: Derived and applied key update from seed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wardenState_ = WardenState::WAIT_CHECKS;
|
wardenState_ = WardenState::WAIT_CHECKS;
|
||||||
|
|
@ -15560,9 +15581,9 @@ void GameHandler::handleLfgBootProposalUpdate(network::Packet& packet) {
|
||||||
lfgBootNeeded_ = votesNeeded;
|
lfgBootNeeded_ = votesNeeded;
|
||||||
|
|
||||||
// Optional: reason string and target name (null-terminated) follow the fixed fields
|
// Optional: reason string and target name (null-terminated) follow the fixed fields
|
||||||
if (packet.getSize() - packet.getReadPos() > 0)
|
if (packet.getReadPos() < packet.getSize())
|
||||||
lfgBootReason_ = packet.readString();
|
lfgBootReason_ = packet.readString();
|
||||||
if (packet.getSize() - packet.getReadPos() > 0)
|
if (packet.getReadPos() < packet.getSize())
|
||||||
lfgBootTargetName_ = packet.readString();
|
lfgBootTargetName_ = packet.readString();
|
||||||
|
|
||||||
if (inProgress) {
|
if (inProgress) {
|
||||||
|
|
@ -16282,6 +16303,21 @@ void GameHandler::handleCompressedMoves(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameHandler::handleMonsterMove(network::Packet& packet) {
|
void GameHandler::handleMonsterMove(network::Packet& packet) {
|
||||||
|
if (isActiveExpansion("classic") || isActiveExpansion("turtle")) {
|
||||||
|
constexpr uint32_t kMaxMonsterMovesPerTick = 256;
|
||||||
|
++monsterMovePacketsThisTick_;
|
||||||
|
if (monsterMovePacketsThisTick_ > kMaxMonsterMovesPerTick) {
|
||||||
|
++monsterMovePacketsDroppedThisTick_;
|
||||||
|
if (monsterMovePacketsDroppedThisTick_ <= 3 ||
|
||||||
|
(monsterMovePacketsDroppedThisTick_ % 100) == 0) {
|
||||||
|
LOG_WARNING("SMSG_MONSTER_MOVE: per-tick cap exceeded, dropping packet",
|
||||||
|
" (processed=", monsterMovePacketsThisTick_,
|
||||||
|
" dropped=", monsterMovePacketsDroppedThisTick_, ")");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MonsterMoveData data;
|
MonsterMoveData data;
|
||||||
auto logMonsterMoveParseFailure = [&](const std::string& msg) {
|
auto logMonsterMoveParseFailure = [&](const std::string& msg) {
|
||||||
static uint32_t failCount = 0;
|
static uint32_t failCount = 0;
|
||||||
|
|
@ -16290,14 +16326,6 @@ void GameHandler::handleMonsterMove(network::Packet& packet) {
|
||||||
LOG_WARNING(msg, " (occurrence=", failCount, ")");
|
LOG_WARNING(msg, " (occurrence=", failCount, ")");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
auto logWrappedFallbackUsed = [&]() {
|
|
||||||
static uint32_t wrappedFallbackCount = 0;
|
|
||||||
++wrappedFallbackCount;
|
|
||||||
if (wrappedFallbackCount <= 10 || (wrappedFallbackCount % 100) == 0) {
|
|
||||||
LOG_WARNING("SMSG_MONSTER_MOVE parsed via wrapped-subpacket fallback",
|
|
||||||
" (occurrence=", wrappedFallbackCount, ")");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
auto logWrappedUncompressedFallbackUsed = [&]() {
|
auto logWrappedUncompressedFallbackUsed = [&]() {
|
||||||
static uint32_t wrappedUncompressedFallbackCount = 0;
|
static uint32_t wrappedUncompressedFallbackCount = 0;
|
||||||
++wrappedUncompressedFallbackCount;
|
++wrappedUncompressedFallbackCount;
|
||||||
|
|
@ -16352,7 +16380,6 @@ void GameHandler::handleMonsterMove(network::Packet& packet) {
|
||||||
network::Packet wrappedPacket(packet.getOpcode(), stripped);
|
network::Packet wrappedPacket(packet.getOpcode(), stripped);
|
||||||
if (packetParsers_->parseMonsterMove(wrappedPacket, data)) {
|
if (packetParsers_->parseMonsterMove(wrappedPacket, data)) {
|
||||||
parsed = true;
|
parsed = true;
|
||||||
logWrappedFallbackUsed();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
|
|
|
||||||
|
|
@ -1818,6 +1818,39 @@ bool TurtlePacketParsers::parseMonsterMove(network::Packet& packet, MonsterMoveD
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto looksLikeWotlkMonsterMove = [&](network::Packet& probe) -> bool {
|
||||||
|
const size_t probeStart = probe.getReadPos();
|
||||||
|
uint64_t guid = UpdateObjectParser::readPackedGuid(probe);
|
||||||
|
if (guid == 0) {
|
||||||
|
probe.setReadPos(probeStart);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (probe.getReadPos() >= probe.getSize()) {
|
||||||
|
probe.setReadPos(probeStart);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint8_t unk = probe.readUInt8();
|
||||||
|
if (unk > 1) {
|
||||||
|
probe.setReadPos(probeStart);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (probe.getReadPos() + 12 + 4 + 1 > probe.getSize()) {
|
||||||
|
probe.setReadPos(probeStart);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
probe.readFloat(); probe.readFloat(); probe.readFloat(); // xyz
|
||||||
|
probe.readUInt32(); // splineId
|
||||||
|
uint8_t moveType = probe.readUInt8();
|
||||||
|
probe.setReadPos(probeStart);
|
||||||
|
return moveType >= 1 && moveType <= 4;
|
||||||
|
};
|
||||||
|
|
||||||
|
packet.setReadPos(start);
|
||||||
|
if (!looksLikeWotlkMonsterMove(packet)) {
|
||||||
|
packet.setReadPos(start);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
packet.setReadPos(start);
|
packet.setReadPos(start);
|
||||||
if (MonsterMoveParser::parse(packet, data)) {
|
if (MonsterMoveParser::parse(packet, data)) {
|
||||||
LOG_DEBUG("[Turtle] SMSG_MONSTER_MOVE parsed via WotLK fallback layout");
|
LOG_DEBUG("[Turtle] SMSG_MONSTER_MOVE parsed via WotLK fallback layout");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue