mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-03 00:03:50 +00:00
Fix online mode creature spawning and packet parsing
- Fix encryption desync by tracking decrypted header bytes in world socket - Fix UPDATE_OBJECT movement block parsing to handle 3.3.5a update flags - Fix UNIT_FIELD_DISPLAYID index (67, not 71) - Add creature spawn/despawn callbacks with DBC-based model loading - Add SMSG_COMPRESSED_UPDATE_OBJECT opcode support
This commit is contained in:
parent
300edd2c7c
commit
9b62acd72d
9 changed files with 445 additions and 30 deletions
|
|
@ -6,6 +6,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace wowee {
|
namespace wowee {
|
||||||
|
|
||||||
|
|
@ -81,6 +82,10 @@ private:
|
||||||
std::string getPlayerModelPath() const;
|
std::string getPlayerModelPath() const;
|
||||||
static const char* mapIdToName(uint32_t mapId);
|
static const char* mapIdToName(uint32_t mapId);
|
||||||
void loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float z);
|
void loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float z);
|
||||||
|
void spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation);
|
||||||
|
void despawnOnlineCreature(uint64_t guid);
|
||||||
|
void buildCreatureDisplayLookups();
|
||||||
|
std::string getModelPathForDisplayId(uint32_t displayId) const;
|
||||||
|
|
||||||
static Application* instance;
|
static Application* instance;
|
||||||
|
|
||||||
|
|
@ -118,6 +123,14 @@ private:
|
||||||
std::vector<std::string> underwearPaths_;
|
std::vector<std::string> underwearPaths_;
|
||||||
uint32_t skinTextureSlotIndex_ = 0;
|
uint32_t skinTextureSlotIndex_ = 0;
|
||||||
uint32_t cloakTextureSlotIndex_ = 0;
|
uint32_t cloakTextureSlotIndex_ = 0;
|
||||||
|
|
||||||
|
// Online creature model spawning
|
||||||
|
std::unordered_map<uint32_t, uint32_t> displayToModelId_; // displayId → modelId (from CreatureDisplayInfo.dbc)
|
||||||
|
std::unordered_map<uint32_t, std::string> modelIdToPath_; // modelId → M2 path (from CreatureModelData.dbc)
|
||||||
|
std::unordered_map<uint64_t, uint32_t> creatureInstances_; // guid → render instanceId
|
||||||
|
std::unordered_map<uint64_t, uint32_t> creatureModelIds_; // guid → loaded modelId
|
||||||
|
uint32_t nextCreatureModelId_ = 5000; // Model IDs for online creatures
|
||||||
|
bool creatureLookupsBuilt_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace core
|
} // namespace core
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,10 @@ public:
|
||||||
uint32_t getEntry() const { return entry; }
|
uint32_t getEntry() const { return entry; }
|
||||||
void setEntry(uint32_t e) { entry = e; }
|
void setEntry(uint32_t e) { entry = e; }
|
||||||
|
|
||||||
|
// Display ID (model display)
|
||||||
|
uint32_t getDisplayId() const { return displayId; }
|
||||||
|
void setDisplayId(uint32_t id) { displayId = id; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string name;
|
std::string name;
|
||||||
uint32_t health = 0;
|
uint32_t health = 0;
|
||||||
|
|
@ -156,6 +160,7 @@ protected:
|
||||||
uint8_t powerType = 0; // 0=mana, 1=rage, 2=focus, 3=energy
|
uint8_t powerType = 0; // 0=mana, 1=rage, 2=focus, 3=energy
|
||||||
uint32_t level = 1;
|
uint32_t level = 1;
|
||||||
uint32_t entry = 0;
|
uint32_t entry = 0;
|
||||||
|
uint32_t displayId = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -276,6 +276,15 @@ public:
|
||||||
using WorldEntryCallback = std::function<void(uint32_t mapId, float x, float y, float z)>;
|
using WorldEntryCallback = std::function<void(uint32_t mapId, float x, float y, float z)>;
|
||||||
void setWorldEntryCallback(WorldEntryCallback cb) { worldEntryCallback_ = std::move(cb); }
|
void setWorldEntryCallback(WorldEntryCallback cb) { worldEntryCallback_ = std::move(cb); }
|
||||||
|
|
||||||
|
// Creature spawn callback (online mode - triggered when creature enters view)
|
||||||
|
// Parameters: guid, displayId, x, y, z (canonical), orientation
|
||||||
|
using CreatureSpawnCallback = std::function<void(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation)>;
|
||||||
|
void setCreatureSpawnCallback(CreatureSpawnCallback cb) { creatureSpawnCallback_ = std::move(cb); }
|
||||||
|
|
||||||
|
// Creature despawn callback (online mode - triggered when creature leaves view)
|
||||||
|
using CreatureDespawnCallback = std::function<void(uint64_t guid)>;
|
||||||
|
void setCreatureDespawnCallback(CreatureDespawnCallback cb) { creatureDespawnCallback_ = std::move(cb); }
|
||||||
|
|
||||||
// Cooldowns
|
// Cooldowns
|
||||||
float getSpellCooldown(uint32_t spellId) const;
|
float getSpellCooldown(uint32_t spellId) const;
|
||||||
|
|
||||||
|
|
@ -383,6 +392,11 @@ private:
|
||||||
*/
|
*/
|
||||||
void handleUpdateObject(network::Packet& packet);
|
void handleUpdateObject(network::Packet& packet);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle SMSG_COMPRESSED_UPDATE_OBJECT from server
|
||||||
|
*/
|
||||||
|
void handleCompressedUpdateObject(network::Packet& packet);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle SMSG_DESTROY_OBJECT from server
|
* Handle SMSG_DESTROY_OBJECT from server
|
||||||
*/
|
*/
|
||||||
|
|
@ -529,6 +543,8 @@ private:
|
||||||
// ---- Phase 3: Spells ----
|
// ---- Phase 3: Spells ----
|
||||||
HearthstoneCallback hearthstoneCallback;
|
HearthstoneCallback hearthstoneCallback;
|
||||||
WorldEntryCallback worldEntryCallback_;
|
WorldEntryCallback worldEntryCallback_;
|
||||||
|
CreatureSpawnCallback creatureSpawnCallback_;
|
||||||
|
CreatureDespawnCallback creatureDespawnCallback_;
|
||||||
std::vector<uint32_t> knownSpells;
|
std::vector<uint32_t> knownSpells;
|
||||||
std::unordered_map<uint32_t, float> spellCooldowns; // spellId -> remaining seconds
|
std::unordered_map<uint32_t, float> spellCooldowns; // spellId -> remaining seconds
|
||||||
uint8_t castCount = 0;
|
uint8_t castCount = 0;
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ enum class Opcode : uint16_t {
|
||||||
|
|
||||||
// ---- Entity/Object updates ----
|
// ---- Entity/Object updates ----
|
||||||
SMSG_UPDATE_OBJECT = 0x0A9,
|
SMSG_UPDATE_OBJECT = 0x0A9,
|
||||||
|
SMSG_COMPRESSED_UPDATE_OBJECT = 0x1F6,
|
||||||
SMSG_DESTROY_OBJECT = 0x0AA,
|
SMSG_DESTROY_OBJECT = 0x0AA,
|
||||||
|
|
||||||
// ---- Chat ----
|
// ---- Chat ----
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,10 @@ private:
|
||||||
// Receive buffer
|
// Receive buffer
|
||||||
std::vector<uint8_t> receiveBuffer;
|
std::vector<uint8_t> receiveBuffer;
|
||||||
|
|
||||||
|
// Track how many header bytes have been decrypted (0-4)
|
||||||
|
// This prevents re-decrypting the same header when waiting for more data
|
||||||
|
size_t headerBytesDecrypted = 0;
|
||||||
|
|
||||||
// Packet callback
|
// Packet callback
|
||||||
std::function<void(const Packet&)> packetCallback;
|
std::function<void(const Packet&)> packetCallback;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -621,6 +621,16 @@ void Application::setupUICallbacks() {
|
||||||
loadOnlineWorldTerrain(mapId, x, y, z);
|
loadOnlineWorldTerrain(mapId, x, y, z);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Creature spawn callback (online mode) - spawn creature models
|
||||||
|
gameHandler->setCreatureSpawnCallback([this](uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation) {
|
||||||
|
spawnOnlineCreature(guid, displayId, x, y, z, orientation);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Creature despawn callback (online mode) - remove creature models
|
||||||
|
gameHandler->setCreatureDespawnCallback([this](uint64_t guid) {
|
||||||
|
despawnOnlineCreature(guid);
|
||||||
|
});
|
||||||
|
|
||||||
// "Create Character" button on character screen
|
// "Create Character" button on character screen
|
||||||
uiManager->getCharacterScreen().setOnCreateCharacter([this]() {
|
uiManager->getCharacterScreen().setOnCreateCharacter([this]() {
|
||||||
uiManager->getCharacterCreateScreen().reset();
|
uiManager->getCharacterCreateScreen().reset();
|
||||||
|
|
@ -1567,5 +1577,143 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
||||||
setState(AppState::IN_GAME);
|
setState(AppState::IN_GAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::buildCreatureDisplayLookups() {
|
||||||
|
if (creatureLookupsBuilt_ || !assetManager || !assetManager->isInitialized()) return;
|
||||||
|
|
||||||
|
LOG_INFO("Building creature display lookups from DBC files");
|
||||||
|
|
||||||
|
// CreatureDisplayInfo.dbc: displayId (col 0) → modelId (col 1)
|
||||||
|
if (auto cdi = assetManager->loadDBC("CreatureDisplayInfo.dbc"); cdi && cdi->isLoaded()) {
|
||||||
|
for (uint32_t i = 0; i < cdi->getRecordCount(); i++) {
|
||||||
|
displayToModelId_[cdi->getUInt32(i, 0)] = cdi->getUInt32(i, 1);
|
||||||
|
}
|
||||||
|
LOG_INFO("Loaded ", displayToModelId_.size(), " display→model mappings");
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatureModelData.dbc: modelId (col 0) → modelPath (col 2, .mdx → .m2)
|
||||||
|
if (auto cmd = assetManager->loadDBC("CreatureModelData.dbc"); cmd && cmd->isLoaded()) {
|
||||||
|
for (uint32_t i = 0; i < cmd->getRecordCount(); i++) {
|
||||||
|
std::string mdx = cmd->getString(i, 2);
|
||||||
|
if (mdx.empty()) continue;
|
||||||
|
// Convert .mdx to .m2
|
||||||
|
if (mdx.size() >= 4) {
|
||||||
|
mdx = mdx.substr(0, mdx.size() - 4) + ".m2";
|
||||||
|
}
|
||||||
|
modelIdToPath_[cmd->getUInt32(i, 0)] = mdx;
|
||||||
|
}
|
||||||
|
LOG_INFO("Loaded ", modelIdToPath_.size(), " model→path mappings");
|
||||||
|
}
|
||||||
|
|
||||||
|
creatureLookupsBuilt_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Application::getModelPathForDisplayId(uint32_t displayId) const {
|
||||||
|
auto itModel = displayToModelId_.find(displayId);
|
||||||
|
if (itModel == displayToModelId_.end()) return "";
|
||||||
|
|
||||||
|
auto itPath = modelIdToPath_.find(itModel->second);
|
||||||
|
if (itPath == modelIdToPath_.end()) return "";
|
||||||
|
|
||||||
|
return itPath->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x, float y, float z, float orientation) {
|
||||||
|
if (!renderer || !renderer->getCharacterRenderer() || !assetManager) return;
|
||||||
|
|
||||||
|
// Build lookups on first creature spawn
|
||||||
|
if (!creatureLookupsBuilt_) {
|
||||||
|
buildCreatureDisplayLookups();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if already spawned
|
||||||
|
if (creatureInstances_.count(guid)) return;
|
||||||
|
|
||||||
|
// Get model path from displayId
|
||||||
|
std::string m2Path = getModelPathForDisplayId(displayId);
|
||||||
|
if (m2Path.empty()) {
|
||||||
|
LOG_WARNING("No model path for displayId ", displayId, " (guid 0x", std::hex, guid, std::dec, ")");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* charRenderer = renderer->getCharacterRenderer();
|
||||||
|
|
||||||
|
// Load model if not already loaded for this displayId
|
||||||
|
uint32_t modelId = nextCreatureModelId_++;
|
||||||
|
|
||||||
|
auto m2Data = assetManager->readFile(m2Path);
|
||||||
|
if (m2Data.empty()) {
|
||||||
|
LOG_WARNING("Failed to read creature M2: ", m2Path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline::M2Model model = pipeline::M2Loader::load(m2Data);
|
||||||
|
if (model.vertices.empty()) {
|
||||||
|
LOG_WARNING("Failed to parse creature M2: ", m2Path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load skin file
|
||||||
|
std::string skinPath = m2Path.substr(0, m2Path.size() - 3) + "00.skin";
|
||||||
|
auto skinData = assetManager->readFile(skinPath);
|
||||||
|
if (!skinData.empty()) {
|
||||||
|
pipeline::M2Loader::loadSkin(skinData, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load external .anim files for sequences without flag 0x20
|
||||||
|
std::string basePath = m2Path.substr(0, m2Path.size() - 3);
|
||||||
|
for (uint32_t si = 0; si < model.sequences.size(); si++) {
|
||||||
|
if (!(model.sequences[si].flags & 0x20)) {
|
||||||
|
char animFileName[256];
|
||||||
|
snprintf(animFileName, sizeof(animFileName), "%s%04u-%02u.anim",
|
||||||
|
basePath.c_str(), model.sequences[si].id, model.sequences[si].variationIndex);
|
||||||
|
auto animData = assetManager->readFile(animFileName);
|
||||||
|
if (!animData.empty()) {
|
||||||
|
pipeline::M2Loader::loadAnimFile(m2Data, animData, si, model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!charRenderer->loadModel(model, modelId)) {
|
||||||
|
LOG_WARNING("Failed to load creature model: ", m2Path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert canonical → render coordinates
|
||||||
|
glm::vec3 renderPos = core::coords::canonicalToRender(glm::vec3(x, y, z));
|
||||||
|
|
||||||
|
// Create instance
|
||||||
|
uint32_t instanceId = charRenderer->createInstance(modelId, renderPos,
|
||||||
|
glm::vec3(0.0f, 0.0f, orientation), 1.0f);
|
||||||
|
|
||||||
|
if (instanceId == 0) {
|
||||||
|
LOG_WARNING("Failed to create creature instance for guid 0x", std::hex, guid, std::dec);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play idle animation
|
||||||
|
charRenderer->playAnimation(instanceId, 0, true);
|
||||||
|
|
||||||
|
// Track instance
|
||||||
|
creatureInstances_[guid] = instanceId;
|
||||||
|
creatureModelIds_[guid] = modelId;
|
||||||
|
|
||||||
|
LOG_INFO("Spawned creature: guid=0x", std::hex, guid, std::dec,
|
||||||
|
" displayId=", displayId, " at (", x, ", ", y, ", ", z, ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::despawnOnlineCreature(uint64_t guid) {
|
||||||
|
auto it = creatureInstances_.find(guid);
|
||||||
|
if (it == creatureInstances_.end()) return;
|
||||||
|
|
||||||
|
if (renderer && renderer->getCharacterRenderer()) {
|
||||||
|
renderer->getCharacterRenderer()->removeInstance(it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
creatureInstances_.erase(it);
|
||||||
|
creatureModelIds_.erase(guid);
|
||||||
|
|
||||||
|
LOG_INFO("Despawned creature: guid=0x", std::hex, guid, std::dec);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace core
|
} // namespace core
|
||||||
} // namespace wowee
|
} // namespace wowee
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
#include <zlib.h>
|
||||||
|
|
||||||
namespace wowee {
|
namespace wowee {
|
||||||
namespace game {
|
namespace game {
|
||||||
|
|
@ -925,12 +926,21 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Opcode::SMSG_UPDATE_OBJECT:
|
case Opcode::SMSG_UPDATE_OBJECT:
|
||||||
|
LOG_INFO("Received SMSG_UPDATE_OBJECT, state=", static_cast<int>(state), " size=", packet.getSize());
|
||||||
// Can be received after entering world
|
// Can be received after entering world
|
||||||
if (state == WorldState::IN_WORLD) {
|
if (state == WorldState::IN_WORLD) {
|
||||||
handleUpdateObject(packet);
|
handleUpdateObject(packet);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Opcode::SMSG_COMPRESSED_UPDATE_OBJECT:
|
||||||
|
LOG_INFO("Received SMSG_COMPRESSED_UPDATE_OBJECT, state=", static_cast<int>(state), " size=", packet.getSize());
|
||||||
|
// Compressed version of UPDATE_OBJECT
|
||||||
|
if (state == WorldState::IN_WORLD) {
|
||||||
|
handleCompressedUpdateObject(packet);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case Opcode::SMSG_DESTROY_OBJECT:
|
case Opcode::SMSG_DESTROY_OBJECT:
|
||||||
// Can be received after entering world
|
// Can be received after entering world
|
||||||
if (state == WorldState::IN_WORLD) {
|
if (state == WorldState::IN_WORLD) {
|
||||||
|
|
@ -2288,6 +2298,10 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
||||||
for (uint64_t guid : data.outOfRangeGuids) {
|
for (uint64_t guid : data.outOfRangeGuids) {
|
||||||
if (entityManager.hasEntity(guid)) {
|
if (entityManager.hasEntity(guid)) {
|
||||||
LOG_INFO("Entity went out of range: 0x", std::hex, guid, std::dec);
|
LOG_INFO("Entity went out of range: 0x", std::hex, guid, std::dec);
|
||||||
|
// Trigger creature despawn callback before removing entity
|
||||||
|
if (creatureDespawnCallback_) {
|
||||||
|
creatureDespawnCallback_(guid);
|
||||||
|
}
|
||||||
entityManager.removeEntity(guid);
|
entityManager.removeEntity(guid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2363,9 +2377,17 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
||||||
case 32: unit->setMaxHealth(val); break;
|
case 32: unit->setMaxHealth(val); break;
|
||||||
case 33: unit->setMaxPower(val); break;
|
case 33: unit->setMaxPower(val); break;
|
||||||
case 54: unit->setLevel(val); break;
|
case 54: unit->setLevel(val); break;
|
||||||
|
case 67: unit->setDisplayId(val); break; // UNIT_FIELD_DISPLAYID
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Trigger creature spawn callback for units with displayId
|
||||||
|
if (block.objectType == ObjectType::UNIT && unit->getDisplayId() != 0) {
|
||||||
|
if (creatureSpawnCallback_) {
|
||||||
|
creatureSpawnCallback_(block.guid, unit->getDisplayId(),
|
||||||
|
unit->getX(), unit->getY(), unit->getZ(), unit->getOrientation());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Extract XP fields for player entity
|
// Extract XP fields for player entity
|
||||||
if (block.guid == playerGuid && block.objectType == ObjectType::PLAYER) {
|
if (block.guid == playerGuid && block.objectType == ObjectType::PLAYER) {
|
||||||
|
|
@ -2444,6 +2466,44 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
||||||
LOG_INFO("Entity count: ", entityManager.getEntityCount());
|
LOG_INFO("Entity count: ", entityManager.getEntityCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameHandler::handleCompressedUpdateObject(network::Packet& packet) {
|
||||||
|
LOG_INFO("Handling SMSG_COMPRESSED_UPDATE_OBJECT, packet size: ", packet.getSize());
|
||||||
|
|
||||||
|
// First 4 bytes = decompressed size
|
||||||
|
if (packet.getSize() < 4) {
|
||||||
|
LOG_WARNING("SMSG_COMPRESSED_UPDATE_OBJECT too small");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t decompressedSize = packet.readUInt32();
|
||||||
|
LOG_INFO(" Decompressed size: ", decompressedSize);
|
||||||
|
|
||||||
|
if (decompressedSize == 0 || decompressedSize > 1024 * 1024) {
|
||||||
|
LOG_WARNING("Invalid decompressed size: ", decompressedSize);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remaining data is zlib compressed
|
||||||
|
size_t compressedSize = packet.getSize() - packet.getReadPos();
|
||||||
|
const uint8_t* compressedData = packet.getData().data() + packet.getReadPos();
|
||||||
|
|
||||||
|
// Decompress
|
||||||
|
std::vector<uint8_t> decompressed(decompressedSize);
|
||||||
|
uLongf destLen = decompressedSize;
|
||||||
|
int ret = uncompress(decompressed.data(), &destLen, compressedData, compressedSize);
|
||||||
|
|
||||||
|
if (ret != Z_OK) {
|
||||||
|
LOG_WARNING("Failed to decompress UPDATE_OBJECT: zlib error ", ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG(" Decompressed ", compressedSize, " -> ", destLen, " bytes");
|
||||||
|
|
||||||
|
// Create packet from decompressed data and parse it
|
||||||
|
network::Packet decompressedPacket(static_cast<uint16_t>(Opcode::SMSG_UPDATE_OBJECT), decompressed);
|
||||||
|
handleUpdateObject(decompressedPacket);
|
||||||
|
}
|
||||||
|
|
||||||
void GameHandler::handleDestroyObject(network::Packet& packet) {
|
void GameHandler::handleDestroyObject(network::Packet& packet) {
|
||||||
LOG_INFO("Handling SMSG_DESTROY_OBJECT");
|
LOG_INFO("Handling SMSG_DESTROY_OBJECT");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -589,28 +589,195 @@ uint64_t UpdateObjectParser::readPackedGuid(network::Packet& packet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock& block) {
|
bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock& block) {
|
||||||
// Skip movement flags and other movement data for now
|
// WoW 3.3.5a UPDATE_OBJECT movement block structure:
|
||||||
// This is a simplified implementation
|
// 1. UpdateFlags (1 byte, sometimes 2)
|
||||||
|
// 2. Movement data depends on update flags
|
||||||
|
|
||||||
// Read movement flags (not used yet)
|
// Update flags (3.3.5a uses 2 bytes for flags)
|
||||||
/*uint32_t flags =*/ packet.readUInt32();
|
uint16_t updateFlags = packet.readUInt16();
|
||||||
/*uint16_t flags2 =*/ packet.readUInt16();
|
|
||||||
|
|
||||||
// Read timestamp (not used yet)
|
LOG_DEBUG(" UpdateFlags: 0x", std::hex, updateFlags, std::dec);
|
||||||
/*uint32_t time =*/ packet.readUInt32();
|
|
||||||
|
|
||||||
// Read position
|
// UpdateFlags bit meanings:
|
||||||
block.x = packet.readFloat();
|
// 0x0001 = UPDATEFLAG_SELF
|
||||||
block.y = packet.readFloat();
|
// 0x0002 = UPDATEFLAG_TRANSPORT
|
||||||
block.z = packet.readFloat();
|
// 0x0004 = UPDATEFLAG_HAS_TARGET
|
||||||
block.orientation = packet.readFloat();
|
// 0x0008 = UPDATEFLAG_LOWGUID
|
||||||
|
// 0x0010 = UPDATEFLAG_HIGHGUID
|
||||||
|
// 0x0020 = UPDATEFLAG_LIVING
|
||||||
|
// 0x0040 = UPDATEFLAG_STATIONARY_POSITION
|
||||||
|
// 0x0080 = UPDATEFLAG_VEHICLE
|
||||||
|
// 0x0100 = UPDATEFLAG_POSITION (transport)
|
||||||
|
// 0x0200 = UPDATEFLAG_ROTATION
|
||||||
|
|
||||||
block.hasMovement = true;
|
const uint16_t UPDATEFLAG_LIVING = 0x0020;
|
||||||
|
const uint16_t UPDATEFLAG_STATIONARY_POSITION = 0x0040;
|
||||||
|
const uint16_t UPDATEFLAG_HAS_TARGET = 0x0004;
|
||||||
|
const uint16_t UPDATEFLAG_TRANSPORT = 0x0002;
|
||||||
|
const uint16_t UPDATEFLAG_POSITION = 0x0100;
|
||||||
|
const uint16_t UPDATEFLAG_VEHICLE = 0x0080;
|
||||||
|
const uint16_t UPDATEFLAG_ROTATION = 0x0200;
|
||||||
|
const uint16_t UPDATEFLAG_LOWGUID = 0x0008;
|
||||||
|
const uint16_t UPDATEFLAG_HIGHGUID = 0x0010;
|
||||||
|
|
||||||
LOG_DEBUG(" Movement: (", block.x, ", ", block.y, ", ", block.z, "), orientation=", block.orientation);
|
if (updateFlags & UPDATEFLAG_LIVING) {
|
||||||
|
// Full movement block for living units
|
||||||
|
uint32_t moveFlags = packet.readUInt32();
|
||||||
|
uint16_t moveFlags2 = packet.readUInt16();
|
||||||
|
/*uint32_t time =*/ packet.readUInt32();
|
||||||
|
|
||||||
// TODO: Parse additional movement fields based on flags
|
// Position
|
||||||
// For now, we'll skip them to keep this simple
|
block.x = packet.readFloat();
|
||||||
|
block.y = packet.readFloat();
|
||||||
|
block.z = packet.readFloat();
|
||||||
|
block.orientation = packet.readFloat();
|
||||||
|
block.hasMovement = true;
|
||||||
|
|
||||||
|
LOG_DEBUG(" LIVING movement: (", block.x, ", ", block.y, ", ", block.z,
|
||||||
|
"), o=", block.orientation, " moveFlags=0x", std::hex, moveFlags, std::dec);
|
||||||
|
|
||||||
|
// Transport data (if on transport)
|
||||||
|
if (moveFlags & 0x00000200) { // MOVEMENTFLAG_ONTRANSPORT
|
||||||
|
/*uint64_t transportGuid =*/ readPackedGuid(packet);
|
||||||
|
/*float tX =*/ packet.readFloat();
|
||||||
|
/*float tY =*/ packet.readFloat();
|
||||||
|
/*float tZ =*/ packet.readFloat();
|
||||||
|
/*float tO =*/ packet.readFloat();
|
||||||
|
/*uint32_t tTime =*/ packet.readUInt32();
|
||||||
|
/*int8_t tSeat =*/ packet.readUInt8();
|
||||||
|
|
||||||
|
if (moveFlags2 & 0x0200) { // MOVEMENTFLAG2_INTERPOLATED_MOVEMENT
|
||||||
|
/*uint32_t tTime2 =*/ packet.readUInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swimming/flying pitch
|
||||||
|
if ((moveFlags & 0x02000000) || (moveFlags2 & 0x0010)) { // MOVEMENTFLAG_SWIMMING or MOVEMENTFLAG2_ALWAYS_ALLOW_PITCHING
|
||||||
|
/*float pitch =*/ packet.readFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall time
|
||||||
|
/*uint32_t fallTime =*/ packet.readUInt32();
|
||||||
|
|
||||||
|
// Jumping
|
||||||
|
if (moveFlags & 0x00001000) { // MOVEMENTFLAG_FALLING
|
||||||
|
/*float jumpVelocity =*/ packet.readFloat();
|
||||||
|
/*float jumpSinAngle =*/ packet.readFloat();
|
||||||
|
/*float jumpCosAngle =*/ packet.readFloat();
|
||||||
|
/*float jumpXYSpeed =*/ packet.readFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spline elevation
|
||||||
|
if (moveFlags & 0x04000000) { // MOVEMENTFLAG_SPLINE_ELEVATION
|
||||||
|
/*float splineElevation =*/ packet.readFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Speeds (7 speed values)
|
||||||
|
/*float walkSpeed =*/ packet.readFloat();
|
||||||
|
/*float runSpeed =*/ packet.readFloat();
|
||||||
|
/*float runBackSpeed =*/ packet.readFloat();
|
||||||
|
/*float swimSpeed =*/ packet.readFloat();
|
||||||
|
/*float swimBackSpeed =*/ packet.readFloat();
|
||||||
|
/*float flightSpeed =*/ packet.readFloat();
|
||||||
|
/*float flightBackSpeed =*/ packet.readFloat();
|
||||||
|
/*float turnRate =*/ packet.readFloat();
|
||||||
|
/*float pitchRate =*/ packet.readFloat();
|
||||||
|
|
||||||
|
// Spline data
|
||||||
|
if (moveFlags & 0x08000000) { // MOVEMENTFLAG_SPLINE_ENABLED
|
||||||
|
// Skip spline data for now - complex structure
|
||||||
|
uint32_t splineFlags = packet.readUInt32();
|
||||||
|
|
||||||
|
if (splineFlags & 0x00010000) { // has final point
|
||||||
|
/*float finalX =*/ packet.readFloat();
|
||||||
|
/*float finalY =*/ packet.readFloat();
|
||||||
|
/*float finalZ =*/ packet.readFloat();
|
||||||
|
} else if (splineFlags & 0x00020000) { // has final target
|
||||||
|
/*uint64_t finalTarget =*/ packet.readUInt64();
|
||||||
|
} else if (splineFlags & 0x00040000) { // has final angle
|
||||||
|
/*float finalAngle =*/ packet.readFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*uint32_t timePassed =*/ packet.readUInt32();
|
||||||
|
/*uint32_t duration =*/ packet.readUInt32();
|
||||||
|
/*uint32_t splineId =*/ packet.readUInt32();
|
||||||
|
|
||||||
|
/*float durationMod =*/ packet.readFloat();
|
||||||
|
/*float durationModNext =*/ packet.readFloat();
|
||||||
|
|
||||||
|
/*float verticalAccel =*/ packet.readFloat();
|
||||||
|
|
||||||
|
/*uint32_t effectStartTime =*/ packet.readUInt32();
|
||||||
|
|
||||||
|
uint32_t pointCount = packet.readUInt32();
|
||||||
|
for (uint32_t i = 0; i < pointCount; i++) {
|
||||||
|
/*float px =*/ packet.readFloat();
|
||||||
|
/*float py =*/ packet.readFloat();
|
||||||
|
/*float pz =*/ packet.readFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*uint8_t splineMode =*/ packet.readUInt8();
|
||||||
|
/*float endPointX =*/ packet.readFloat();
|
||||||
|
/*float endPointY =*/ packet.readFloat();
|
||||||
|
/*float endPointZ =*/ packet.readFloat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (updateFlags & UPDATEFLAG_POSITION) {
|
||||||
|
// Transport position update
|
||||||
|
/*uint64_t transportGuid =*/ readPackedGuid(packet);
|
||||||
|
block.x = packet.readFloat();
|
||||||
|
block.y = packet.readFloat();
|
||||||
|
block.z = packet.readFloat();
|
||||||
|
/*float transportOffsetX =*/ packet.readFloat();
|
||||||
|
/*float transportOffsetY =*/ packet.readFloat();
|
||||||
|
/*float transportOffsetZ =*/ packet.readFloat();
|
||||||
|
block.orientation = packet.readFloat();
|
||||||
|
/*float corpseOrientation =*/ packet.readFloat();
|
||||||
|
block.hasMovement = true;
|
||||||
|
|
||||||
|
LOG_DEBUG(" POSITION: (", block.x, ", ", block.y, ", ", block.z, "), o=", block.orientation);
|
||||||
|
}
|
||||||
|
else if (updateFlags & UPDATEFLAG_STATIONARY_POSITION) {
|
||||||
|
// Simple stationary position (4 floats)
|
||||||
|
block.x = packet.readFloat();
|
||||||
|
block.y = packet.readFloat();
|
||||||
|
block.z = packet.readFloat();
|
||||||
|
block.orientation = packet.readFloat();
|
||||||
|
block.hasMovement = true;
|
||||||
|
|
||||||
|
LOG_DEBUG(" STATIONARY: (", block.x, ", ", block.y, ", ", block.z, "), o=", block.orientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Target GUID (for units with target)
|
||||||
|
if (updateFlags & UPDATEFLAG_HAS_TARGET) {
|
||||||
|
/*uint64_t targetGuid =*/ readPackedGuid(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transport time
|
||||||
|
if (updateFlags & UPDATEFLAG_TRANSPORT) {
|
||||||
|
/*uint32_t transportTime =*/ packet.readUInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vehicle
|
||||||
|
if (updateFlags & UPDATEFLAG_VEHICLE) {
|
||||||
|
/*uint32_t vehicleId =*/ packet.readUInt32();
|
||||||
|
/*float vehicleOrientation =*/ packet.readFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotation (GameObjects)
|
||||||
|
if (updateFlags & UPDATEFLAG_ROTATION) {
|
||||||
|
/*int64_t rotation =*/ packet.readUInt64();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Low GUID
|
||||||
|
if (updateFlags & UPDATEFLAG_LOWGUID) {
|
||||||
|
/*uint32_t lowGuid =*/ packet.readUInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
// High GUID
|
||||||
|
if (updateFlags & UPDATEFLAG_HIGHGUID) {
|
||||||
|
/*uint32_t highGuid =*/ packet.readUInt32();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ void WorldSocket::disconnect() {
|
||||||
connected = false;
|
connected = false;
|
||||||
encryptionEnabled = false;
|
encryptionEnabled = false;
|
||||||
receiveBuffer.clear();
|
receiveBuffer.clear();
|
||||||
|
headerBytesDecrypted = 0;
|
||||||
LOG_INFO("Disconnected from world server");
|
LOG_INFO("Disconnected from world server");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,31 +198,30 @@ void WorldSocket::update() {
|
||||||
void WorldSocket::tryParsePackets() {
|
void WorldSocket::tryParsePackets() {
|
||||||
// World server packets have 4-byte incoming header: size(2) + opcode(2)
|
// World server packets have 4-byte incoming header: size(2) + opcode(2)
|
||||||
while (receiveBuffer.size() >= 4) {
|
while (receiveBuffer.size() >= 4) {
|
||||||
// Copy header for decryption
|
// Decrypt header bytes in-place if encryption is enabled
|
||||||
uint8_t header[4];
|
// Only decrypt bytes we haven't already decrypted
|
||||||
memcpy(header, receiveBuffer.data(), 4);
|
if (encryptionEnabled && headerBytesDecrypted < 4) {
|
||||||
|
size_t toDecrypt = 4 - headerBytesDecrypted;
|
||||||
// Decrypt header if encryption is enabled
|
decryptCipher.process(receiveBuffer.data() + headerBytesDecrypted, toDecrypt);
|
||||||
if (encryptionEnabled) {
|
headerBytesDecrypted = 4;
|
||||||
decryptCipher.process(header, 4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse header
|
// Parse header (now decrypted in-place)
|
||||||
// Size: 2 bytes big-endian (includes opcode, so payload = size - 2)
|
// Size: 2 bytes big-endian (includes opcode, so payload = size - 2)
|
||||||
uint16_t size = (header[0] << 8) | header[1];
|
uint16_t size = (receiveBuffer[0] << 8) | receiveBuffer[1];
|
||||||
// Opcode: 2 bytes little-endian
|
// Opcode: 2 bytes little-endian
|
||||||
uint16_t opcode = header[2] | (header[3] << 8);
|
uint16_t opcode = receiveBuffer[2] | (receiveBuffer[3] << 8);
|
||||||
|
|
||||||
LOG_DEBUG("RECV encryptionEnabled=", encryptionEnabled,
|
LOG_DEBUG("RECV encryptionEnabled=", encryptionEnabled,
|
||||||
" header=[", std::hex, (int)header[0], " ", (int)header[1], " ",
|
" header=[", std::hex, (int)receiveBuffer[0], " ", (int)receiveBuffer[1], " ",
|
||||||
(int)header[2], " ", (int)header[3], std::dec, "]",
|
(int)receiveBuffer[2], " ", (int)receiveBuffer[3], std::dec, "]",
|
||||||
" -> size=", size, " opcode=0x", std::hex, opcode, std::dec);
|
" -> size=", size, " opcode=0x", std::hex, opcode, std::dec);
|
||||||
|
|
||||||
// Total packet size: size field (2) + size value (which includes opcode + payload)
|
// Total packet size: size field (2) + size value (which includes opcode + payload)
|
||||||
size_t totalSize = 2 + size;
|
size_t totalSize = 2 + size;
|
||||||
|
|
||||||
if (receiveBuffer.size() < totalSize) {
|
if (receiveBuffer.size() < totalSize) {
|
||||||
// Not enough data yet
|
// Not enough data yet - header stays decrypted in buffer
|
||||||
LOG_DEBUG("Waiting for more data: have ", receiveBuffer.size(),
|
LOG_DEBUG("Waiting for more data: have ", receiveBuffer.size(),
|
||||||
" bytes, need ", totalSize);
|
" bytes, need ", totalSize);
|
||||||
break;
|
break;
|
||||||
|
|
@ -249,8 +249,9 @@ void WorldSocket::tryParsePackets() {
|
||||||
// Create packet with opcode and payload
|
// Create packet with opcode and payload
|
||||||
Packet packet(opcode, packetData);
|
Packet packet(opcode, packetData);
|
||||||
|
|
||||||
// Remove parsed data from buffer
|
// Remove parsed data from buffer and reset header decryption counter
|
||||||
receiveBuffer.erase(receiveBuffer.begin(), receiveBuffer.begin() + totalSize);
|
receiveBuffer.erase(receiveBuffer.begin(), receiveBuffer.begin() + totalSize);
|
||||||
|
headerBytesDecrypted = 0;
|
||||||
|
|
||||||
// Call callback if set
|
// Call callback if set
|
||||||
if (packetCallback) {
|
if (packetCallback) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue