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:
Kelsi 2026-03-10 07:00:43 -07:00
parent dc2aab5e90
commit 3cdaf78369
5 changed files with 114 additions and 3 deletions

View file

@ -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) {
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);
}

View file

@ -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) {