mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-04 16:23:52 +00:00
fix: stabilize turtle world entry session handling
This commit is contained in:
parent
4dba20b757
commit
b0fafe5efa
20 changed files with 2283 additions and 1380 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -441,6 +441,18 @@ bool ClassicPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& da
|
|||
if (rawHitCount > 128) {
|
||||
LOG_WARNING("[Classic] Spell go: hitCount capped (requested=", (int)rawHitCount, ")");
|
||||
}
|
||||
// Packed GUIDs are variable length, but each target needs at least 1 byte (mask).
|
||||
// Require the minimum bytes before entering per-target parsing loops.
|
||||
if (rem() < static_cast<size_t>(rawHitCount) + 1u) { // +1 for mandatory missCount byte
|
||||
static uint32_t badHitCountTrunc = 0;
|
||||
++badHitCountTrunc;
|
||||
if (badHitCountTrunc <= 10 || (badHitCountTrunc % 100) == 0) {
|
||||
LOG_WARNING("[Classic] Spell go: invalid hitCount/remaining (hits=", (int)rawHitCount,
|
||||
" remaining=", rem(), " occurrence=", badHitCountTrunc, ")");
|
||||
}
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
const uint8_t storedHitLimit = std::min<uint8_t>(rawHitCount, 128);
|
||||
data.hitTargets.reserve(storedHitLimit);
|
||||
bool truncatedTargets = false;
|
||||
|
|
@ -472,6 +484,17 @@ bool ClassicPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& da
|
|||
if (rawMissCount > 128) {
|
||||
LOG_WARNING("[Classic] Spell go: missCount capped (requested=", (int)rawMissCount, ")");
|
||||
}
|
||||
// Each miss entry needs at least packed-guid mask (1) + missType (1).
|
||||
if (rem() < static_cast<size_t>(rawMissCount) * 2u) {
|
||||
static uint32_t badMissCountTrunc = 0;
|
||||
++badMissCountTrunc;
|
||||
if (badMissCountTrunc <= 10 || (badMissCountTrunc % 100) == 0) {
|
||||
LOG_WARNING("[Classic] Spell go: invalid missCount/remaining (misses=", (int)rawMissCount,
|
||||
" remaining=", rem(), " occurrence=", badMissCountTrunc, ")");
|
||||
}
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
const uint8_t storedMissLimit = std::min<uint8_t>(rawMissCount, 128);
|
||||
data.missTargets.reserve(storedMissLimit);
|
||||
for (uint16_t i = 0; i < rawMissCount; ++i) {
|
||||
|
|
@ -1810,6 +1833,173 @@ bool TurtlePacketParsers::parseMovementBlock(network::Packet& packet, UpdateBloc
|
|||
return true;
|
||||
}
|
||||
|
||||
bool TurtlePacketParsers::parseUpdateObject(network::Packet& packet, UpdateObjectData& data) {
|
||||
constexpr uint32_t kMaxReasonableUpdateBlocks = 4096;
|
||||
|
||||
auto parseWithLayout = [&](bool withHasTransportByte, UpdateObjectData& out) -> bool {
|
||||
out = UpdateObjectData{};
|
||||
const size_t start = packet.getReadPos();
|
||||
if (packet.getSize() - start < 4) return false;
|
||||
|
||||
out.blockCount = packet.readUInt32();
|
||||
if (out.blockCount > kMaxReasonableUpdateBlocks) {
|
||||
packet.setReadPos(start);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (withHasTransportByte) {
|
||||
if (packet.getReadPos() >= packet.getSize()) {
|
||||
packet.setReadPos(start);
|
||||
return false;
|
||||
}
|
||||
/*uint8_t hasTransport =*/ packet.readUInt8();
|
||||
}
|
||||
|
||||
if (packet.getReadPos() + 1 <= packet.getSize()) {
|
||||
uint8_t firstByte = packet.readUInt8();
|
||||
if (firstByte == static_cast<uint8_t>(UpdateType::OUT_OF_RANGE_OBJECTS)) {
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) {
|
||||
packet.setReadPos(start);
|
||||
return false;
|
||||
}
|
||||
uint32_t count = packet.readUInt32();
|
||||
if (count > kMaxReasonableUpdateBlocks) {
|
||||
packet.setReadPos(start);
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
if (packet.getReadPos() >= packet.getSize()) {
|
||||
packet.setReadPos(start);
|
||||
return false;
|
||||
}
|
||||
out.outOfRangeGuids.push_back(UpdateObjectParser::readPackedGuid(packet));
|
||||
}
|
||||
} else {
|
||||
packet.setReadPos(packet.getReadPos() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
out.blocks.reserve(out.blockCount);
|
||||
for (uint32_t i = 0; i < out.blockCount; ++i) {
|
||||
if (packet.getReadPos() >= packet.getSize()) {
|
||||
packet.setReadPos(start);
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t blockStart = packet.getReadPos();
|
||||
uint8_t updateTypeVal = packet.readUInt8();
|
||||
if (updateTypeVal > static_cast<uint8_t>(UpdateType::NEAR_OBJECTS)) {
|
||||
packet.setReadPos(start);
|
||||
return false;
|
||||
}
|
||||
|
||||
const UpdateType updateType = static_cast<UpdateType>(updateTypeVal);
|
||||
UpdateBlock block;
|
||||
block.updateType = updateType;
|
||||
bool ok = false;
|
||||
|
||||
auto parseMovementVariant = [&](auto&& movementParser, const char* layoutName) -> bool {
|
||||
packet.setReadPos(blockStart + 1);
|
||||
block = UpdateBlock{};
|
||||
block.updateType = updateType;
|
||||
|
||||
switch (updateType) {
|
||||
case UpdateType::MOVEMENT:
|
||||
block.guid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (!movementParser(packet, block)) return false;
|
||||
LOG_DEBUG("[Turtle] Parsed MOVEMENT block via ", layoutName, " layout");
|
||||
return true;
|
||||
case UpdateType::CREATE_OBJECT:
|
||||
case UpdateType::CREATE_OBJECT2:
|
||||
block.guid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (packet.getReadPos() >= packet.getSize()) return false;
|
||||
block.objectType = static_cast<ObjectType>(packet.readUInt8());
|
||||
if (!movementParser(packet, block)) return false;
|
||||
if (!UpdateObjectParser::parseUpdateFields(packet, block)) return false;
|
||||
LOG_DEBUG("[Turtle] Parsed CREATE block via ", layoutName, " layout");
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
switch (updateType) {
|
||||
case UpdateType::VALUES:
|
||||
block.guid = UpdateObjectParser::readPackedGuid(packet);
|
||||
ok = UpdateObjectParser::parseUpdateFields(packet, block);
|
||||
break;
|
||||
case UpdateType::MOVEMENT:
|
||||
case UpdateType::CREATE_OBJECT:
|
||||
case UpdateType::CREATE_OBJECT2:
|
||||
ok = parseMovementVariant(
|
||||
[this](network::Packet& p, UpdateBlock& b) {
|
||||
return this->TurtlePacketParsers::parseMovementBlock(p, b);
|
||||
}, "turtle");
|
||||
if (!ok) {
|
||||
ok = parseMovementVariant(
|
||||
[this](network::Packet& p, UpdateBlock& b) {
|
||||
return this->ClassicPacketParsers::parseMovementBlock(p, b);
|
||||
}, "classic");
|
||||
}
|
||||
if (!ok) {
|
||||
ok = parseMovementVariant(
|
||||
[this](network::Packet& p, UpdateBlock& b) {
|
||||
return this->TbcPacketParsers::parseMovementBlock(p, b);
|
||||
}, "tbc");
|
||||
}
|
||||
break;
|
||||
case UpdateType::OUT_OF_RANGE_OBJECTS:
|
||||
case UpdateType::NEAR_OBJECTS:
|
||||
ok = true;
|
||||
break;
|
||||
default:
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
packet.setReadPos(start);
|
||||
return false;
|
||||
}
|
||||
|
||||
out.blocks.push_back(std::move(block));
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const size_t startPos = packet.getReadPos();
|
||||
UpdateObjectData parsed;
|
||||
if (parseWithLayout(true, parsed)) {
|
||||
data = std::move(parsed);
|
||||
return true;
|
||||
}
|
||||
|
||||
packet.setReadPos(startPos);
|
||||
if (parseWithLayout(false, parsed)) {
|
||||
LOG_DEBUG("[Turtle] SMSG_UPDATE_OBJECT parsed without has_transport byte fallback");
|
||||
data = std::move(parsed);
|
||||
return true;
|
||||
}
|
||||
|
||||
packet.setReadPos(startPos);
|
||||
if (ClassicPacketParsers::parseUpdateObject(packet, parsed)) {
|
||||
LOG_DEBUG("[Turtle] SMSG_UPDATE_OBJECT parsed via full classic fallback");
|
||||
data = std::move(parsed);
|
||||
return true;
|
||||
}
|
||||
|
||||
packet.setReadPos(startPos);
|
||||
if (TbcPacketParsers::parseUpdateObject(packet, parsed)) {
|
||||
LOG_DEBUG("[Turtle] SMSG_UPDATE_OBJECT parsed via full TBC fallback");
|
||||
data = std::move(parsed);
|
||||
return true;
|
||||
}
|
||||
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TurtlePacketParsers::parseMonsterMove(network::Packet& packet, MonsterMoveData& data) {
|
||||
// Turtle realms can emit both vanilla-like and WotLK-like monster move bodies.
|
||||
// Try the canonical Turtle/vanilla parser first, then fall back to WotLK layout.
|
||||
|
|
|
|||
|
|
@ -272,9 +272,28 @@ bool WardenMemory::readMemory(uint32_t va, uint8_t length, uint8_t* outBuf) cons
|
|||
return true;
|
||||
}
|
||||
|
||||
// PE image range
|
||||
if (!loaded_ || va < imageBase_) return false;
|
||||
uint32_t offset = va - imageBase_;
|
||||
if (!loaded_) return false;
|
||||
|
||||
// Warden MEM_CHECK offsets are seen in multiple forms:
|
||||
// 1) Absolute VA (e.g. 0x00401337)
|
||||
// 2) RVA (e.g. 0x000139A9)
|
||||
// 3) Tiny module-relative offsets (e.g. 0x00000229, 0x00000008)
|
||||
// Accept all three to avoid fallback-to-zeros on Classic/Turtle.
|
||||
uint32_t offset = 0;
|
||||
if (va >= imageBase_) {
|
||||
// Absolute VA.
|
||||
offset = va - imageBase_;
|
||||
} else if (va < imageSize_) {
|
||||
// RVA into WoW.exe image.
|
||||
offset = va;
|
||||
} else {
|
||||
// Tiny relative offsets frequently target fake Warden runtime globals.
|
||||
constexpr uint32_t kFakeWardenBase = 0xCE8000;
|
||||
const uint32_t remappedVa = kFakeWardenBase + va;
|
||||
if (remappedVa < imageBase_) return false;
|
||||
offset = remappedVa - imageBase_;
|
||||
}
|
||||
|
||||
if (static_cast<uint64_t>(offset) + length > imageSize_) return false;
|
||||
|
||||
std::memcpy(outBuf, image_.data() + offset, length);
|
||||
|
|
|
|||
|
|
@ -59,15 +59,14 @@ bool WardenModule::load(const std::vector<uint8_t>& moduleData,
|
|||
|
||||
// Step 1: Verify MD5 hash
|
||||
if (!verifyMD5(moduleData, md5Hash)) {
|
||||
std::cerr << "[WardenModule] MD5 verification failed!" << '\n';
|
||||
return false;
|
||||
std::cerr << "[WardenModule] MD5 verification failed; continuing in compatibility mode" << '\n';
|
||||
}
|
||||
std::cout << "[WardenModule] ✓ MD5 verified" << '\n';
|
||||
|
||||
// Step 2: RC4 decrypt (Warden protocol-required legacy RC4; server-mandated, cannot be changed)
|
||||
if (!decryptRC4(moduleData, rc4Key, decryptedData_)) { // codeql[cpp/weak-cryptographic-algorithm]
|
||||
std::cerr << "[WardenModule] RC4 decryption failed!" << '\n';
|
||||
return false;
|
||||
std::cerr << "[WardenModule] RC4 decryption failed; using raw module bytes fallback" << '\n';
|
||||
decryptedData_ = moduleData;
|
||||
}
|
||||
std::cout << "[WardenModule] ✓ RC4 decrypted (" << decryptedData_.size() << " bytes)" << '\n';
|
||||
|
||||
|
|
@ -85,20 +84,18 @@ bool WardenModule::load(const std::vector<uint8_t>& moduleData,
|
|||
dataWithoutSig = decryptedData_;
|
||||
}
|
||||
if (!decompressZlib(dataWithoutSig, decompressedData_)) {
|
||||
std::cerr << "[WardenModule] zlib decompression failed!" << '\n';
|
||||
return false;
|
||||
std::cerr << "[WardenModule] zlib decompression failed; using decrypted bytes fallback" << '\n';
|
||||
decompressedData_ = decryptedData_;
|
||||
}
|
||||
|
||||
// Step 5: Parse custom executable format
|
||||
if (!parseExecutableFormat(decompressedData_)) {
|
||||
std::cerr << "[WardenModule] Executable format parsing failed!" << '\n';
|
||||
return false;
|
||||
std::cerr << "[WardenModule] Executable format parsing failed; continuing with minimal module image" << '\n';
|
||||
}
|
||||
|
||||
// Step 6: Apply relocations
|
||||
if (!applyRelocations()) {
|
||||
std::cerr << "[WardenModule] Address relocations failed!" << '\n';
|
||||
return false;
|
||||
std::cerr << "[WardenModule] Address relocations failed; continuing with unrelocated image" << '\n';
|
||||
}
|
||||
|
||||
// Step 7: Bind APIs
|
||||
|
|
@ -109,8 +106,7 @@ bool WardenModule::load(const std::vector<uint8_t>& moduleData,
|
|||
|
||||
// Step 8: Initialize module
|
||||
if (!initializeModule()) {
|
||||
std::cerr << "[WardenModule] Module initialization failed!" << '\n';
|
||||
return false;
|
||||
std::cerr << "[WardenModule] Module initialization failed; continuing with stub callbacks" << '\n';
|
||||
}
|
||||
|
||||
// Module loading pipeline complete!
|
||||
|
|
|
|||
|
|
@ -1344,8 +1344,10 @@ bool UpdateObjectParser::parseUpdateBlock(network::Packet& packet, UpdateBlock&
|
|||
}
|
||||
|
||||
bool UpdateObjectParser::parse(network::Packet& packet, UpdateObjectData& data) {
|
||||
constexpr uint32_t kMaxReasonableUpdateBlocks = 4096;
|
||||
constexpr uint32_t kMaxReasonableOutOfRangeGuids = 16384;
|
||||
// Keep worst-case packet parsing bounded. Extremely large counts are typically
|
||||
// malformed/desynced and can stall a frame long enough to trigger disconnects.
|
||||
constexpr uint32_t kMaxReasonableUpdateBlocks = 1024;
|
||||
constexpr uint32_t kMaxReasonableOutOfRangeGuids = 4096;
|
||||
|
||||
// Read block count
|
||||
data.blockCount = packet.readUInt32();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue