diff --git a/include/pipeline/asset_manager.hpp b/include/pipeline/asset_manager.hpp index a32895d6..2ad9e6c0 100644 --- a/include/pipeline/asset_manager.hpp +++ b/include/pipeline/asset_manager.hpp @@ -66,6 +66,14 @@ public: */ std::shared_ptr loadDBC(const std::string& name); + /** + * Load a DBC file that is optional (not all expansions ship it). + * Returns nullptr quietly (debug-level log only) when the file is absent. + * @param name DBC file name (e.g., "Item.dbc") + * @return Loaded DBC file, or nullptr if not available + */ + std::shared_ptr loadDBCOptional(const std::string& name); + /** * Get a cached DBC file * @param name DBC file name diff --git a/src/core/application.cpp b/src/core/application.cpp index 9ffc1302..90c02172 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -3365,7 +3365,9 @@ bool Application::tryAttachCreatureVirtualWeapons(uint64_t guid, uint32_t instan auto itemDisplayDbc = assetManager->loadDBC("ItemDisplayInfo.dbc"); if (!itemDisplayDbc) return false; - auto itemDbc = assetManager->loadDBC("Item.dbc"); + // Item.dbc is not distributed to clients in Vanilla 1.12; on those expansions + // item display IDs are resolved via the server-sent item cache instead. + auto itemDbc = assetManager->loadDBCOptional("Item.dbc"); const auto* idiL = pipeline::getActiveDBCLayout() ? pipeline::getActiveDBCLayout()->getLayout("ItemDisplayInfo") : nullptr; const auto* itemL = pipeline::getActiveDBCLayout() @@ -3373,7 +3375,7 @@ bool Application::tryAttachCreatureVirtualWeapons(uint64_t guid, uint32_t instan auto resolveDisplayInfoId = [&](uint32_t rawId) -> uint32_t { if (rawId == 0) return 0; - // AzerothCore uses item entries in UNIT_VIRTUAL_ITEM_SLOT_ID. + // Primary path: AzerothCore uses item entries in UNIT_VIRTUAL_ITEM_SLOT_ID. // Resolve strictly through Item.dbc entry -> DisplayID to avoid // accidental ItemDisplayInfo ID collisions (staff/hilt mismatches). if (itemDbc) { @@ -3386,6 +3388,17 @@ bool Application::tryAttachCreatureVirtualWeapons(uint64_t guid, uint32_t instan } } } + // Fallback: Vanilla 1.12 does not distribute Item.dbc to clients. + // Items arrive via SMSG_ITEM_QUERY_SINGLE_RESPONSE and are cached in + // itemInfoCache_. Use the server-sent displayInfoId when available. + if (!itemDbc && gameHandler) { + if (const auto* info = gameHandler->getItemInfo(rawId)) { + uint32_t displayIdB = info->displayInfoId; + if (displayIdB != 0 && itemDisplayDbc->findRecordById(displayIdB) >= 0) { + return displayIdB; + } + } + } return 0; }; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 75440ff7..c6355e9c 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -659,6 +659,30 @@ void GameHandler::update(float deltaTime) { } } + // Periodically re-query names for players whose initial CMSG_NAME_QUERY was + // lost (server didn't respond) or whose entity was recreated while the query + // was still pending. Runs every 5 seconds to keep overhead minimal. + if (state == WorldState::IN_WORLD && socket) { + static float nameResyncTimer = 0.0f; + nameResyncTimer += deltaTime; + if (nameResyncTimer >= 5.0f) { + nameResyncTimer = 0.0f; + for (const auto& [guid, entity] : entityManager.getEntities()) { + if (!entity || entity->getType() != ObjectType::PLAYER) continue; + if (guid == playerGuid) continue; + auto player = std::static_pointer_cast(entity); + if (!player->getName().empty()) continue; + if (playerNameCache.count(guid)) continue; + if (pendingNameQueries.count(guid)) continue; + // Player entity exists with empty name and no pending query — resend. + LOG_DEBUG("Name resync: re-querying guid=0x", std::hex, guid, std::dec); + pendingNameQueries.insert(guid); + auto pkt = NameQueryPacket::build(guid); + socket->send(pkt); + } + } + } + if (pendingLootMoneyNotifyTimer_ > 0.0f) { pendingLootMoneyNotifyTimer_ -= deltaTime; if (pendingLootMoneyNotifyTimer_ <= 0.0f) { @@ -7436,6 +7460,9 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { otherPlayerMoveTimeMs_.erase(guid); inspectedPlayerItemEntries_.erase(guid); pendingAutoInspect_.erase(guid); + // Clear pending name query so the query is re-sent when this player + // comes back into range (entity is recreated as a new object). + pendingNameQueries.erase(guid); } else if (entity->getType() == ObjectType::GAMEOBJECT && gameObjectDespawnCallback_) { gameObjectDespawnCallback_(guid); } @@ -8407,6 +8434,7 @@ void GameHandler::handleDestroyObject(network::Packet& packet) { otherPlayerMoveTimeMs_.erase(data.guid); inspectedPlayerItemEntries_.erase(data.guid); pendingAutoInspect_.erase(data.guid); + pendingNameQueries.erase(data.guid); } else if (entity->getType() == ObjectType::GAMEOBJECT && gameObjectDespawnCallback_) { gameObjectDespawnCallback_(data.guid); } diff --git a/src/game/warden_emulator.cpp b/src/game/warden_emulator.cpp index 1d43768b..570f063b 100644 --- a/src/game/warden_emulator.cpp +++ b/src/game/warden_emulator.cpp @@ -14,9 +14,11 @@ namespace game { #ifdef HAVE_UNICORN // Memory layout for emulated environment +// Note: heap must not overlap the module region (typically loaded at 0x400000) +// or the stack. Keep heap above 0x02000000 (32MB) to leave space for module + padding. constexpr uint32_t STACK_BASE = 0x00100000; // 1MB constexpr uint32_t STACK_SIZE = 0x00100000; // 1MB stack -constexpr uint32_t HEAP_BASE = 0x00200000; // 2MB +constexpr uint32_t HEAP_BASE = 0x02000000; // 32MB — well above typical module base (0x400000) constexpr uint32_t HEAP_SIZE = 0x01000000; // 16MB heap constexpr uint32_t API_STUB_BASE = 0x70000000; // API stub area (high memory) @@ -58,6 +60,17 @@ bool WardenEmulator::initialize(const void* moduleCode, size_t moduleSize, uint3 moduleBase_ = baseAddress; moduleSize_ = (moduleSize + 0xFFF) & ~0xFFF; // Align to 4KB + // Detect overlap between module and heap/stack regions early. + uint32_t modEnd = moduleBase_ + moduleSize_; + if (modEnd > heapBase_ && moduleBase_ < heapBase_ + heapSize_) { + std::cerr << "[WardenEmulator] Module [0x" << std::hex << moduleBase_ + << ", 0x" << modEnd << ") overlaps heap [0x" << heapBase_ + << ", 0x" << (heapBase_ + heapSize_) << ") — adjust HEAP_BASE\n" << std::dec; + uc_close(uc_); + uc_ = nullptr; + return false; + } + // Map module memory (code + data) err = uc_mem_map(uc_, moduleBase_, moduleSize_, UC_PROT_ALL); if (err != UC_ERR_OK) { diff --git a/src/pipeline/asset_manager.cpp b/src/pipeline/asset_manager.cpp index 19fa13f8..74b46219 100644 --- a/src/pipeline/asset_manager.cpp +++ b/src/pipeline/asset_manager.cpp @@ -296,6 +296,55 @@ std::shared_ptr AssetManager::loadDBC(const std::string& name) { return dbc; } +std::shared_ptr AssetManager::loadDBCOptional(const std::string& name) { + // Check cache first + auto it = dbcCache.find(name); + if (it != dbcCache.end()) return it->second; + + // Try binary DBC + std::vector dbcData; + { + std::string dbcPath = "DBFilesClient\\" + name; + dbcData = readFile(dbcPath); + } + + // Fall back to expansion-specific CSV + if (dbcData.empty() && !expansionDataPath_.empty()) { + std::string baseName = name; + auto dot = baseName.rfind('.'); + if (dot != std::string::npos) baseName = baseName.substr(0, dot); + std::string csvPath = expansionDataPath_ + "/db/" + baseName + ".csv"; + if (std::filesystem::exists(csvPath)) { + std::ifstream f(csvPath, std::ios::binary | std::ios::ate); + if (f) { + auto size = f.tellg(); + if (size > 0) { + f.seekg(0); + dbcData.resize(static_cast(size)); + f.read(reinterpret_cast(dbcData.data()), size); + LOG_INFO("Binary DBC not found, using CSV fallback: ", csvPath); + } + } + } + } + + if (dbcData.empty()) { + // Expected on some expansions — log at debug level only. + LOG_DEBUG("Optional DBC not found (expected on some expansions): ", name); + return nullptr; + } + + auto dbc = std::make_shared(); + if (!dbc->load(dbcData)) { + LOG_ERROR("Failed to load DBC: ", name); + return nullptr; + } + + dbcCache[name] = dbc; + LOG_INFO("Loaded optional DBC: ", name, " (", dbc->getRecordCount(), " records)"); + return dbc; +} + std::shared_ptr AssetManager::getDBC(const std::string& name) const { auto it = dbcCache.find(name); if (it != dbcCache.end()) {