mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Compare commits
31 commits
d34a3967f6
...
2aebf3dd2f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2aebf3dd2f | ||
|
|
98acf28bee | ||
|
|
73f38eaa76 | ||
|
|
a42428d117 | ||
|
|
f930ecbffd | ||
|
|
c7dffccb4e | ||
|
|
442d3baea5 | ||
|
|
fb7a7bf76e | ||
|
|
b2f03b2fe0 | ||
|
|
87359f0e1c | ||
|
|
4f5c051199 | ||
|
|
b8cf867814 | ||
|
|
51ef28e3f7 | ||
|
|
96fc315c47 | ||
|
|
fbcbdc2935 | ||
|
|
4e97a19b23 | ||
|
|
3dba13bbd2 | ||
|
|
debc47b5cc | ||
|
|
2006b71d48 | ||
|
|
0c7dfdebe9 | ||
|
|
43cc2635ac | ||
|
|
98267d6517 | ||
|
|
c32dbb082d | ||
|
|
f2204f9d7b | ||
|
|
bfe9167a42 | ||
|
|
0077986a22 | ||
|
|
b8d694d6b3 | ||
|
|
51cff764a9 | ||
|
|
7f5dedd57e | ||
|
|
6d06da52be | ||
|
|
5756826723 |
4 changed files with 507 additions and 210 deletions
|
|
@ -116,6 +116,25 @@ bool hasFullPackedGuid(const network::Packet& packet) {
|
|||
return packet.getSize() - packet.getReadPos() >= guidBytes;
|
||||
}
|
||||
|
||||
CombatTextEntry::Type combatTextTypeFromSpellMissInfo(uint8_t missInfo) {
|
||||
switch (missInfo) {
|
||||
case 0: return CombatTextEntry::MISS;
|
||||
case 1: return CombatTextEntry::DODGE;
|
||||
case 2: return CombatTextEntry::PARRY;
|
||||
case 3: return CombatTextEntry::BLOCK;
|
||||
case 4: return CombatTextEntry::EVADE;
|
||||
case 5: return CombatTextEntry::IMMUNE;
|
||||
case 6: return CombatTextEntry::DEFLECT;
|
||||
case 7: return CombatTextEntry::ABSORB;
|
||||
case 8: return CombatTextEntry::RESIST;
|
||||
case 9: // Some cores encode SPELL_MISS_IMMUNE2 as 9.
|
||||
case 10: // Others encode SPELL_MISS_IMMUNE2 as 10.
|
||||
return CombatTextEntry::IMMUNE;
|
||||
case 11: return CombatTextEntry::REFLECT;
|
||||
default: return CombatTextEntry::MISS;
|
||||
}
|
||||
}
|
||||
|
||||
std::string formatCopperAmount(uint32_t amount) {
|
||||
uint32_t gold = amount / 10000;
|
||||
uint32_t silver = (amount / 100) % 100;
|
||||
|
|
@ -2741,39 +2760,56 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
uint64_t casterGuid = readSpellMissGuid();
|
||||
if (packet.getSize() - packet.getReadPos() < 5) break;
|
||||
/*uint8_t unk =*/ packet.readUInt8();
|
||||
uint32_t count = packet.readUInt32();
|
||||
count = std::min(count, 32u);
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
const uint32_t rawCount = packet.readUInt32();
|
||||
if (rawCount > 128) {
|
||||
LOG_WARNING("SMSG_SPELLLOGMISS: miss count capped (requested=", rawCount, ")");
|
||||
}
|
||||
const uint32_t storedLimit = std::min<uint32_t>(rawCount, 128u);
|
||||
|
||||
struct SpellMissLogEntry {
|
||||
uint64_t victimGuid = 0;
|
||||
uint8_t missInfo = 0;
|
||||
};
|
||||
std::vector<SpellMissLogEntry> parsedMisses;
|
||||
parsedMisses.reserve(storedLimit);
|
||||
|
||||
bool truncated = false;
|
||||
for (uint32_t i = 0; i < rawCount; ++i) {
|
||||
if (packet.getSize() - packet.getReadPos() < (spellMissUsesFullGuid ? 9u : 2u)
|
||||
|| (!spellMissUsesFullGuid && !hasFullPackedGuid(packet))) {
|
||||
packet.setReadPos(packet.getSize()); break;
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
uint64_t victimGuid = readSpellMissGuid();
|
||||
if (packet.getSize() - packet.getReadPos() < 1) break;
|
||||
uint8_t missInfo = packet.readUInt8();
|
||||
const uint64_t victimGuid = readSpellMissGuid();
|
||||
if (packet.getSize() - packet.getReadPos() < 1) {
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
const uint8_t missInfo = packet.readUInt8();
|
||||
// REFLECT (11): extra uint32 reflectSpellId + uint8 reflectResult
|
||||
if (missInfo == 11) {
|
||||
if (packet.getSize() - packet.getReadPos() >= 5) {
|
||||
/*uint32_t reflectSpellId =*/ packet.readUInt32();
|
||||
/*uint8_t reflectResult =*/ packet.readUInt8();
|
||||
} else {
|
||||
packet.setReadPos(packet.getSize());
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
static const CombatTextEntry::Type missTypes[] = {
|
||||
CombatTextEntry::MISS, // 0=MISS
|
||||
CombatTextEntry::DODGE, // 1=DODGE
|
||||
CombatTextEntry::PARRY, // 2=PARRY
|
||||
CombatTextEntry::BLOCK, // 3=BLOCK
|
||||
CombatTextEntry::EVADE, // 4=EVADE
|
||||
CombatTextEntry::IMMUNE, // 5=IMMUNE
|
||||
CombatTextEntry::DEFLECT, // 6=DEFLECT
|
||||
CombatTextEntry::ABSORB, // 7=ABSORB
|
||||
CombatTextEntry::RESIST, // 8=RESIST
|
||||
};
|
||||
CombatTextEntry::Type ct = (missInfo < 9) ? missTypes[missInfo]
|
||||
: (missInfo == 11 ? CombatTextEntry::REFLECT : CombatTextEntry::MISS);
|
||||
if (i < storedLimit) {
|
||||
parsedMisses.push_back({victimGuid, missInfo});
|
||||
}
|
||||
}
|
||||
|
||||
if (truncated) {
|
||||
packet.setReadPos(packet.getSize());
|
||||
break;
|
||||
}
|
||||
|
||||
for (const auto& miss : parsedMisses) {
|
||||
const uint64_t victimGuid = miss.victimGuid;
|
||||
const uint8_t missInfo = miss.missInfo;
|
||||
CombatTextEntry::Type ct = combatTextTypeFromSpellMissInfo(missInfo);
|
||||
if (casterGuid == playerGuid) {
|
||||
// We cast a spell and it missed the target
|
||||
addCombatText(ct, 0, spellId, true, 0, casterGuid, victimGuid);
|
||||
|
|
@ -4090,11 +4126,13 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
return (packet.getSize() - packet.getReadPos() >= 8) ? packet.readUInt64() : 0;
|
||||
return UpdateObjectParser::readPackedGuid(packet);
|
||||
};
|
||||
if (packet.getSize() - packet.getReadPos() < (energizeTbc ? 8u : 1u)) {
|
||||
if (packet.getSize() - packet.getReadPos() < (energizeTbc ? 8u : 1u)
|
||||
|| (!energizeTbc && !hasFullPackedGuid(packet))) {
|
||||
packet.setReadPos(packet.getSize()); break;
|
||||
}
|
||||
uint64_t victimGuid = readEnergizeGuid();
|
||||
if (packet.getSize() - packet.getReadPos() < (energizeTbc ? 8u : 1u)) {
|
||||
if (packet.getSize() - packet.getReadPos() < (energizeTbc ? 8u : 1u)
|
||||
|| (!energizeTbc && !hasFullPackedGuid(packet))) {
|
||||
packet.setReadPos(packet.getSize()); break;
|
||||
}
|
||||
uint64_t casterGuid = readEnergizeGuid();
|
||||
|
|
@ -6489,7 +6527,10 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
}
|
||||
uint64_t ikVictim = ikUsesFullGuid
|
||||
? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet);
|
||||
uint32_t ikSpell = (ik_rem() >= 4) ? packet.readUInt32() : 0;
|
||||
if (ik_rem() < 4) {
|
||||
packet.setReadPos(packet.getSize()); break;
|
||||
}
|
||||
uint32_t ikSpell = packet.readUInt32();
|
||||
// Show kill/death feedback for the local player
|
||||
if (ikCaster == playerGuid) {
|
||||
addCombatText(CombatTextEntry::INSTAKILL, 0, ikSpell, true, 0, ikCaster, ikVictim);
|
||||
|
|
@ -7105,11 +7146,19 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
? packet.readUInt64() : UpdateObjectParser::readPackedGuid(packet);
|
||||
if (rl_rem() < 4) { packet.setReadPos(packet.getSize()); break; }
|
||||
uint32_t spellId = packet.readUInt32();
|
||||
// Resist payload includes:
|
||||
// float resistFactor + uint32 targetResistance + uint32 resistedValue.
|
||||
// Require the full payload so truncated packets cannot synthesize
|
||||
// zero-value resist events.
|
||||
if (rl_rem() < 12) { packet.setReadPos(packet.getSize()); break; }
|
||||
/*float resistFactor =*/ packet.readFloat();
|
||||
/*uint32_t targetRes =*/ packet.readUInt32();
|
||||
int32_t resistedAmount = static_cast<int32_t>(packet.readUInt32());
|
||||
// Show RESIST when the player is involved on either side.
|
||||
if (victimGuid == playerGuid) {
|
||||
addCombatText(CombatTextEntry::RESIST, 0, spellId, false, 0, attackerGuid, victimGuid);
|
||||
} else if (attackerGuid == playerGuid) {
|
||||
addCombatText(CombatTextEntry::RESIST, 0, spellId, true, 0, attackerGuid, victimGuid);
|
||||
if (resistedAmount > 0 && victimGuid == playerGuid) {
|
||||
addCombatText(CombatTextEntry::RESIST, resistedAmount, spellId, false, 0, attackerGuid, victimGuid);
|
||||
} else if (resistedAmount > 0 && attackerGuid == playerGuid) {
|
||||
addCombatText(CombatTextEntry::RESIST, resistedAmount, spellId, true, 0, attackerGuid, victimGuid);
|
||||
}
|
||||
packet.setReadPos(packet.getSize());
|
||||
break;
|
||||
|
|
@ -14381,8 +14430,11 @@ void GameHandler::addCombatText(CombatTextEntry::Type type, int32_t amount, uint
|
|||
log.spellId = spellId;
|
||||
log.isPlayerSource = isPlayerSource;
|
||||
log.timestamp = std::time(nullptr);
|
||||
// If the caller provided an explicit destination GUID but left source GUID as 0,
|
||||
// preserve "unknown/no source" (e.g. environmental damage) instead of
|
||||
// backfilling from current target.
|
||||
uint64_t effectiveSrc = (srcGuid != 0) ? srcGuid
|
||||
: (isPlayerSource ? playerGuid : targetGuid);
|
||||
: ((dstGuid != 0) ? 0 : (isPlayerSource ? playerGuid : targetGuid));
|
||||
uint64_t effectiveDst = (dstGuid != 0) ? dstGuid
|
||||
: (isPlayerSource ? targetGuid : playerGuid);
|
||||
log.sourceName = lookupName(effectiveSrc);
|
||||
|
|
@ -17224,17 +17276,6 @@ void GameHandler::handleSpellGo(network::Packet& packet) {
|
|||
// Preserve spellId and actual participants for spell-go miss results.
|
||||
// This keeps the persistent combat log aligned with the later GUID fixes.
|
||||
if (!data.missTargets.empty()) {
|
||||
static const CombatTextEntry::Type missTypes[] = {
|
||||
CombatTextEntry::MISS, // 0=MISS
|
||||
CombatTextEntry::DODGE, // 1=DODGE
|
||||
CombatTextEntry::PARRY, // 2=PARRY
|
||||
CombatTextEntry::BLOCK, // 3=BLOCK
|
||||
CombatTextEntry::EVADE, // 4=EVADE
|
||||
CombatTextEntry::IMMUNE, // 5=IMMUNE
|
||||
CombatTextEntry::DEFLECT, // 6=DEFLECT
|
||||
CombatTextEntry::ABSORB, // 7=ABSORB
|
||||
CombatTextEntry::RESIST, // 8=RESIST
|
||||
};
|
||||
const uint64_t spellCasterGuid = data.casterUnit != 0 ? data.casterUnit : data.casterGuid;
|
||||
const bool playerIsCaster = (spellCasterGuid == playerGuid);
|
||||
|
||||
|
|
@ -17242,8 +17283,7 @@ void GameHandler::handleSpellGo(network::Packet& packet) {
|
|||
if (!playerIsCaster && m.targetGuid != playerGuid) {
|
||||
continue;
|
||||
}
|
||||
CombatTextEntry::Type ct = (m.missType < 9) ? missTypes[m.missType]
|
||||
: (m.missType == 11 ? CombatTextEntry::REFLECT : CombatTextEntry::MISS);
|
||||
CombatTextEntry::Type ct = combatTextTypeFromSpellMissInfo(m.missType);
|
||||
addCombatText(ct, 0, data.spellId, playerIsCaster, 0, spellCasterGuid, m.targetGuid);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -355,11 +355,21 @@ network::Packet ClassicPacketParsers::buildUseItem(uint8_t bagIndex, uint8_t slo
|
|||
// + uint16(targetFlags) [+ PackedGuid(unitTarget) if TARGET_FLAG_UNIT]
|
||||
// ============================================================================
|
||||
bool ClassicPacketParsers::parseSpellStart(network::Packet& packet, SpellStartData& data) {
|
||||
data = SpellStartData{};
|
||||
|
||||
auto rem = [&]() { return packet.getSize() - packet.getReadPos(); };
|
||||
const size_t startPos = packet.getReadPos();
|
||||
if (rem() < 2) return false;
|
||||
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.casterGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (rem() < 1) return false;
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.casterUnit = UpdateObjectParser::readPackedGuid(packet);
|
||||
|
||||
// uint8 castCount + uint32 spellId + uint16 castFlags + uint32 castTime = 11 bytes
|
||||
|
|
@ -370,10 +380,18 @@ bool ClassicPacketParsers::parseSpellStart(network::Packet& packet, SpellStartDa
|
|||
data.castTime = packet.readUInt32();
|
||||
|
||||
// SpellCastTargets: uint16 targetFlags in Vanilla (uint32 in TBC/WotLK)
|
||||
if (rem() < 2) return true;
|
||||
if (rem() < 2) {
|
||||
LOG_WARNING("[Classic] Spell start: missing targetFlags");
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
uint16_t targetFlags = packet.readUInt16();
|
||||
// TARGET_FLAG_UNIT (0x02) or TARGET_FLAG_OBJECT (0x800) carry a packed GUID
|
||||
if (((targetFlags & 0x02) || (targetFlags & 0x800)) && rem() >= 1) {
|
||||
if ((targetFlags & 0x02) || (targetFlags & 0x800)) {
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.targetGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
}
|
||||
|
||||
|
|
@ -395,11 +413,16 @@ bool ClassicPacketParsers::parseSpellStart(network::Packet& packet, SpellStartDa
|
|||
// + uint8(missCount) + [PackedGuid(missTarget) + uint8(missType)] × missCount
|
||||
// ============================================================================
|
||||
bool ClassicPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& data) {
|
||||
// Always reset output to avoid stale targets when callers reuse buffers.
|
||||
data = SpellGoData{};
|
||||
|
||||
auto rem = [&]() { return packet.getSize() - packet.getReadPos(); };
|
||||
const size_t startPos = packet.getReadPos();
|
||||
if (rem() < 2) return false;
|
||||
|
||||
if (!hasFullPackedGuid(packet)) return false;
|
||||
data.casterGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (rem() < 1) return false;
|
||||
if (!hasFullPackedGuid(packet)) return false;
|
||||
data.casterUnit = UpdateObjectParser::readPackedGuid(packet);
|
||||
|
||||
// uint8 castCount + uint32 spellId + uint16 castFlags = 7 bytes
|
||||
|
|
@ -408,52 +431,84 @@ bool ClassicPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& da
|
|||
data.spellId = packet.readUInt32();
|
||||
data.castFlags = packet.readUInt16(); // uint16 in Vanilla (uint32 in TBC/WotLK)
|
||||
|
||||
// Hit targets
|
||||
if (rem() < 1) return true;
|
||||
data.hitCount = packet.readUInt8();
|
||||
// Cap hit count to prevent OOM from huge target lists
|
||||
if (data.hitCount > 128) {
|
||||
LOG_WARNING("[Classic] Spell go: hitCount capped (requested=", (int)data.hitCount, ")");
|
||||
data.hitCount = 128;
|
||||
// hitCount is mandatory in SMSG_SPELL_GO. Missing byte means truncation.
|
||||
if (rem() < 1) {
|
||||
LOG_WARNING("[Classic] Spell go: missing hitCount after fixed fields");
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.hitTargets.reserve(data.hitCount);
|
||||
for (uint8_t i = 0; i < data.hitCount && rem() >= 1; ++i) {
|
||||
data.hitTargets.push_back(UpdateObjectParser::readPackedGuid(packet));
|
||||
const uint8_t rawHitCount = packet.readUInt8();
|
||||
if (rawHitCount > 128) {
|
||||
LOG_WARNING("[Classic] Spell go: hitCount capped (requested=", (int)rawHitCount, ")");
|
||||
}
|
||||
// Check if we read all expected hits
|
||||
if (data.hitTargets.size() < data.hitCount) {
|
||||
LOG_WARNING("[Classic] Spell go: truncated hit targets at index ", (int)data.hitTargets.size(),
|
||||
"/", (int)data.hitCount);
|
||||
data.hitCount = data.hitTargets.size();
|
||||
const uint8_t storedHitLimit = std::min<uint8_t>(rawHitCount, 128);
|
||||
data.hitTargets.reserve(storedHitLimit);
|
||||
bool truncatedTargets = false;
|
||||
for (uint16_t i = 0; i < rawHitCount; ++i) {
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
LOG_WARNING("[Classic] Spell go: truncated hit targets at index ", i,
|
||||
"/", (int)rawHitCount);
|
||||
truncatedTargets = true;
|
||||
break;
|
||||
}
|
||||
const uint64_t targetGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (i < storedHitLimit) {
|
||||
data.hitTargets.push_back(targetGuid);
|
||||
}
|
||||
}
|
||||
if (truncatedTargets) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.hitCount = static_cast<uint8_t>(data.hitTargets.size());
|
||||
|
||||
// Miss targets
|
||||
if (rem() < 1) return true;
|
||||
data.missCount = packet.readUInt8();
|
||||
// Cap miss count to prevent OOM
|
||||
if (data.missCount > 128) {
|
||||
LOG_WARNING("[Classic] Spell go: missCount capped (requested=", (int)data.missCount, ")");
|
||||
data.missCount = 128;
|
||||
// missCount is mandatory in SMSG_SPELL_GO. Missing byte means truncation.
|
||||
if (rem() < 1) {
|
||||
LOG_WARNING("[Classic] Spell go: missing missCount after hit target list");
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.missTargets.reserve(data.missCount);
|
||||
for (uint8_t i = 0; i < data.missCount && rem() >= 2; ++i) {
|
||||
const uint8_t rawMissCount = packet.readUInt8();
|
||||
if (rawMissCount > 128) {
|
||||
LOG_WARNING("[Classic] Spell go: missCount capped (requested=", (int)rawMissCount, ")");
|
||||
}
|
||||
const uint8_t storedMissLimit = std::min<uint8_t>(rawMissCount, 128);
|
||||
data.missTargets.reserve(storedMissLimit);
|
||||
for (uint16_t i = 0; i < rawMissCount; ++i) {
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
LOG_WARNING("[Classic] Spell go: truncated miss targets at index ", i,
|
||||
"/", (int)rawMissCount);
|
||||
truncatedTargets = true;
|
||||
break;
|
||||
}
|
||||
SpellGoMissEntry m;
|
||||
m.targetGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (rem() < 1) break;
|
||||
if (rem() < 1) {
|
||||
LOG_WARNING("[Classic] Spell go: missing missType at miss index ", i,
|
||||
"/", (int)rawMissCount);
|
||||
truncatedTargets = true;
|
||||
break;
|
||||
}
|
||||
m.missType = packet.readUInt8();
|
||||
if (m.missType == 11) {
|
||||
if (rem() < 5) break;
|
||||
if (rem() < 5) {
|
||||
LOG_WARNING("[Classic] Spell go: truncated reflect payload at miss index ", i,
|
||||
"/", (int)rawMissCount);
|
||||
truncatedTargets = true;
|
||||
break;
|
||||
}
|
||||
(void)packet.readUInt32();
|
||||
(void)packet.readUInt8();
|
||||
}
|
||||
data.missTargets.push_back(m);
|
||||
if (i < storedMissLimit) {
|
||||
data.missTargets.push_back(m);
|
||||
}
|
||||
}
|
||||
// Check if we read all expected misses
|
||||
if (data.missTargets.size() < data.missCount) {
|
||||
LOG_WARNING("[Classic] Spell go: truncated miss targets at index ", (int)data.missTargets.size(),
|
||||
"/", (int)data.missCount);
|
||||
data.missCount = data.missTargets.size();
|
||||
if (truncatedTargets) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.missCount = static_cast<uint8_t>(data.missTargets.size());
|
||||
|
||||
LOG_DEBUG("[Classic] Spell go: spell=", data.spellId, " hits=", (int)data.hitCount,
|
||||
" misses=", (int)data.missCount);
|
||||
|
|
@ -471,19 +526,42 @@ bool ClassicPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& da
|
|||
// + uint32(victimState) + int32(overkill) [+ uint32(blocked)]
|
||||
// ============================================================================
|
||||
bool ClassicPacketParsers::parseAttackerStateUpdate(network::Packet& packet, AttackerStateUpdateData& data) {
|
||||
data = AttackerStateUpdateData{};
|
||||
|
||||
auto rem = [&]() { return packet.getSize() - packet.getReadPos(); };
|
||||
if (rem() < 5) return false; // hitInfo(4) + at least GUID mask byte(1)
|
||||
|
||||
const size_t startPos = packet.getReadPos();
|
||||
data.hitInfo = packet.readUInt32();
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.attackerGuid = UpdateObjectParser::readPackedGuid(packet); // PackedGuid in Vanilla
|
||||
if (rem() < 1) return false;
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.targetGuid = UpdateObjectParser::readPackedGuid(packet); // PackedGuid in Vanilla
|
||||
|
||||
if (rem() < 5) return false; // int32 totalDamage + uint8 subDamageCount
|
||||
if (rem() < 5) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
} // int32 totalDamage + uint8 subDamageCount
|
||||
data.totalDamage = static_cast<int32_t>(packet.readUInt32());
|
||||
data.subDamageCount = packet.readUInt8();
|
||||
|
||||
for (uint8_t i = 0; i < data.subDamageCount && rem() >= 20; ++i) {
|
||||
const uint8_t maxSubDamageCount = static_cast<uint8_t>(std::min<size_t>(rem() / 20, 64));
|
||||
if (data.subDamageCount > maxSubDamageCount) {
|
||||
data.subDamageCount = maxSubDamageCount;
|
||||
}
|
||||
|
||||
data.subDamages.reserve(data.subDamageCount);
|
||||
for (uint8_t i = 0; i < data.subDamageCount; ++i) {
|
||||
if (rem() < 20) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
SubDamage sub;
|
||||
sub.schoolMask = packet.readUInt32();
|
||||
sub.damage = packet.readFloat();
|
||||
|
|
@ -492,8 +570,12 @@ bool ClassicPacketParsers::parseAttackerStateUpdate(network::Packet& packet, Att
|
|||
sub.resisted = packet.readUInt32();
|
||||
data.subDamages.push_back(sub);
|
||||
}
|
||||
data.subDamageCount = static_cast<uint8_t>(data.subDamages.size());
|
||||
|
||||
if (rem() < 8) return true;
|
||||
if (rem() < 8) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.victimState = packet.readUInt32();
|
||||
data.overkill = static_cast<int32_t>(packet.readUInt32());
|
||||
|
||||
|
|
|
|||
|
|
@ -1234,6 +1234,8 @@ bool TbcPacketParsers::parseMailList(network::Packet& packet, std::vector<MailMe
|
|||
// Correct TBC format (cmangos-tbc): objectGuid(u64) + casterGuid(u64) + castCount(u8) + spellId(u32) + castFlags(u32) + castTime(u32)
|
||||
// ============================================================================
|
||||
bool TbcPacketParsers::parseSpellStart(network::Packet& packet, SpellStartData& data) {
|
||||
data = SpellStartData{};
|
||||
const size_t startPos = packet.getReadPos();
|
||||
if (packet.getSize() - packet.getReadPos() < 22) return false;
|
||||
|
||||
data.casterGuid = packet.readUInt64(); // full GUID (object)
|
||||
|
|
@ -1243,11 +1245,20 @@ bool TbcPacketParsers::parseSpellStart(network::Packet& packet, SpellStartData&
|
|||
data.castFlags = packet.readUInt32();
|
||||
data.castTime = packet.readUInt32();
|
||||
|
||||
if (packet.getReadPos() + 4 <= packet.getSize()) {
|
||||
uint32_t targetFlags = packet.readUInt32();
|
||||
if ((targetFlags & 0x02) && packet.getReadPos() + 8 <= packet.getSize()) {
|
||||
data.targetGuid = packet.readUInt64(); // full GUID in TBC
|
||||
if (packet.getReadPos() + 4 > packet.getSize()) {
|
||||
LOG_WARNING("[TBC] Spell start: missing targetFlags");
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t targetFlags = packet.readUInt32();
|
||||
const bool needsTargetGuid = (targetFlags & 0x02) || (targetFlags & 0x800); // UNIT/OBJECT
|
||||
if (needsTargetGuid) {
|
||||
if (packet.getReadPos() + 8 > packet.getSize()) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.targetGuid = packet.readUInt64(); // full GUID in TBC
|
||||
}
|
||||
|
||||
LOG_DEBUG("[TBC] Spell start: spell=", data.spellId, " castTime=", data.castTime, "ms");
|
||||
|
|
@ -1261,6 +1272,10 @@ bool TbcPacketParsers::parseSpellStart(network::Packet& packet, SpellStartData&
|
|||
// WotLK uses packed GUIDs and adds a timestamp (u32) after castFlags.
|
||||
// ============================================================================
|
||||
bool TbcPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& data) {
|
||||
// Always reset output to avoid stale targets when callers reuse buffers.
|
||||
data = SpellGoData{};
|
||||
|
||||
const size_t startPos = packet.getReadPos();
|
||||
// Fixed header before hit/miss lists:
|
||||
// casterGuid(u64) + casterUnit(u64) + castCount(u8) + spellId(u32) + castFlags(u32)
|
||||
if (packet.getSize() - packet.getReadPos() < 25) return false;
|
||||
|
|
@ -1273,55 +1288,77 @@ bool TbcPacketParsers::parseSpellGo(network::Packet& packet, SpellGoData& data)
|
|||
// NOTE: NO timestamp field here in TBC (WotLK added packet.readUInt32())
|
||||
|
||||
if (packet.getReadPos() >= packet.getSize()) {
|
||||
LOG_DEBUG("[TBC] Spell go: spell=", data.spellId, " (no hit data)");
|
||||
return true;
|
||||
LOG_WARNING("[TBC] Spell go: missing hitCount after fixed fields");
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
|
||||
data.hitCount = packet.readUInt8();
|
||||
// Cap hit count to prevent OOM from huge target lists
|
||||
if (data.hitCount > 128) {
|
||||
LOG_WARNING("[TBC] Spell go: hitCount capped (requested=", (int)data.hitCount, ")");
|
||||
data.hitCount = 128;
|
||||
const uint8_t rawHitCount = packet.readUInt8();
|
||||
if (rawHitCount > 128) {
|
||||
LOG_WARNING("[TBC] Spell go: hitCount capped (requested=", (int)rawHitCount, ")");
|
||||
}
|
||||
data.hitTargets.reserve(data.hitCount);
|
||||
for (uint8_t i = 0; i < data.hitCount && packet.getReadPos() + 8 <= packet.getSize(); ++i) {
|
||||
data.hitTargets.push_back(packet.readUInt64()); // full GUID in TBC
|
||||
}
|
||||
// Check if we read all expected hits
|
||||
if (data.hitTargets.size() < data.hitCount) {
|
||||
LOG_WARNING("[TBC] Spell go: truncated hit targets at index ", (int)data.hitTargets.size(),
|
||||
"/", (int)data.hitCount);
|
||||
data.hitCount = data.hitTargets.size();
|
||||
}
|
||||
|
||||
if (packet.getReadPos() < packet.getSize()) {
|
||||
data.missCount = packet.readUInt8();
|
||||
// Cap miss count to prevent OOM
|
||||
if (data.missCount > 128) {
|
||||
LOG_WARNING("[TBC] Spell go: missCount capped (requested=", (int)data.missCount, ")");
|
||||
data.missCount = 128;
|
||||
const uint8_t storedHitLimit = std::min<uint8_t>(rawHitCount, 128);
|
||||
data.hitTargets.reserve(storedHitLimit);
|
||||
bool truncatedTargets = false;
|
||||
for (uint16_t i = 0; i < rawHitCount; ++i) {
|
||||
if (packet.getReadPos() + 8 > packet.getSize()) {
|
||||
LOG_WARNING("[TBC] Spell go: truncated hit targets at index ", i,
|
||||
"/", (int)rawHitCount);
|
||||
truncatedTargets = true;
|
||||
break;
|
||||
}
|
||||
data.missTargets.reserve(data.missCount);
|
||||
for (uint8_t i = 0; i < data.missCount && packet.getReadPos() + 9 <= packet.getSize(); ++i) {
|
||||
SpellGoMissEntry m;
|
||||
m.targetGuid = packet.readUInt64(); // full GUID in TBC
|
||||
m.missType = packet.readUInt8();
|
||||
if (m.missType == 11) {
|
||||
if (packet.getReadPos() + 5 > packet.getSize()) {
|
||||
break;
|
||||
}
|
||||
(void)packet.readUInt32();
|
||||
(void)packet.readUInt8();
|
||||
const uint64_t targetGuid = packet.readUInt64(); // full GUID in TBC
|
||||
if (i < storedHitLimit) {
|
||||
data.hitTargets.push_back(targetGuid);
|
||||
}
|
||||
}
|
||||
if (truncatedTargets) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.hitCount = static_cast<uint8_t>(data.hitTargets.size());
|
||||
|
||||
if (packet.getReadPos() >= packet.getSize()) {
|
||||
LOG_WARNING("[TBC] Spell go: missing missCount after hit target list");
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t rawMissCount = packet.readUInt8();
|
||||
if (rawMissCount > 128) {
|
||||
LOG_WARNING("[TBC] Spell go: missCount capped (requested=", (int)rawMissCount, ")");
|
||||
}
|
||||
const uint8_t storedMissLimit = std::min<uint8_t>(rawMissCount, 128);
|
||||
data.missTargets.reserve(storedMissLimit);
|
||||
for (uint16_t i = 0; i < rawMissCount; ++i) {
|
||||
if (packet.getReadPos() + 9 > packet.getSize()) {
|
||||
LOG_WARNING("[TBC] Spell go: truncated miss targets at index ", i,
|
||||
"/", (int)rawMissCount);
|
||||
truncatedTargets = true;
|
||||
break;
|
||||
}
|
||||
SpellGoMissEntry m;
|
||||
m.targetGuid = packet.readUInt64(); // full GUID in TBC
|
||||
m.missType = packet.readUInt8();
|
||||
if (m.missType == 11) {
|
||||
if (packet.getReadPos() + 5 > packet.getSize()) {
|
||||
LOG_WARNING("[TBC] Spell go: truncated reflect payload at miss index ", i,
|
||||
"/", (int)rawMissCount);
|
||||
truncatedTargets = true;
|
||||
break;
|
||||
}
|
||||
(void)packet.readUInt32();
|
||||
(void)packet.readUInt8();
|
||||
}
|
||||
if (i < storedMissLimit) {
|
||||
data.missTargets.push_back(m);
|
||||
}
|
||||
// Check if we read all expected misses
|
||||
if (data.missTargets.size() < data.missCount) {
|
||||
LOG_WARNING("[TBC] Spell go: truncated miss targets at index ", (int)data.missTargets.size(),
|
||||
"/", (int)data.missCount);
|
||||
data.missCount = data.missTargets.size();
|
||||
}
|
||||
}
|
||||
if (truncatedTargets) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.missCount = static_cast<uint8_t>(data.missTargets.size());
|
||||
|
||||
LOG_DEBUG("[TBC] Spell go: spell=", data.spellId, " hits=", (int)data.hitCount,
|
||||
" misses=", (int)data.missCount);
|
||||
|
|
@ -1369,15 +1406,33 @@ bool TbcPacketParsers::parseCastFailed(network::Packet& packet, CastFailedData&
|
|||
// would mis-parse TBC's GUIDs and corrupt all subsequent damage fields.
|
||||
// ============================================================================
|
||||
bool TbcPacketParsers::parseAttackerStateUpdate(network::Packet& packet, AttackerStateUpdateData& data) {
|
||||
if (packet.getSize() - packet.getReadPos() < 21) return false;
|
||||
data = AttackerStateUpdateData{};
|
||||
|
||||
data.hitInfo = packet.readUInt32();
|
||||
data.attackerGuid = packet.readUInt64(); // full GUID in TBC
|
||||
data.targetGuid = packet.readUInt64(); // full GUID in TBC
|
||||
data.totalDamage = static_cast<int32_t>(packet.readUInt32());
|
||||
const size_t startPos = packet.getReadPos();
|
||||
auto rem = [&]() { return packet.getSize() - packet.getReadPos(); };
|
||||
|
||||
// Fixed fields before sub-damage list:
|
||||
// hitInfo(4) + attackerGuid(8) + targetGuid(8) + totalDamage(4) + subDamageCount(1) = 25 bytes
|
||||
if (rem() < 25) return false;
|
||||
|
||||
data.hitInfo = packet.readUInt32();
|
||||
data.attackerGuid = packet.readUInt64(); // full GUID in TBC
|
||||
data.targetGuid = packet.readUInt64(); // full GUID in TBC
|
||||
data.totalDamage = static_cast<int32_t>(packet.readUInt32());
|
||||
data.subDamageCount = packet.readUInt8();
|
||||
|
||||
// Clamp to what can fit in the remaining payload (20 bytes per sub-damage entry).
|
||||
const uint8_t maxSubDamageCount = static_cast<uint8_t>(std::min<size_t>(rem() / 20, 64));
|
||||
if (data.subDamageCount > maxSubDamageCount) {
|
||||
data.subDamageCount = maxSubDamageCount;
|
||||
}
|
||||
|
||||
data.subDamages.reserve(data.subDamageCount);
|
||||
for (uint8_t i = 0; i < data.subDamageCount; ++i) {
|
||||
if (rem() < 20) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
SubDamage sub;
|
||||
sub.schoolMask = packet.readUInt32();
|
||||
sub.damage = packet.readFloat();
|
||||
|
|
@ -1387,10 +1442,17 @@ bool TbcPacketParsers::parseAttackerStateUpdate(network::Packet& packet, Attacke
|
|||
data.subDamages.push_back(sub);
|
||||
}
|
||||
|
||||
data.subDamageCount = static_cast<uint8_t>(data.subDamages.size());
|
||||
|
||||
// victimState + overkill are part of the expected payload.
|
||||
if (rem() < 8) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.victimState = packet.readUInt32();
|
||||
data.overkill = static_cast<int32_t>(packet.readUInt32());
|
||||
|
||||
if (packet.getReadPos() < packet.getSize()) {
|
||||
if (rem() >= 4) {
|
||||
data.blocked = packet.readUInt32();
|
||||
}
|
||||
|
||||
|
|
@ -1406,20 +1468,28 @@ bool TbcPacketParsers::parseAttackerStateUpdate(network::Packet& packet, Attacke
|
|||
// TBC uses full uint64 GUIDs; WotLK uses packed GUIDs.
|
||||
// ============================================================================
|
||||
bool TbcPacketParsers::parseSpellDamageLog(network::Packet& packet, SpellDamageLogData& data) {
|
||||
if (packet.getSize() - packet.getReadPos() < 29) return false;
|
||||
// Fixed TBC payload size:
|
||||
// targetGuid(8) + attackerGuid(8) + spellId(4) + damage(4) + schoolMask(1)
|
||||
// + absorbed(4) + resisted(4) + periodicLog(1) + unused(1) + blocked(4) + flags(4)
|
||||
// = 43 bytes
|
||||
// Some servers append additional trailing fields; consume the canonical minimum
|
||||
// and leave any extension bytes unread.
|
||||
if (packet.getSize() - packet.getReadPos() < 43) return false;
|
||||
|
||||
data.targetGuid = packet.readUInt64(); // full GUID in TBC
|
||||
data = SpellDamageLogData{};
|
||||
|
||||
data.targetGuid = packet.readUInt64(); // full GUID in TBC
|
||||
data.attackerGuid = packet.readUInt64(); // full GUID in TBC
|
||||
data.spellId = packet.readUInt32();
|
||||
data.damage = packet.readUInt32();
|
||||
data.schoolMask = packet.readUInt8();
|
||||
data.absorbed = packet.readUInt32();
|
||||
data.resisted = packet.readUInt32();
|
||||
data.spellId = packet.readUInt32();
|
||||
data.damage = packet.readUInt32();
|
||||
data.schoolMask = packet.readUInt8();
|
||||
data.absorbed = packet.readUInt32();
|
||||
data.resisted = packet.readUInt32();
|
||||
|
||||
uint8_t periodicLog = packet.readUInt8();
|
||||
(void)periodicLog;
|
||||
packet.readUInt8(); // unused
|
||||
packet.readUInt32(); // blocked
|
||||
packet.readUInt8(); // unused
|
||||
packet.readUInt32(); // blocked
|
||||
uint32_t flags = packet.readUInt32();
|
||||
data.isCrit = (flags & 0x02) != 0;
|
||||
|
||||
|
|
@ -1437,13 +1507,17 @@ bool TbcPacketParsers::parseSpellDamageLog(network::Packet& packet, SpellDamageL
|
|||
// TBC uses full uint64 GUIDs; WotLK uses packed GUIDs.
|
||||
// ============================================================================
|
||||
bool TbcPacketParsers::parseSpellHealLog(network::Packet& packet, SpellHealLogData& data) {
|
||||
if (packet.getSize() - packet.getReadPos() < 25) return false;
|
||||
// Fixed payload is 28 bytes; many cores append crit flag (1 byte).
|
||||
// targetGuid(8) + casterGuid(8) + spellId(4) + heal(4) + overheal(4)
|
||||
if (packet.getSize() - packet.getReadPos() < 28) return false;
|
||||
|
||||
data.targetGuid = packet.readUInt64(); // full GUID in TBC
|
||||
data.casterGuid = packet.readUInt64(); // full GUID in TBC
|
||||
data.spellId = packet.readUInt32();
|
||||
data.heal = packet.readUInt32();
|
||||
data.overheal = packet.readUInt32();
|
||||
data = SpellHealLogData{};
|
||||
|
||||
data.targetGuid = packet.readUInt64(); // full GUID in TBC
|
||||
data.casterGuid = packet.readUInt64(); // full GUID in TBC
|
||||
data.spellId = packet.readUInt32();
|
||||
data.heal = packet.readUInt32();
|
||||
data.overheal = packet.readUInt32();
|
||||
// TBC has no absorbed field in SMSG_SPELLHEALLOG; skip crit flag
|
||||
if (packet.getReadPos() < packet.getSize()) {
|
||||
uint8_t critFlag = packet.readUInt8();
|
||||
|
|
|
|||
|
|
@ -19,6 +19,22 @@ namespace {
|
|||
inline uint16_t bswap16(uint16_t v) {
|
||||
return static_cast<uint16_t>(((v & 0xFF00u) >> 8) | ((v & 0x00FFu) << 8));
|
||||
}
|
||||
|
||||
bool hasFullPackedGuid(const wowee::network::Packet& packet) {
|
||||
if (packet.getReadPos() >= packet.getSize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& rawData = packet.getData();
|
||||
const uint8_t mask = rawData[packet.getReadPos()];
|
||||
size_t guidBytes = 1;
|
||||
for (int bit = 0; bit < 8; ++bit) {
|
||||
if ((mask & (1u << bit)) != 0) {
|
||||
++guidBytes;
|
||||
}
|
||||
}
|
||||
return packet.getSize() - packet.getReadPos() >= guidBytes;
|
||||
}
|
||||
}
|
||||
|
||||
namespace wowee {
|
||||
|
|
@ -3156,12 +3172,10 @@ bool MonsterMoveParser::parse(network::Packet& packet, MonsterMoveData& data) {
|
|||
|
||||
if (pointCount == 0) return true;
|
||||
|
||||
// Cap pointCount to prevent excessive iteration from malformed packets
|
||||
// Reject extreme point counts from malformed packets.
|
||||
constexpr uint32_t kMaxSplinePoints = 1000;
|
||||
if (pointCount > kMaxSplinePoints) {
|
||||
LOG_WARNING("SMSG_MONSTER_MOVE: pointCount=", pointCount, " exceeds max ", kMaxSplinePoints,
|
||||
" (guid=0x", std::hex, data.guid, std::dec, "), capping");
|
||||
pointCount = kMaxSplinePoints;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Catmullrom or Flying → all waypoints stored as absolute float3 (uncompressed).
|
||||
|
|
@ -3169,20 +3183,27 @@ bool MonsterMoveParser::parse(network::Packet& packet, MonsterMoveData& data) {
|
|||
bool uncompressed = (data.splineFlags & (0x00080000 | 0x00002000)) != 0;
|
||||
|
||||
if (uncompressed) {
|
||||
const size_t requiredBytes = static_cast<size_t>(pointCount) * 12ull;
|
||||
if (packet.getReadPos() + requiredBytes > packet.getSize()) return false;
|
||||
|
||||
// Read last point as destination
|
||||
// Skip to last point: each point is 12 bytes
|
||||
for (uint32_t i = 0; i < pointCount - 1; i++) {
|
||||
if (packet.getReadPos() + 12 > packet.getSize()) return true;
|
||||
if (packet.getReadPos() + 12 > packet.getSize()) return false;
|
||||
packet.readFloat(); packet.readFloat(); packet.readFloat();
|
||||
}
|
||||
if (packet.getReadPos() + 12 > packet.getSize()) return true;
|
||||
if (packet.getReadPos() + 12 > packet.getSize()) return false;
|
||||
data.destX = packet.readFloat();
|
||||
data.destY = packet.readFloat();
|
||||
data.destZ = packet.readFloat();
|
||||
data.hasDest = true;
|
||||
} else {
|
||||
// Compressed: first 3 floats are the destination (final point)
|
||||
if (packet.getReadPos() + 12 > packet.getSize()) return true;
|
||||
size_t requiredBytes = 12;
|
||||
if (pointCount > 1) {
|
||||
requiredBytes += static_cast<size_t>(pointCount - 1) * 4ull;
|
||||
}
|
||||
if (packet.getReadPos() + requiredBytes > packet.getSize()) return false;
|
||||
data.destX = packet.readFloat();
|
||||
data.destY = packet.readFloat();
|
||||
data.destZ = packet.readFloat();
|
||||
|
|
@ -3266,16 +3287,19 @@ bool MonsterMoveParser::parseVanilla(network::Packet& packet, MonsterMoveData& d
|
|||
|
||||
if (pointCount == 0) return true;
|
||||
|
||||
// Cap pointCount to prevent excessive iteration from malformed packets
|
||||
// Reject extreme point counts from malformed packets.
|
||||
constexpr uint32_t kMaxSplinePoints = 1000;
|
||||
if (pointCount > kMaxSplinePoints) {
|
||||
LOG_WARNING("SMSG_MONSTER_MOVE(Vanilla): pointCount=", pointCount, " exceeds max ", kMaxSplinePoints,
|
||||
" (guid=0x", std::hex, data.guid, std::dec, "), capping");
|
||||
pointCount = kMaxSplinePoints;
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t requiredBytes = 12;
|
||||
if (pointCount > 1) {
|
||||
requiredBytes += static_cast<size_t>(pointCount - 1) * 4ull;
|
||||
}
|
||||
if (packet.getReadPos() + requiredBytes > packet.getSize()) return false;
|
||||
|
||||
// First float[3] is destination.
|
||||
if (packet.getReadPos() + 12 > packet.getSize()) return true;
|
||||
data.destX = packet.readFloat();
|
||||
data.destY = packet.readFloat();
|
||||
data.destZ = packet.readFloat();
|
||||
|
|
@ -3285,9 +3309,8 @@ bool MonsterMoveParser::parseVanilla(network::Packet& packet, MonsterMoveData& d
|
|||
if (pointCount > 1) {
|
||||
size_t skipBytes = static_cast<size_t>(pointCount - 1) * 4;
|
||||
size_t newPos = packet.getReadPos() + skipBytes;
|
||||
if (newPos <= packet.getSize()) {
|
||||
packet.setReadPos(newPos);
|
||||
}
|
||||
if (newPos > packet.getSize()) return false;
|
||||
packet.setReadPos(newPos);
|
||||
}
|
||||
|
||||
LOG_DEBUG("MonsterMove(turtle): guid=0x", std::hex, data.guid, std::dec,
|
||||
|
|
@ -3327,7 +3350,15 @@ bool AttackerStateUpdateParser::parse(network::Packet& packet, AttackerStateUpda
|
|||
|
||||
size_t startPos = packet.getReadPos();
|
||||
data.hitInfo = packet.readUInt32();
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.attackerGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.targetGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
|
||||
// Validate totalDamage + subDamageCount can be read (5 bytes)
|
||||
|
|
@ -3339,15 +3370,15 @@ bool AttackerStateUpdateParser::parse(network::Packet& packet, AttackerStateUpda
|
|||
data.totalDamage = static_cast<int32_t>(packet.readUInt32());
|
||||
data.subDamageCount = packet.readUInt8();
|
||||
|
||||
// Cap subDamageCount: each entry is 20 bytes. If the claimed count
|
||||
// Cap subDamageCount: each entry is 20 bytes. If the claimed count
|
||||
// exceeds what the remaining bytes can hold, a GUID was mis-parsed
|
||||
// (off by one byte), causing the school-mask byte to be read as count.
|
||||
// In that case silently clamp to the number of full entries that fit.
|
||||
// In that case clamp to the number of full entries that fit.
|
||||
{
|
||||
size_t remaining = packet.getSize() - packet.getReadPos();
|
||||
size_t maxFit = remaining / 20;
|
||||
if (data.subDamageCount > maxFit) {
|
||||
data.subDamageCount = static_cast<uint8_t>(maxFit > 0 ? 1 : 0);
|
||||
data.subDamageCount = static_cast<uint8_t>(std::min<size_t>(maxFit, 64));
|
||||
} else if (data.subDamageCount > 64) {
|
||||
data.subDamageCount = 64;
|
||||
}
|
||||
|
|
@ -3399,11 +3430,22 @@ bool AttackerStateUpdateParser::parse(network::Packet& packet, AttackerStateUpda
|
|||
}
|
||||
|
||||
bool SpellDamageLogParser::parse(network::Packet& packet, SpellDamageLogData& data) {
|
||||
// Upfront validation: packed GUIDs(1-8 each) + spellId(4) + damage(4) + overkill(4) + schoolMask(1) + absorbed(4) + resisted(4) = 30 bytes minimum
|
||||
if (packet.getSize() - packet.getReadPos() < 30) return false;
|
||||
// Upfront validation:
|
||||
// packed GUIDs(1-8 each) + spellId(4) + damage(4) + overkill(4) + schoolMask(1)
|
||||
// + absorbed(4) + resisted(4) + periodicLog(1) + unused(1) + blocked(4) + flags(4)
|
||||
// = 33 bytes minimum.
|
||||
if (packet.getSize() - packet.getReadPos() < 33) return false;
|
||||
|
||||
size_t startPos = packet.getReadPos();
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.targetGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.attackerGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
|
||||
// Validate core fields (spellId + damage + overkill + schoolMask + absorbed + resisted = 21 bytes)
|
||||
|
|
@ -3419,11 +3461,11 @@ bool SpellDamageLogParser::parse(network::Packet& packet, SpellDamageLogData& da
|
|||
data.absorbed = packet.readUInt32();
|
||||
data.resisted = packet.readUInt32();
|
||||
|
||||
// Skip remaining fields (periodicLog + unused + blocked + flags = 10 bytes)
|
||||
// Remaining fields are required for a complete event.
|
||||
// Reject truncated packets so we do not emit partial/incorrect combat entries.
|
||||
if (packet.getSize() - packet.getReadPos() < 10) {
|
||||
LOG_WARNING("SpellDamageLog: truncated trailing fields");
|
||||
data.isCrit = false;
|
||||
return true;
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t periodicLog = packet.readUInt8();
|
||||
|
|
@ -3445,7 +3487,15 @@ bool SpellHealLogParser::parse(network::Packet& packet, SpellHealLogData& data)
|
|||
if (packet.getSize() - packet.getReadPos() < 21) return false;
|
||||
|
||||
size_t startPos = packet.getReadPos();
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.targetGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.casterGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
|
||||
// Validate remaining fields (spellId + heal + overheal + absorbed + critFlag = 17 bytes)
|
||||
|
|
@ -3663,15 +3713,25 @@ bool CastFailedParser::parse(network::Packet& packet, CastFailedData& data) {
|
|||
}
|
||||
|
||||
bool SpellStartParser::parse(network::Packet& packet, SpellStartData& data) {
|
||||
// Upfront validation: packed GUID(1-8) + packed GUID(1-8) + castCount(1) + spellId(4) + castFlags(4) + castTime(4) = 22 bytes minimum
|
||||
if (packet.getSize() - packet.getReadPos() < 22) return false;
|
||||
data = SpellStartData{};
|
||||
|
||||
// Packed GUIDs are variable-length; only require minimal packet shape up front:
|
||||
// two GUID masks + castCount(1) + spellId(4) + castFlags(4) + castTime(4).
|
||||
if (packet.getSize() - packet.getReadPos() < 15) return false;
|
||||
|
||||
size_t startPos = packet.getReadPos();
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
return false;
|
||||
}
|
||||
data.casterGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.casterUnit = UpdateObjectParser::readPackedGuid(packet);
|
||||
|
||||
// Validate remaining fixed fields (castCount + spellId + castFlags + castTime = 9 bytes)
|
||||
if (packet.getSize() - packet.getReadPos() < 9) {
|
||||
// Validate remaining fixed fields (castCount + spellId + castFlags + castTime = 13 bytes)
|
||||
if (packet.getSize() - packet.getReadPos() < 13) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -3681,12 +3741,21 @@ bool SpellStartParser::parse(network::Packet& packet, SpellStartData& data) {
|
|||
data.castFlags = packet.readUInt32();
|
||||
data.castTime = packet.readUInt32();
|
||||
|
||||
// Read target flags and target (simplified)
|
||||
if (packet.getSize() - packet.getReadPos() >= 4) {
|
||||
uint32_t targetFlags = packet.readUInt32();
|
||||
if ((targetFlags & 0x02) && packet.getSize() - packet.getReadPos() >= 1) { // TARGET_FLAG_UNIT, validate packed GUID read
|
||||
data.targetGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
// SpellCastTargets starts with target flags and is mandatory.
|
||||
if (packet.getSize() - packet.getReadPos() < 4) {
|
||||
LOG_WARNING("Spell start: missing targetFlags");
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t targetFlags = packet.readUInt32();
|
||||
const bool needsTargetGuid = (targetFlags & 0x02) || (targetFlags & 0x800); // UNIT/OBJECT
|
||||
if (needsTargetGuid) {
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.targetGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
}
|
||||
|
||||
LOG_DEBUG("Spell start: spell=", data.spellId, " castTime=", data.castTime, "ms");
|
||||
|
|
@ -3694,12 +3763,22 @@ bool SpellStartParser::parse(network::Packet& packet, SpellStartData& data) {
|
|||
}
|
||||
|
||||
bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) {
|
||||
// Always reset output to avoid stale targets when callers reuse buffers.
|
||||
data = SpellGoData{};
|
||||
|
||||
// Packed GUIDs are variable-length, so only require the smallest possible
|
||||
// shape up front: 2 GUID masks + fixed fields through missCount.
|
||||
if (packet.getSize() - packet.getReadPos() < 17) return false;
|
||||
// shape up front: 2 GUID masks + fixed fields through hitCount.
|
||||
if (packet.getSize() - packet.getReadPos() < 16) return false;
|
||||
|
||||
size_t startPos = packet.getReadPos();
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
return false;
|
||||
}
|
||||
data.casterGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.casterUnit = UpdateObjectParser::readPackedGuid(packet);
|
||||
|
||||
// Validate remaining fixed fields up to hitCount/missCount
|
||||
|
|
@ -3714,59 +3793,81 @@ bool SpellGoParser::parse(network::Packet& packet, SpellGoData& data) {
|
|||
// Timestamp in 3.3.5a
|
||||
packet.readUInt32();
|
||||
|
||||
data.hitCount = packet.readUInt8();
|
||||
// Cap hit count to prevent DoS via massive arrays
|
||||
if (data.hitCount > 128) {
|
||||
LOG_WARNING("Spell go: hitCount capped (requested=", (int)data.hitCount, ")");
|
||||
data.hitCount = 128;
|
||||
const uint8_t rawHitCount = packet.readUInt8();
|
||||
if (rawHitCount > 128) {
|
||||
LOG_WARNING("Spell go: hitCount capped (requested=", (int)rawHitCount, ")");
|
||||
}
|
||||
const uint8_t storedHitLimit = std::min<uint8_t>(rawHitCount, 128);
|
||||
|
||||
data.hitTargets.reserve(data.hitCount);
|
||||
for (uint8_t i = 0; i < data.hitCount; ++i) {
|
||||
bool truncatedTargets = false;
|
||||
|
||||
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 (packet.getSize() - packet.getReadPos() < 1) {
|
||||
LOG_WARNING("Spell go: truncated hit targets at index ", (int)i, "/", (int)data.hitCount);
|
||||
data.hitCount = i;
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
LOG_WARNING("Spell go: truncated hit targets at index ", i, "/", (int)rawHitCount);
|
||||
truncatedTargets = true;
|
||||
break;
|
||||
}
|
||||
data.hitTargets.push_back(UpdateObjectParser::readPackedGuid(packet));
|
||||
const uint64_t targetGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (i < storedHitLimit) {
|
||||
data.hitTargets.push_back(targetGuid);
|
||||
}
|
||||
}
|
||||
if (truncatedTargets) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.hitCount = static_cast<uint8_t>(data.hitTargets.size());
|
||||
|
||||
// Validate missCount field exists
|
||||
// missCount is mandatory in SMSG_SPELL_GO. Missing byte means truncation.
|
||||
if (packet.getSize() - packet.getReadPos() < 1) {
|
||||
return true; // Valid, just no misses
|
||||
LOG_WARNING("Spell go: missing missCount after hit target list");
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
|
||||
data.missCount = packet.readUInt8();
|
||||
// Cap miss count to prevent DoS
|
||||
if (data.missCount > 128) {
|
||||
LOG_WARNING("Spell go: missCount capped (requested=", (int)data.missCount, ")");
|
||||
data.missCount = 128;
|
||||
const uint8_t rawMissCount = packet.readUInt8();
|
||||
if (rawMissCount > 128) {
|
||||
LOG_WARNING("Spell go: missCount capped (requested=", (int)rawMissCount, ")");
|
||||
}
|
||||
const uint8_t storedMissLimit = std::min<uint8_t>(rawMissCount, 128);
|
||||
|
||||
data.missTargets.reserve(data.missCount);
|
||||
for (uint8_t i = 0; i < data.missCount; ++i) {
|
||||
data.missTargets.reserve(storedMissLimit);
|
||||
for (uint16_t i = 0; i < rawMissCount; ++i) {
|
||||
// Each miss entry: packed GUID(1-8 bytes) + missType(1 byte).
|
||||
// REFLECT additionally appends uint32 reflectSpellId + uint8 reflectResult.
|
||||
if (packet.getSize() - packet.getReadPos() < 2) {
|
||||
LOG_WARNING("Spell go: truncated miss targets at index ", (int)i, "/", (int)data.missCount);
|
||||
data.missCount = i;
|
||||
if (!hasFullPackedGuid(packet)) {
|
||||
LOG_WARNING("Spell go: truncated miss targets at index ", i, "/", (int)rawMissCount);
|
||||
truncatedTargets = true;
|
||||
break;
|
||||
}
|
||||
SpellGoMissEntry m;
|
||||
m.targetGuid = UpdateObjectParser::readPackedGuid(packet); // packed GUID in WotLK
|
||||
m.missType = (packet.getSize() - packet.getReadPos() >= 1) ? packet.readUInt8() : 0;
|
||||
if (packet.getSize() - packet.getReadPos() < 1) {
|
||||
LOG_WARNING("Spell go: missing missType at miss index ", i, "/", (int)rawMissCount);
|
||||
truncatedTargets = true;
|
||||
break;
|
||||
}
|
||||
m.missType = packet.readUInt8();
|
||||
if (m.missType == 11) {
|
||||
if (packet.getSize() - packet.getReadPos() < 5) {
|
||||
LOG_WARNING("Spell go: truncated reflect payload at miss index ", (int)i, "/", (int)data.missCount);
|
||||
data.missCount = i;
|
||||
LOG_WARNING("Spell go: truncated reflect payload at miss index ", i, "/", (int)rawMissCount);
|
||||
truncatedTargets = true;
|
||||
break;
|
||||
}
|
||||
(void)packet.readUInt32();
|
||||
(void)packet.readUInt8();
|
||||
}
|
||||
data.missTargets.push_back(m);
|
||||
if (i < storedMissLimit) {
|
||||
data.missTargets.push_back(m);
|
||||
}
|
||||
}
|
||||
if (truncatedTargets) {
|
||||
packet.setReadPos(startPos);
|
||||
return false;
|
||||
}
|
||||
data.missCount = static_cast<uint8_t>(data.missTargets.size());
|
||||
|
||||
LOG_DEBUG("Spell go: spell=", data.spellId, " hits=", (int)data.hitCount,
|
||||
" misses=", (int)data.missCount);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue