diff --git a/tools/asset_extract/extractor.cpp b/tools/asset_extract/extractor.cpp index 7a847806..81c32ba1 100644 --- a/tools/asset_extract/extractor.cpp +++ b/tools/asset_extract/extractor.cpp @@ -448,11 +448,12 @@ static std::vector discoverArchives(const std::string& mpqDir, if (actualLocaleDir.empty()) { actualLocaleDir = locale; } - std::string localeDir = mpqDir + "/" + actualLocaleDir; - std::cout << "Locale directory: " << localeDir << "\n"; + fs::path localeDirPath = fs::path(mpqDir) / actualLocaleDir; + std::string localeDir = localeDirPath.string(); auto localeMap = buildCaseMap(localeDir); for (auto& [name, realName] : localeMap) { - caseMap[lowerLocale + "/" + name] = actualLocaleDir + "/" + realName; + fs::path fullPath = fs::path(actualLocaleDir) / realName; + caseMap[lowerLocale + "/" + name] = fullPath.string(); } } @@ -520,7 +521,8 @@ static std::vector discoverArchives(const std::string& mpqDir, auto addIfPresent = [&](const std::string& expected) { auto it = caseMap.find(toLowerStr(expected)); if (it != caseMap.end()) { - result.push_back(mpqDir + "/" + it->second); + fs::path fullPath = fs::path(mpqDir) / it->second; + result.push_back(fullPath.string()); } }; @@ -733,7 +735,7 @@ bool Extractor::run(const Options& opts) { // Map to new filesystem path std::string mappedPath = PathMapper::mapPath(wowPath); - std::string fullOutputPath = effectiveOutputDir + "/" + mappedPath; + fs::path fullOutputPath = fs::path(effectiveOutputDir) / mappedPath; // Read file data from MPQ under lock std::vector data; @@ -862,7 +864,7 @@ bool Extractor::run(const Options& opts) { } // Merge with existing manifest so partial extractions don't nuke prior entries - std::string manifestPath = effectiveOutputDir + "/manifest.json"; + fs::path manifestPath = fs::path(effectiveOutputDir) / "manifest.json"; if (fs::exists(manifestPath)) { auto existing = loadManifestEntries(manifestPath); if (!existing.empty()) { @@ -899,7 +901,7 @@ bool Extractor::run(const Options& opts) { std::cout << "Verifying extracted files...\n"; uint64_t verified = 0, verifyFailed = 0; for (const auto& entry : manifestEntries) { - std::string fsPath = effectiveOutputDir + "/" + entry.filesystemPath; + fs::path fsPath = fs::path(effectiveOutputDir) / entry.filesystemPath; std::ifstream f(fsPath, std::ios::binary | std::ios::ate); if (!f.is_open()) { std::cerr << " MISSING: " << fsPath << "\n"; diff --git a/tools/asset_extract/path_mapper.cpp b/tools/asset_extract/path_mapper.cpp index d9a0c3af..85a5ebe0 100644 --- a/tools/asset_extract/path_mapper.cpp +++ b/tools/asset_extract/path_mapper.cpp @@ -18,189 +18,10 @@ std::string PathMapper::toForwardSlash(const std::string& str) { return result; } -bool PathMapper::startsWithCI(const std::string& str, const std::string& prefix) { - if (str.size() < prefix.size()) return false; - for (size_t i = 0; i < prefix.size(); ++i) { - if (std::tolower(static_cast(str[i])) != - std::tolower(static_cast(prefix[i]))) { - return false; - } - } - return true; -} - -std::string PathMapper::extractAfterPrefix(const std::string& path, size_t prefixLen) { - if (prefixLen >= path.size()) return {}; - return path.substr(prefixLen); -} - std::string PathMapper::mapPath(const std::string& wowPath) { // 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/ - if (startsWithCI(wowPath, "DBFilesClient\\")) { - rest = extractAfterPrefix(wowPath, 14); - return "db/" + toForwardSlash(rest); - } - - // Character\{Race}\{Gender}\ → character/{race}/{gender}/ - if (startsWithCI(wowPath, "Character\\")) { - rest = extractAfterPrefix(wowPath, 10); - std::string lowered = toLower(rest); - return "character/" + toForwardSlash(lowered); - } - - // Creature\{Name}\ → creature/{name}/ - if (startsWithCI(wowPath, "Creature\\")) { - rest = extractAfterPrefix(wowPath, 9); - // Keep first component lowercase for directory, preserve filename case - std::string fwd = toForwardSlash(rest); - auto slash = fwd.find('/'); - if (slash != std::string::npos) { - return "creature/" + toLower(fwd.substr(0, slash)) + "/" + fwd.substr(slash + 1); - } - return "creature/" + fwd; - } - - // Item\ObjectComponents\ → item/objectcomponents/ - if (startsWithCI(wowPath, "Item\\ObjectComponents\\")) { - rest = extractAfterPrefix(wowPath, 22); - return "item/objectcomponents/" + toForwardSlash(rest); - } - - // Item\TextureComponents\ → item/texturecomponents/ - if (startsWithCI(wowPath, "Item\\TextureComponents\\")) { - rest = extractAfterPrefix(wowPath, 23); - return "item/texturecomponents/" + toForwardSlash(rest); - } - - // Interface\Icons\ → interface/icons/ - if (startsWithCI(wowPath, "Interface\\Icons\\")) { - rest = extractAfterPrefix(wowPath, 16); - return "interface/icons/" + toForwardSlash(rest); - } - - // Interface\GossipFrame\ → interface/gossip/ - if (startsWithCI(wowPath, "Interface\\GossipFrame\\")) { - rest = extractAfterPrefix(wowPath, 21); - return "interface/gossip/" + toForwardSlash(rest); - } - - // Interface\{rest} → interface/{rest}/ - if (startsWithCI(wowPath, "Interface\\")) { - rest = extractAfterPrefix(wowPath, 10); - return "interface/" + toForwardSlash(rest); - } - - // Textures\Minimap\ → terrain/minimap/ - if (startsWithCI(wowPath, "Textures\\Minimap\\")) { - rest = extractAfterPrefix(wowPath, 17); - return "terrain/minimap/" + toForwardSlash(rest); - } - - // Textures\BakedNpcTextures\ → creature/baked/ - if (startsWithCI(wowPath, "Textures\\BakedNpcTextures\\")) { - rest = extractAfterPrefix(wowPath, 25); - return "creature/baked/" + toForwardSlash(rest); - } - - // Textures\{rest} → terrain/textures/{rest} - if (startsWithCI(wowPath, "Textures\\")) { - rest = extractAfterPrefix(wowPath, 9); - return "terrain/textures/" + toForwardSlash(rest); - } - - // World\Maps\{Map}\ → terrain/maps/{map}/ - if (startsWithCI(wowPath, "World\\Maps\\")) { - rest = extractAfterPrefix(wowPath, 11); - std::string fwd = toForwardSlash(rest); - auto slash = fwd.find('/'); - if (slash != std::string::npos) { - return "terrain/maps/" + toLower(fwd.substr(0, slash)) + "/" + fwd.substr(slash + 1); - } - return "terrain/maps/" + fwd; - } - - // World\wmo\ → world/wmo/ (preserve subpath) - if (startsWithCI(wowPath, "World\\wmo\\")) { - rest = extractAfterPrefix(wowPath, 10); - return "world/wmo/" + toForwardSlash(rest); - } - - // World\Doodads\ → world/doodads/ - if (startsWithCI(wowPath, "World\\Doodads\\")) { - rest = extractAfterPrefix(wowPath, 14); - return "world/doodads/" + toForwardSlash(rest); - } - - // World\{rest} → world/{rest}/ - if (startsWithCI(wowPath, "World\\")) { - rest = extractAfterPrefix(wowPath, 6); - return "world/" + toForwardSlash(rest); - } - - // Environments\ → environment/ - if (startsWithCI(wowPath, "Environments\\")) { - rest = extractAfterPrefix(wowPath, 13); - return "environment/" + toForwardSlash(rest); - } - - // Sound\Ambience\ → sound/ambient/ - if (startsWithCI(wowPath, "Sound\\Ambience\\")) { - rest = extractAfterPrefix(wowPath, 15); - return "sound/ambient/" + toForwardSlash(rest); - } - - // Sound\Character\ → sound/character/ - if (startsWithCI(wowPath, "Sound\\Character\\")) { - rest = extractAfterPrefix(wowPath, 16); - return "sound/character/" + toForwardSlash(rest); - } - - // Sound\Doodad\ → sound/doodad/ - if (startsWithCI(wowPath, "Sound\\Doodad\\")) { - rest = extractAfterPrefix(wowPath, 13); - return "sound/doodad/" + toForwardSlash(rest); - } - - // Sound\Creature\ → sound/creature/ - if (startsWithCI(wowPath, "Sound\\Creature\\")) { - rest = extractAfterPrefix(wowPath, 15); - return "sound/creature/" + toForwardSlash(rest); - } - - // Sound\Spells\ → sound/spell/ - if (startsWithCI(wowPath, "Sound\\Spells\\")) { - rest = extractAfterPrefix(wowPath, 13); - return "sound/spell/" + toForwardSlash(rest); - } - - // Sound\Music\ → sound/music/ - if (startsWithCI(wowPath, "Sound\\Music\\")) { - rest = extractAfterPrefix(wowPath, 12); - return "sound/music/" + toForwardSlash(rest); - } - - // Sound\{rest} → sound/{rest}/ - if (startsWithCI(wowPath, "Sound\\")) { - rest = extractAfterPrefix(wowPath, 6); - return "sound/" + toForwardSlash(rest); - } - - // Spells\ → spell/ - if (startsWithCI(wowPath, "Spells\\")) { - rest = extractAfterPrefix(wowPath, 7); - return "spell/" + toForwardSlash(rest); - } - - // Everything else → misc/{original_path} - return "misc/" + toForwardSlash(wowPath); + return toLower(toForwardSlash(wowPath)); } } // namespace tools diff --git a/tools/asset_extract/path_mapper.hpp b/tools/asset_extract/path_mapper.hpp index 7369b000..c5db03d1 100644 --- a/tools/asset_extract/path_mapper.hpp +++ b/tools/asset_extract/path_mapper.hpp @@ -6,7 +6,7 @@ namespace wowee { namespace tools { /** - * Maps WoW virtual paths to reorganized filesystem categories. + * Maps WoW virtual paths to organized filesystem categories. * * Input: WoW virtual path (e.g., "Creature\\Bear\\BearSkin.blp") * Output: Category-based relative path (e.g., "creature/bear/BearSkin.blp") @@ -14,19 +14,15 @@ namespace tools { class PathMapper { public: /** - * Map a WoW virtual path to a reorganized filesystem path. + * Map a WoW virtual path to a organized filesystem path. * @param wowPath Original WoW virtual path (backslash-separated) - * @return Reorganized relative path (forward-slash separated, fully lowercased) + * @return Organized 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); static std::string toForwardSlash(const std::string& str); - static std::string extractAfterPrefix(const std::string& path, size_t prefixLen); }; } // namespace tools