mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
fix: auto-detect Classic vs WotLK spline format in UPDATE_OBJECT
The spline parser assumed WotLK format (durationMod, durationModNext, conditional PARABOLIC fields) for all expansions. Classic/Turtle has a simpler layout: timePassed+duration+splineId+pointCount directly. Reading WotLK-specific fields from Classic data consumed wrong bytes, causing pointCount to read garbage and the entire update block to fail — losing dozens of NPC spawns in multi-block packets. Now tries Classic format first (pointCount at offset 12), then WotLK (offset 20+), then compact fallback. Also fixes WotLK SMSG_SPELL_GO hit/miss targets to use full uint64 GUIDs instead of PackedGuid, which was the root cause of garbage missCount values (46, 64, 241).
This commit is contained in:
parent
6484dfc32d
commit
ce3caf0438
1 changed files with 56 additions and 53 deletions
|
|
@ -1058,50 +1058,60 @@ bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock
|
|||
/*float finalAngle =*/ packet.readFloat();
|
||||
}
|
||||
|
||||
// Legacy UPDATE_OBJECT spline layout used by many servers:
|
||||
// timePassed, duration, splineId, durationMod, durationModNext,
|
||||
// [ANIMATION: animType(1)+animTime(4) if SPLINEFLAG_ANIMATION(0x00400000)],
|
||||
// [PARABOLIC: verticalAccel(4)+effectStartTime(4) if SPLINEFLAG_PARABOLIC(0x00000800)],
|
||||
// pointCount, points, splineMode, endPoint.
|
||||
// Spline data layout varies by expansion:
|
||||
// Classic/Vanilla: timePassed(4)+duration(4)+splineId(4)+pointCount(4)+points+mode(1)+endPoint(12)
|
||||
// WotLK: timePassed(4)+duration(4)+splineId(4)+durationMod(4)+durationModNext(4)
|
||||
// +[ANIMATION(5)]+[PARABOLIC(8)]+pointCount(4)+points+mode(1)+endPoint(12)
|
||||
// Since the parser has no expansion context, auto-detect by trying Classic first.
|
||||
const size_t legacyStart = packet.getReadPos();
|
||||
if (!bytesAvailable(12 + 8 + 4)) return false;
|
||||
if (!bytesAvailable(16)) return false; // minimum: 12 common + 4 pointCount
|
||||
/*uint32_t timePassed =*/ packet.readUInt32();
|
||||
/*uint32_t duration =*/ packet.readUInt32();
|
||||
/*uint32_t splineId =*/ packet.readUInt32();
|
||||
/*float durationMod =*/ packet.readFloat();
|
||||
/*float durationModNext =*/ packet.readFloat();
|
||||
if (splineFlags & 0x00400000) { // SPLINEFLAG_ANIMATION
|
||||
if (!bytesAvailable(5)) return false;
|
||||
packet.readUInt8(); // animationType
|
||||
packet.readUInt32(); // animTime
|
||||
}
|
||||
if (splineFlags & 0x00000800) { // SPLINEFLAG_PARABOLIC
|
||||
if (!bytesAvailable(8)) return false;
|
||||
/*float verticalAccel =*/ packet.readFloat();
|
||||
/*uint32_t effectStartTime =*/ packet.readUInt32();
|
||||
}
|
||||
if (!bytesAvailable(4)) return false;
|
||||
uint32_t pointCount = packet.readUInt32();
|
||||
const size_t afterSplineId = packet.getReadPos();
|
||||
|
||||
const size_t remainingAfterCount = packet.getSize() - packet.getReadPos();
|
||||
const bool legacyCountLooksValid = (pointCount <= 256);
|
||||
const size_t legacyPointsBytes = static_cast<size_t>(pointCount) * 12ull;
|
||||
const bool legacyPayloadFits = (legacyPointsBytes + 13ull) <= remainingAfterCount;
|
||||
|
||||
if (legacyCountLooksValid && legacyPayloadFits) {
|
||||
for (uint32_t i = 0; i < pointCount; i++) {
|
||||
/*float px =*/ packet.readFloat();
|
||||
/*float py =*/ packet.readFloat();
|
||||
/*float pz =*/ packet.readFloat();
|
||||
// Helper: try to parse uncompressed spline points from current read position.
|
||||
auto tryParseUncompressedSpline = [&](const char* tag) -> bool {
|
||||
if (!bytesAvailable(4)) return false;
|
||||
uint32_t pc = packet.readUInt32();
|
||||
if (pc > 256) return false;
|
||||
size_t needed = static_cast<size_t>(pc) * 12ull + 13ull;
|
||||
if (!bytesAvailable(needed)) return false;
|
||||
for (uint32_t i = 0; i < pc; i++) {
|
||||
packet.readFloat(); packet.readFloat(); packet.readFloat();
|
||||
}
|
||||
/*uint8_t splineMode =*/ packet.readUInt8();
|
||||
/*float endPointX =*/ packet.readFloat();
|
||||
/*float endPointY =*/ packet.readFloat();
|
||||
/*float endPointZ =*/ packet.readFloat();
|
||||
LOG_DEBUG(" Spline pointCount=", pointCount, " (legacy)");
|
||||
} else {
|
||||
// Legacy pointCount looks invalid; try compact WotLK layout as recovery.
|
||||
// This keeps malformed/variant packets from desyncing the whole update block.
|
||||
packet.readUInt8(); // splineMode
|
||||
packet.readFloat(); packet.readFloat(); packet.readFloat(); // endPoint
|
||||
LOG_DEBUG(" Spline pointCount=", pc, " (", tag, ")");
|
||||
return true;
|
||||
};
|
||||
|
||||
// --- Try 1: Classic format (pointCount immediately after splineId) ---
|
||||
bool splineParsed = tryParseUncompressedSpline("classic");
|
||||
|
||||
// --- Try 2: WotLK format (durationMod+durationModNext+conditional+pointCount) ---
|
||||
if (!splineParsed) {
|
||||
packet.setReadPos(afterSplineId);
|
||||
bool wotlkOk = bytesAvailable(8); // durationMod + durationModNext
|
||||
if (wotlkOk) {
|
||||
/*float durationMod =*/ packet.readFloat();
|
||||
/*float durationModNext =*/ packet.readFloat();
|
||||
if (splineFlags & 0x00400000) { // SPLINEFLAG_ANIMATION
|
||||
if (!bytesAvailable(5)) { wotlkOk = false; }
|
||||
else { packet.readUInt8(); packet.readUInt32(); }
|
||||
}
|
||||
}
|
||||
if (wotlkOk && (splineFlags & 0x00000800)) { // SPLINEFLAG_PARABOLIC
|
||||
if (!bytesAvailable(8)) { wotlkOk = false; }
|
||||
else { packet.readFloat(); packet.readUInt32(); }
|
||||
}
|
||||
if (wotlkOk) {
|
||||
splineParsed = tryParseUncompressedSpline("wotlk");
|
||||
}
|
||||
}
|
||||
|
||||
// --- Try 3: Compact layout (compressed points) as final recovery ---
|
||||
if (!splineParsed) {
|
||||
packet.setReadPos(legacyStart);
|
||||
const size_t afterFinalFacingPos = packet.getReadPos();
|
||||
if (splineFlags & 0x00400000) { // Animation
|
||||
|
|
@ -1122,8 +1132,7 @@ bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock
|
|||
static uint32_t badSplineCount = 0;
|
||||
++badSplineCount;
|
||||
if (badSplineCount <= 5 || (badSplineCount % 100) == 0) {
|
||||
LOG_WARNING(" Spline pointCount=", pointCount,
|
||||
" invalid (legacy+compact) at readPos=",
|
||||
LOG_WARNING(" Spline invalid (classic+wotlk+compact) at readPos=",
|
||||
afterFinalFacingPos, "/", packet.getSize(),
|
||||
", occurrence=", badSplineCount);
|
||||
}
|
||||
|
|
@ -1143,7 +1152,7 @@ bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock
|
|||
if (!bytesAvailable(compactPayloadBytes)) return false;
|
||||
packet.setReadPos(packet.getReadPos() + compactPayloadBytes);
|
||||
}
|
||||
} // end else (compact fallback)
|
||||
} // end compact fallback
|
||||
}
|
||||
}
|
||||
else if (updateFlags & UPDATEFLAG_POSITION) {
|
||||
|
|
@ -3869,13 +3878,13 @@ bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) {
|
|||
|
||||
data.hitTargets.reserve(storedHitLimit);
|
||||
for (uint16_t i = 0; i < rawHitCount; ++i) {
|
||||
// WotLK hit targets are packed GUIDs, like the caster and miss targets.
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
// WotLK 3.3.5a hit targets are full uint64 GUIDs (not PackedGuid).
|
||||
if (packet.getSize() - packet.getReadPos() < 8) {
|
||||
LOG_WARNING("Spell go: truncated hit targets at index ", i, "/", (int)rawHitCount);
|
||||
truncatedTargets = true;
|
||||
break;
|
||||
}
|
||||
const uint64_t targetGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
const uint64_t targetGuid = packet.readUInt64();
|
||||
if (i < storedHitLimit) {
|
||||
data.hitTargets.push_back(targetGuid);
|
||||
}
|
||||
|
|
@ -3923,22 +3932,16 @@ bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) {
|
|||
|
||||
data.missTargets.reserve(storedMissLimit);
|
||||
for (uint16_t i = 0; i < rawMissCount; ++i) {
|
||||
// Each miss entry: packed GUID(1-8 bytes) + missType(1 byte).
|
||||
// WotLK 3.3.5a miss targets are full uint64 GUIDs + uint8 missType.
|
||||
// REFLECT additionally appends uint8 reflectResult.
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
if (packet.getSize() - packet.getReadPos() < 9) { // 8 GUID + 1 missType
|
||||
LOG_WARNING("Spell go: truncated miss targets at index ", i, "/", (int)rawMissCount,
|
||||
" spell=", data.spellId, " hits=", (int)data.hitCount);
|
||||
truncatedTargets = true;
|
||||
break;
|
||||
}
|
||||
SpellGoMissEntry m;
|
||||
m.targetGuid = UpdateObjectParser::readPackedGuid(packet); // packed GUID in WotLK
|
||||
if (packet.getSize() - packet.getReadPos() < 1) {
|
||||
LOG_WARNING("Spell go: missing missType at miss index ", i, "/", (int)rawMissCount,
|
||||
" spell=", data.spellId);
|
||||
truncatedTargets = true;
|
||||
break;
|
||||
}
|
||||
m.targetGuid = packet.readUInt64();
|
||||
m.missType = packet.readUInt8();
|
||||
if (m.missType == 11) { // SPELL_MISS_REFLECT
|
||||
if (packet.getSize() - packet.getReadPos() < 1) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue