Fix letter-named patch MPQs not loading on case-sensitive filesystems
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run

Two bugs in loadPatchArchives():
1. isLetterPatch detection was inverted (rfind != 0 is false for all
   "patch-*" entries), making the disable flags non-functional
2. Patch file lookup used exact std::filesystem::exists() which is
   case-sensitive on Linux — Patch-A.MPQ wouldn't match patch-a.mpq

Now scans the data directory once and builds a case-insensitive lookup
map, so any case variant (Patch-A.MPQ, patch-a.mpq, PATCH-A.MPQ) is
found correctly.
This commit is contained in:
Kelsi 2026-02-25 12:18:36 -08:00
parent 956e2c8bb1
commit 94e4a0bdb3

View file

@ -458,12 +458,27 @@ bool MPQManager::loadPatchArchives() {
{"patch.MPQ", 150},
};
// Build a case-insensitive lookup of files in the data directory so that
// Patch-A.MPQ, patch-a.mpq, PATCH-A.MPQ, etc. all resolve correctly on
// case-sensitive filesystems (Linux).
std::unordered_map<std::string, std::string> lowerToActual; // lowercase name → actual path
if (std::filesystem::is_directory(dataPath)) {
for (const auto& entry : std::filesystem::directory_iterator(dataPath)) {
if (!entry.is_regular_file()) continue;
std::string fname = entry.path().filename().string();
std::string lower = toLowerCopy(fname);
lowerToActual[lower] = entry.path().string();
}
}
int loadedPatches = 0;
for (const auto& [archive, priority] : patchArchives) {
// Classify letter vs numeric patch for the disable flags
std::string lowerArchive = toLowerCopy(archive);
const bool isLetterPatch =
(archive.size() >= 10) &&
(toLowerCopy(archive).rfind("patch-", 0) != 0) && // not patch-*.MPQ
(toLowerCopy(archive).rfind("patch.", 0) != 0); // not patch.MPQ
(lowerArchive.size() >= 11) && // "patch-X.mpq" = 11 chars
(lowerArchive.rfind("patch-", 0) == 0) && // starts with "patch-"
(lowerArchive[6] >= 'a' && lowerArchive[6] <= 'z'); // letter after dash
if (isLetterPatch && disableLetterPatches) {
continue;
}
@ -471,9 +486,10 @@ bool MPQManager::loadPatchArchives() {
continue;
}
std::string fullPath = dataPath + "/" + archive;
if (std::filesystem::exists(fullPath)) {
if (loadArchive(fullPath, priority)) {
// Case-insensitive file lookup
auto it = lowerToActual.find(lowerArchive);
if (it != lowerToActual.end()) {
if (loadArchive(it->second, priority)) {
loadedPatches++;
}
}