mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
fix state gate races and robust spline
Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
This commit is contained in:
parent
6ba0edc2fb
commit
535cc20afe
2 changed files with 88 additions and 24 deletions
|
|
@ -73,17 +73,24 @@ EntityController::EntityController(GameHandler& owner)
|
||||||
: owner_(owner) { initTypeHandlers(); }
|
: owner_(owner) { initTypeHandlers(); }
|
||||||
|
|
||||||
void EntityController::registerOpcodes(DispatchTable& table) {
|
void EntityController::registerOpcodes(DispatchTable& table) {
|
||||||
// World object updates
|
// World object updates — accept during ENTERING_WORLD too so that entity
|
||||||
table[Opcode::SMSG_UPDATE_OBJECT] = [this](network::Packet& packet) {
|
// packets arriving before SMSG_LOGIN_VERIFY_WORLD are parsed and queued
|
||||||
|
// rather than silently dropped (the budget system processes them later once
|
||||||
|
// the state transitions to IN_WORLD).
|
||||||
|
auto inWorldOrEntering = [this]() {
|
||||||
|
auto s = owner_.getState();
|
||||||
|
return s == WorldState::IN_WORLD || s == WorldState::ENTERING_WORLD;
|
||||||
|
};
|
||||||
|
table[Opcode::SMSG_UPDATE_OBJECT] = [this, inWorldOrEntering](network::Packet& packet) {
|
||||||
LOG_DEBUG("Received SMSG_UPDATE_OBJECT, state=", static_cast<int>(owner_.getState()), " size=", packet.getSize());
|
LOG_DEBUG("Received SMSG_UPDATE_OBJECT, state=", static_cast<int>(owner_.getState()), " size=", packet.getSize());
|
||||||
if (owner_.getState() == WorldState::IN_WORLD) handleUpdateObject(packet);
|
if (inWorldOrEntering()) handleUpdateObject(packet);
|
||||||
};
|
};
|
||||||
table[Opcode::SMSG_COMPRESSED_UPDATE_OBJECT] = [this](network::Packet& packet) {
|
table[Opcode::SMSG_COMPRESSED_UPDATE_OBJECT] = [this, inWorldOrEntering](network::Packet& packet) {
|
||||||
LOG_DEBUG("Received SMSG_COMPRESSED_UPDATE_OBJECT, state=", static_cast<int>(owner_.getState()), " size=", packet.getSize());
|
LOG_DEBUG("Received SMSG_COMPRESSED_UPDATE_OBJECT, state=", static_cast<int>(owner_.getState()), " size=", packet.getSize());
|
||||||
if (owner_.getState() == WorldState::IN_WORLD) handleCompressedUpdateObject(packet);
|
if (inWorldOrEntering()) handleCompressedUpdateObject(packet);
|
||||||
};
|
};
|
||||||
table[Opcode::SMSG_DESTROY_OBJECT] = [this](network::Packet& packet) {
|
table[Opcode::SMSG_DESTROY_OBJECT] = [this, inWorldOrEntering](network::Packet& packet) {
|
||||||
if (owner_.getState() == WorldState::IN_WORLD) handleDestroyObject(packet);
|
if (inWorldOrEntering()) handleDestroyObject(packet);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Entity queries
|
// Entity queries
|
||||||
|
|
|
||||||
|
|
@ -1029,25 +1029,37 @@ bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock
|
||||||
// Save position before WotLK spline header for fallback
|
// Save position before WotLK spline header for fallback
|
||||||
size_t beforeSplineHeader = packet.getReadPos();
|
size_t beforeSplineHeader = packet.getReadPos();
|
||||||
|
|
||||||
|
// AzerothCore MoveSplineFlag constants:
|
||||||
|
// CATMULLROM = 0x00080000 — uncompressed Catmull-Rom interpolation
|
||||||
|
// CYCLIC = 0x00100000 — cyclic path
|
||||||
|
// ENTER_CYCLE = 0x00200000 — entering cyclic path
|
||||||
|
// ANIMATION = 0x00400000 — animation spline with animType+effectStart
|
||||||
|
// PARABOLIC = 0x00000008 — vertical_acceleration+effectStartTime
|
||||||
|
constexpr uint32_t SF_PARABOLIC = 0x00000008;
|
||||||
|
constexpr uint32_t SF_CATMULLROM = 0x00080000;
|
||||||
|
constexpr uint32_t SF_CYCLIC = 0x00100000;
|
||||||
|
constexpr uint32_t SF_ENTER_CYCLE = 0x00200000;
|
||||||
|
constexpr uint32_t SF_ANIMATION = 0x00400000;
|
||||||
|
constexpr uint32_t SF_UNCOMPRESSED_MASK = SF_CATMULLROM | SF_CYCLIC | SF_ENTER_CYCLE;
|
||||||
|
|
||||||
// Try 1: WotLK format (durationMod+durationModNext+[ANIMATION]+vertAccel+effectStart+points)
|
// Try 1: WotLK format (durationMod+durationModNext+[ANIMATION]+vertAccel+effectStart+points)
|
||||||
|
// Some servers (ChromieCraft) always write vertAccel+effectStart unconditionally.
|
||||||
bool splineParsed = false;
|
bool splineParsed = false;
|
||||||
if (bytesAvailable(8)) {
|
if (bytesAvailable(8)) {
|
||||||
/*float durationMod =*/ packet.readFloat();
|
/*float durationMod =*/ packet.readFloat();
|
||||||
/*float durationModNext =*/ packet.readFloat();
|
/*float durationModNext =*/ packet.readFloat();
|
||||||
bool wotlkOk = true;
|
bool wotlkOk = true;
|
||||||
if (splineFlags & 0x00400000) { // SPLINEFLAG_ANIMATION
|
if (splineFlags & SF_ANIMATION) {
|
||||||
if (!bytesAvailable(5)) { wotlkOk = false; }
|
if (!bytesAvailable(5)) { wotlkOk = false; }
|
||||||
else { packet.readUInt8(); packet.readUInt32(); }
|
else { packet.readUInt8(); packet.readUInt32(); }
|
||||||
}
|
}
|
||||||
// AzerothCore/ChromieCraft always writes verticalAcceleration(float)
|
// Unconditional vertAccel+effectStart (ChromieCraft/some AzerothCore builds)
|
||||||
// + effectStartTime(uint32) unconditionally -- NOT gated by PARABOLIC flag.
|
|
||||||
if (wotlkOk) {
|
if (wotlkOk) {
|
||||||
if (!bytesAvailable(8)) { wotlkOk = false; }
|
if (!bytesAvailable(8)) { wotlkOk = false; }
|
||||||
else { /*float vertAccel =*/ packet.readFloat(); /*uint32_t effectStart =*/ packet.readUInt32(); }
|
else { /*float vertAccel =*/ packet.readFloat(); /*uint32_t effectStart =*/ packet.readUInt32(); }
|
||||||
}
|
}
|
||||||
if (wotlkOk) {
|
if (wotlkOk) {
|
||||||
// WotLK: compressed unless CYCLIC(0x80000) or ENTER_CYCLE(0x2000) set
|
bool useCompressed = (splineFlags & SF_UNCOMPRESSED_MASK) == 0;
|
||||||
bool useCompressed = (splineFlags & (0x00080000 | 0x00002000)) == 0;
|
|
||||||
splineParsed = tryParseSplinePoints(useCompressed, "wotlk-compressed");
|
splineParsed = tryParseSplinePoints(useCompressed, "wotlk-compressed");
|
||||||
if (!splineParsed) {
|
if (!splineParsed) {
|
||||||
splineParsed = tryParseSplinePoints(false, "wotlk-uncompressed");
|
splineParsed = tryParseSplinePoints(false, "wotlk-uncompressed");
|
||||||
|
|
@ -1055,29 +1067,72 @@ bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!splineParsed) {
|
// Try 2: ANIMATION present but vertAccel+effectStart gated by PARABOLIC
|
||||||
// WotLK compressed+uncompressed both failed. Try without the parabolic
|
// (standard AzerothCore: only writes vertAccel+effectStart when PARABOLIC is set)
|
||||||
// fields (some cores don't send vertAccel+effectStart unconditionally).
|
if (!splineParsed && (splineFlags & SF_ANIMATION)) {
|
||||||
|
packet.setReadPos(beforeSplineHeader);
|
||||||
|
if (bytesAvailable(8)) {
|
||||||
|
packet.readFloat(); // durationMod
|
||||||
|
packet.readFloat(); // durationModNext
|
||||||
|
bool ok = true;
|
||||||
|
if (!bytesAvailable(5)) { ok = false; }
|
||||||
|
else { packet.readUInt8(); packet.readUInt32(); } // animType + effectStart
|
||||||
|
if (ok && (splineFlags & SF_PARABOLIC)) {
|
||||||
|
if (!bytesAvailable(8)) { ok = false; }
|
||||||
|
else { packet.readFloat(); packet.readUInt32(); }
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
bool useCompressed = (splineFlags & SF_UNCOMPRESSED_MASK) == 0;
|
||||||
|
splineParsed = tryParseSplinePoints(useCompressed, "wotlk-anim-conditional");
|
||||||
|
if (!splineParsed) {
|
||||||
|
splineParsed = tryParseSplinePoints(false, "wotlk-anim-conditional-uncomp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try 3: No ANIMATION — vertAccel+effectStart only when PARABOLIC set
|
||||||
|
if (!splineParsed) {
|
||||||
|
packet.setReadPos(beforeSplineHeader);
|
||||||
|
if (bytesAvailable(8)) {
|
||||||
|
packet.readFloat(); // durationMod
|
||||||
|
packet.readFloat(); // durationModNext
|
||||||
|
bool ok = true;
|
||||||
|
if (splineFlags & SF_PARABOLIC) {
|
||||||
|
if (!bytesAvailable(8)) { ok = false; }
|
||||||
|
else { packet.readFloat(); packet.readUInt32(); }
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
bool useCompressed = (splineFlags & SF_UNCOMPRESSED_MASK) == 0;
|
||||||
|
splineParsed = tryParseSplinePoints(useCompressed, "wotlk-parabolic-gated");
|
||||||
|
if (!splineParsed) {
|
||||||
|
splineParsed = tryParseSplinePoints(false, "wotlk-parabolic-gated-uncomp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try 4: No header at all — just durationMod+durationModNext then points
|
||||||
|
if (!splineParsed) {
|
||||||
packet.setReadPos(beforeSplineHeader);
|
packet.setReadPos(beforeSplineHeader);
|
||||||
if (bytesAvailable(8)) {
|
if (bytesAvailable(8)) {
|
||||||
packet.readFloat(); // durationMod
|
packet.readFloat(); // durationMod
|
||||||
packet.readFloat(); // durationModNext
|
packet.readFloat(); // durationModNext
|
||||||
// Skip parabolic fields — try points directly
|
|
||||||
splineParsed = tryParseSplinePoints(false, "wotlk-no-parabolic");
|
splineParsed = tryParseSplinePoints(false, "wotlk-no-parabolic");
|
||||||
if (!splineParsed) {
|
if (!splineParsed) {
|
||||||
bool useComp = (splineFlags & (0x00080000 | 0x00002000)) == 0;
|
bool useComp = (splineFlags & SF_UNCOMPRESSED_MASK) == 0;
|
||||||
splineParsed = tryParseSplinePoints(useComp, "wotlk-no-parabolic-compressed");
|
splineParsed = tryParseSplinePoints(useComp, "wotlk-no-parabolic-compressed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try 3: bare points (no WotLK header at all — some spline types skip everything)
|
// Try 5: bare points (no WotLK header at all — some spline types skip everything)
|
||||||
if (!splineParsed) {
|
if (!splineParsed) {
|
||||||
packet.setReadPos(beforeSplineHeader);
|
packet.setReadPos(beforeSplineHeader);
|
||||||
splineParsed = tryParseSplinePoints(false, "bare-uncompressed");
|
splineParsed = tryParseSplinePoints(false, "bare-uncompressed");
|
||||||
if (!splineParsed) {
|
if (!splineParsed) {
|
||||||
packet.setReadPos(beforeSplineHeader);
|
packet.setReadPos(beforeSplineHeader);
|
||||||
bool useComp = (splineFlags & (0x00080000 | 0x00002000)) == 0;
|
bool useComp = (splineFlags & SF_UNCOMPRESSED_MASK) == 0;
|
||||||
splineParsed = tryParseSplinePoints(useComp, "bare-compressed");
|
splineParsed = tryParseSplinePoints(useComp, "bare-compressed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1090,8 +1145,8 @@ bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock
|
||||||
d[di] = packet.readUInt32();
|
d[di] = packet.readUInt32();
|
||||||
packet.setReadPos(beforeSplineHeader);
|
packet.setReadPos(beforeSplineHeader);
|
||||||
LOG_WARNING("WotLK spline parse failed for guid=0x", std::hex, block.guid, std::dec,
|
LOG_WARNING("WotLK spline parse failed for guid=0x", std::hex, block.guid, std::dec,
|
||||||
" splineFlags=0x", splineFlags,
|
" splineFlags=0x", std::hex, splineFlags, std::dec,
|
||||||
" remaining=", std::dec, packet.getRemainingSize(),
|
" remaining=", packet.getRemainingSize(),
|
||||||
" header=[0x", std::hex, d[0], " 0x", d[1], " 0x", d[2],
|
" header=[0x", std::hex, d[0], " 0x", d[1], " 0x", d[2],
|
||||||
" 0x", d[3], " 0x", d[4], "]", std::dec);
|
" 0x", d[3], " 0x", d[4], "]", std::dec);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1424,10 +1479,12 @@ bool UpdateObjectParser::parse(network::Packet& packet, UpdateObjectData& data)
|
||||||
UpdateBlock block;
|
UpdateBlock block;
|
||||||
if (!parseUpdateBlock(packet, block)) {
|
if (!parseUpdateBlock(packet, block)) {
|
||||||
static int parseBlockErrors = 0;
|
static int parseBlockErrors = 0;
|
||||||
if (++parseBlockErrors <= 5) {
|
const uint32_t lostBlocks = data.blockCount - i;
|
||||||
|
if (++parseBlockErrors <= 10) {
|
||||||
LOG_ERROR("Failed to parse update block ", i + 1, " of ", data.blockCount,
|
LOG_ERROR("Failed to parse update block ", i + 1, " of ", data.blockCount,
|
||||||
" (", i, " blocks parsed successfully before failure)");
|
" (", i, " blocks parsed, ", lostBlocks, " blocks LOST",
|
||||||
if (parseBlockErrors == 5)
|
", remaining=", packet.getRemainingSize(), " bytes)");
|
||||||
|
if (parseBlockErrors == 10)
|
||||||
LOG_ERROR("(suppressing further update block parse errors)");
|
LOG_ERROR("(suppressing further update block parse errors)");
|
||||||
}
|
}
|
||||||
// Cannot reliably re-sync to the next block after a parse failure,
|
// Cannot reliably re-sync to the next block after a parse failure,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue