From 4a213d8da8835c3d2ca7ff714d1c91465b85f0f7 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 10 Mar 2026 03:27:30 -0700 Subject: [PATCH] tools/game: fix dbc_to_csv false-positive string detection + clear DBC cache on expansion switch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dbc_to_csv: The string-column auto-detector would mark integer fields (e.g. RaceID=1, SexID=0, BaseSection=0-4) as string columns whenever their small values were valid string-block offsets that happened to land inside longer strings. Fix by requiring that an offset point to a string *boundary* (offset 0 or immediately after a null byte) rather than any valid position — this eliminates false positives from integer fields whose values accidentally alias path substrings. Affected CSVs (CharSections, ItemDisplayInfo for Classic/TBC) can now be regenerated correctly. game_handler: clearDBCCache() is already called by application.cpp before resetDbcCaches(), but also add it inside resetDbcCaches() as a defensive measure so that future callers of resetDbcCaches() alone also flush stale expansion-specific DBC data (CharSections, ItemDisplayInfo, etc.). --- src/game/game_handler.cpp | 7 +++++++ tools/dbc_to_csv/main.cpp | 32 +++++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 9d5e4587..192c213b 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -520,6 +520,13 @@ void GameHandler::resetDbcCaches() { talentDbcLoaded_ = false; talentCache_.clear(); talentTabCache_.clear(); + // Clear the AssetManager DBC file cache so that expansion-specific DBCs + // (CharSections, ItemDisplayInfo, etc.) are reloaded from the new expansion's + // MPQ files instead of returning stale data from a previous session/expansion. + auto* am = core::Application::getInstance().getAssetManager(); + if (am) { + am->clearDBCCache(); + } LOG_INFO("GameHandler: DBC caches cleared for expansion switch"); } diff --git a/tools/dbc_to_csv/main.cpp b/tools/dbc_to_csv/main.cpp index 514d5a40..9afa875d 100644 --- a/tools/dbc_to_csv/main.cpp +++ b/tools/dbc_to_csv/main.cpp @@ -41,9 +41,31 @@ std::vector readFileBytes(const std::string& path) { return buf; } -// Check whether offset points to a plausible string in the string block. -bool isValidStringOffset(const std::vector& stringBlock, uint32_t offset) { +// Precompute the set of valid string-boundary offsets in the string block. +// An offset is a valid boundary if it is 0 or immediately follows a null byte. +// This prevents small integer values (e.g. RaceID=1, 2, 3) from being falsely +// detected as string offsets just because they land in the middle of a longer +// string that starts at a lower offset. +std::set computeStringBoundaries(const std::vector& stringBlock) { + std::set boundaries; + if (stringBlock.empty()) return boundaries; + boundaries.insert(0); // offset 0 is always a valid start + for (size_t i = 0; i + 1 < stringBlock.size(); ++i) { + if (stringBlock[i] == 0) { + boundaries.insert(static_cast(i + 1)); + } + } + return boundaries; +} + +// Check whether offset points to a valid string-boundary position in the block +// and that the string there is printable and null-terminated. +bool isValidStringOffset(const std::vector& stringBlock, + const std::set& boundaries, + uint32_t offset) { if (offset >= stringBlock.size()) return false; + // Must start at a string boundary (offset 0 or right after a null byte). + if (!boundaries.count(offset)) return false; // Must be null-terminated within the block and contain only printable/whitespace bytes. for (size_t i = offset; i < stringBlock.size(); ++i) { uint8_t c = stringBlock[i]; @@ -75,6 +97,10 @@ std::set detectStringColumns(const DBCFile& dbc, // If no string block (or trivial size), no string columns. if (stringBlock.size() <= 1) return stringCols; + // Precompute valid string-start boundaries to avoid false positives from + // integer fields whose small values accidentally land inside longer strings. + auto boundaries = computeStringBoundaries(stringBlock); + for (uint32_t col = 0; col < fieldCount; ++col) { bool allZeroOrValid = true; bool hasNonZero = false; @@ -83,7 +109,7 @@ std::set detectStringColumns(const DBCFile& dbc, uint32_t val = dbc.getUInt32(row, col); if (val == 0) continue; hasNonZero = true; - if (!isValidStringOffset(stringBlock, val)) { + if (!isValidStringOffset(stringBlock, boundaries, val)) { allZeroOrValid = false; break; }