mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
game,warden,assets: fix unknown player names, warden heap overlap, and Vanilla Item.dbc
- game: clear pendingNameQueries on player out-of-range and DESTROY_OBJECT so re-entering players get a fresh name query instead of being silently skipped - game: add 5s periodic name resync scan that re-queries players with empty names and no pending query, recovering from dropped CMSG_NAME_QUERY responses - warden: fix UC_ERR_MAP by moving HEAP_BASE from 0x200000 to 0x20000000; the old heap [0x200000, 0x1200000) overlapped the module at 0x400000, causing Unicorn to reject the heap mapping and abort emulator initialisation - warden: add early overlap check between module and heap regions to catch future layout bugs at init time - assets: add loadDBCOptional() which logs at DEBUG level when a DBC is absent, for files that are not distributed on all expansions - assets: use loadDBCOptional for Item.dbc (absent on Vanilla 1.12 clients) and fall back to server-sent itemInfoCache displayInfoId for NPC weapon resolution
This commit is contained in:
parent
dc2aab5e90
commit
3cdaf78369
5 changed files with 114 additions and 3 deletions
|
|
@ -66,6 +66,14 @@ public:
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<DBCFile> loadDBC(const std::string& name);
|
std::shared_ptr<DBCFile> 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<DBCFile> loadDBCOptional(const std::string& name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a cached DBC file
|
* Get a cached DBC file
|
||||||
* @param name DBC file name
|
* @param name DBC file name
|
||||||
|
|
|
||||||
|
|
@ -3365,7 +3365,9 @@ bool Application::tryAttachCreatureVirtualWeapons(uint64_t guid, uint32_t instan
|
||||||
|
|
||||||
auto itemDisplayDbc = assetManager->loadDBC("ItemDisplayInfo.dbc");
|
auto itemDisplayDbc = assetManager->loadDBC("ItemDisplayInfo.dbc");
|
||||||
if (!itemDisplayDbc) return false;
|
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()
|
const auto* idiL = pipeline::getActiveDBCLayout()
|
||||||
? pipeline::getActiveDBCLayout()->getLayout("ItemDisplayInfo") : nullptr;
|
? pipeline::getActiveDBCLayout()->getLayout("ItemDisplayInfo") : nullptr;
|
||||||
const auto* itemL = pipeline::getActiveDBCLayout()
|
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 {
|
auto resolveDisplayInfoId = [&](uint32_t rawId) -> uint32_t {
|
||||||
if (rawId == 0) return 0;
|
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
|
// Resolve strictly through Item.dbc entry -> DisplayID to avoid
|
||||||
// accidental ItemDisplayInfo ID collisions (staff/hilt mismatches).
|
// accidental ItemDisplayInfo ID collisions (staff/hilt mismatches).
|
||||||
if (itemDbc) {
|
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;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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<Player>(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) {
|
if (pendingLootMoneyNotifyTimer_ > 0.0f) {
|
||||||
pendingLootMoneyNotifyTimer_ -= deltaTime;
|
pendingLootMoneyNotifyTimer_ -= deltaTime;
|
||||||
if (pendingLootMoneyNotifyTimer_ <= 0.0f) {
|
if (pendingLootMoneyNotifyTimer_ <= 0.0f) {
|
||||||
|
|
@ -7436,6 +7460,9 @@ void GameHandler::handleUpdateObject(network::Packet& packet) {
|
||||||
otherPlayerMoveTimeMs_.erase(guid);
|
otherPlayerMoveTimeMs_.erase(guid);
|
||||||
inspectedPlayerItemEntries_.erase(guid);
|
inspectedPlayerItemEntries_.erase(guid);
|
||||||
pendingAutoInspect_.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_) {
|
} else if (entity->getType() == ObjectType::GAMEOBJECT && gameObjectDespawnCallback_) {
|
||||||
gameObjectDespawnCallback_(guid);
|
gameObjectDespawnCallback_(guid);
|
||||||
}
|
}
|
||||||
|
|
@ -8407,6 +8434,7 @@ void GameHandler::handleDestroyObject(network::Packet& packet) {
|
||||||
otherPlayerMoveTimeMs_.erase(data.guid);
|
otherPlayerMoveTimeMs_.erase(data.guid);
|
||||||
inspectedPlayerItemEntries_.erase(data.guid);
|
inspectedPlayerItemEntries_.erase(data.guid);
|
||||||
pendingAutoInspect_.erase(data.guid);
|
pendingAutoInspect_.erase(data.guid);
|
||||||
|
pendingNameQueries.erase(data.guid);
|
||||||
} else if (entity->getType() == ObjectType::GAMEOBJECT && gameObjectDespawnCallback_) {
|
} else if (entity->getType() == ObjectType::GAMEOBJECT && gameObjectDespawnCallback_) {
|
||||||
gameObjectDespawnCallback_(data.guid);
|
gameObjectDespawnCallback_(data.guid);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,11 @@ namespace game {
|
||||||
#ifdef HAVE_UNICORN
|
#ifdef HAVE_UNICORN
|
||||||
|
|
||||||
// Memory layout for emulated environment
|
// 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_BASE = 0x00100000; // 1MB
|
||||||
constexpr uint32_t STACK_SIZE = 0x00100000; // 1MB stack
|
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 HEAP_SIZE = 0x01000000; // 16MB heap
|
||||||
constexpr uint32_t API_STUB_BASE = 0x70000000; // API stub area (high memory)
|
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;
|
moduleBase_ = baseAddress;
|
||||||
moduleSize_ = (moduleSize + 0xFFF) & ~0xFFF; // Align to 4KB
|
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)
|
// Map module memory (code + data)
|
||||||
err = uc_mem_map(uc_, moduleBase_, moduleSize_, UC_PROT_ALL);
|
err = uc_mem_map(uc_, moduleBase_, moduleSize_, UC_PROT_ALL);
|
||||||
if (err != UC_ERR_OK) {
|
if (err != UC_ERR_OK) {
|
||||||
|
|
|
||||||
|
|
@ -296,6 +296,55 @@ std::shared_ptr<DBCFile> AssetManager::loadDBC(const std::string& name) {
|
||||||
return dbc;
|
return dbc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<DBCFile> 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<uint8_t> 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_t>(size));
|
||||||
|
f.read(reinterpret_cast<char*>(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<DBCFile>();
|
||||||
|
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<DBCFile> AssetManager::getDBC(const std::string& name) const {
|
std::shared_ptr<DBCFile> AssetManager::getDBC(const std::string& name) const {
|
||||||
auto it = dbcCache.find(name);
|
auto it = dbcCache.find(name);
|
||||||
if (it != dbcCache.end()) {
|
if (it != dbcCache.end()) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue