From 7264ba17069ad2a43e409eadf20f84f21b698aac Mon Sep 17 00:00:00 2001 From: Kelsi Date: Fri, 3 Apr 2026 21:26:20 -0700 Subject: [PATCH] fix(extractor): lowercase all output paths to prevent duplicate folders WoW archives contain mixed-case variants of the same path (e.g., ARMLOWERTEXTURE vs ArmLowerTexture) which created duplicate directories on case-sensitive Linux filesystems. Now mapPath() lowercases the entire output. Also keeps TextureComponents and ObjectComponents directory names instead of abbreviating them (item/texturecomponents/ instead of item/texture/) so filesystem paths match the WoW virtual paths used in manifest lookups. --- tools/asset_extract/path_mapper.cpp | 20 ++++++++++---------- tools/asset_extract/path_mapper.hpp | 3 ++- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/tools/asset_extract/path_mapper.cpp b/tools/asset_extract/path_mapper.cpp index e4e1c8c4..d9a0c3af 100644 --- a/tools/asset_extract/path_mapper.cpp +++ b/tools/asset_extract/path_mapper.cpp @@ -35,7 +35,12 @@ std::string PathMapper::extractAfterPrefix(const std::string& path, size_t prefi } std::string PathMapper::mapPath(const std::string& wowPath) { - // Preserve original casing in the remainder for filesystem readability + // Lowercase entire output path — WoW archives contain mixed-case variants + // of the same path which create duplicate directories on case-sensitive filesystems. + return toLower(mapPathImpl(wowPath)); +} + +std::string PathMapper::mapPathImpl(const std::string& wowPath) { std::string rest; // DBFilesClient\ → db/ @@ -63,21 +68,16 @@ std::string PathMapper::mapPath(const std::string& wowPath) { return "creature/" + fwd; } - // Item\ObjectComponents\{Type}\ → item/{type}/ + // Item\ObjectComponents\ → item/objectcomponents/ if (startsWithCI(wowPath, "Item\\ObjectComponents\\")) { rest = extractAfterPrefix(wowPath, 22); - std::string fwd = toForwardSlash(rest); - auto slash = fwd.find('/'); - if (slash != std::string::npos) { - return "item/" + toLower(fwd.substr(0, slash)) + "/" + fwd.substr(slash + 1); - } - return "item/" + fwd; + return "item/objectcomponents/" + toForwardSlash(rest); } - // Item\TextureComponents\ → item/texture/ + // Item\TextureComponents\ → item/texturecomponents/ if (startsWithCI(wowPath, "Item\\TextureComponents\\")) { rest = extractAfterPrefix(wowPath, 23); - return "item/texture/" + toForwardSlash(rest); + return "item/texturecomponents/" + toForwardSlash(rest); } // Interface\Icons\ → interface/icons/ diff --git a/tools/asset_extract/path_mapper.hpp b/tools/asset_extract/path_mapper.hpp index cad31c57..7369b000 100644 --- a/tools/asset_extract/path_mapper.hpp +++ b/tools/asset_extract/path_mapper.hpp @@ -16,11 +16,12 @@ public: /** * Map a WoW virtual path to a reorganized filesystem path. * @param wowPath Original WoW virtual path (backslash-separated) - * @return Reorganized relative path (forward-slash separated, original casing preserved) + * @return Reorganized relative path (forward-slash separated, fully lowercased) */ static std::string mapPath(const std::string& wowPath); private: + static std::string mapPathImpl(const std::string& wowPath); // Helpers for prefix matching (case-insensitive) static bool startsWithCI(const std::string& str, const std::string& prefix); static std::string toLower(const std::string& str);