From 0523a2125527b6f59ad779bc9710f022043628f9 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 11 Aug 2026 11:19:50 -0400 Subject: [PATCH 1/4] refactor(lib): Add path separator helpers to PathUtil --- Core/Libraries/Include/Lib/PathUtil.h | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/Core/Libraries/Include/Lib/PathUtil.h b/Core/Libraries/Include/Lib/PathUtil.h index cf1ce769d91..eddade82485 100644 --- a/Core/Libraries/Include/Lib/PathUtil.h +++ b/Core/Libraries/Include/Lib/PathUtil.h @@ -23,6 +23,29 @@ #include "BaseType.h" #include +inline char getNativePathSeparator() +{ +#ifdef _WIN32 + return '\\'; +#else + return '/'; +#endif +} + +inline const char* getLastPathSeparator(const char* path) +{ + const char* forward = strrchr(path, '/'); + const char* backward = strrchr(path, '\\'); + return !forward ? backward : (!backward || forward > backward ? forward : backward); +} + +inline const wchar_t* getLastPathSeparator(const wchar_t* path) +{ + const wchar_t* forward = wcsrchr(path, L'/'); + const wchar_t* backward = wcsrchr(path, L'\\'); + return !forward ? backward : (!backward || forward > backward ? forward : backward); +} + inline const char* getExtension(const char* path) { const char* lastDot = strrchr(path, '.'); @@ -32,7 +55,7 @@ inline const char* getExtension(const char* path) return nullptr; } - const char* lastSeparator = maxPtr(strrchr(path, '/'), strrchr(path, '\\')); + const char* lastSeparator = getLastPathSeparator(path); // Check if the dot is contained in the filename if (lastSeparator && lastDot < lastSeparator) @@ -52,7 +75,7 @@ inline const wchar_t* getExtension(const wchar_t* path) return nullptr; } - const wchar_t* lastSeparator = maxPtr(wcsrchr(path, L'/'), wcsrchr(path, L'\\')); + const wchar_t* lastSeparator = getLastPathSeparator(path); // Check if the dot is contained in the filename if (lastSeparator && lastDot < lastSeparator) From 754f164179596bae0e2a5878c4523e55afadfdb8 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 11 Aug 2026 11:21:17 -0400 Subject: [PATCH 2/4] bugfix(map): Guard the fallback map display name against a missing separator --- Core/GameEngine/Source/GameClient/MapUtil.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/MapUtil.cpp b/Core/GameEngine/Source/GameClient/MapUtil.cpp index 1806c50741a..8ac297c5804 100644 --- a/Core/GameEngine/Source/GameClient/MapUtil.cpp +++ b/Core/GameEngine/Source/GameClient/MapUtil.cpp @@ -60,12 +60,19 @@ #include "GameLogic/FPUControl.h" #include "GameNetwork/GameInfo.h" #include "GameNetwork/NetworkDefs.h" +#include "Lib/PathUtil.h" //------------------------------------------------------------------------------- // PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// static const char *mapExtension = ".map"; +static const char *getPathLeaf( const AsciiString &path ) +{ + const char *separator = getLastPathSeparator(path.str()); + return separator ? separator + 1 : path.str(); +} + static Int m_width = 0; ///< Height map width. static Int m_height = 0; ///< Height map height (y size of array). static Int m_borderSize = 0; ///< Non-playable border area. @@ -592,7 +599,7 @@ Bool MapCache::addMap( { // unofficial maps or maps without names AsciiString tempdisplayname; - tempdisplayname = fname.reverseFind('\\') + 1; + tempdisplayname = getPathLeaf(fname); (*this)[lowerFname].m_displayName.translate(tempdisplayname); if (md.m_numPlayers >= 2) { @@ -654,7 +661,7 @@ Bool MapCache::addMap( { DEBUG_LOG(("Missing TheKey_mapName!")); AsciiString tempdisplayname; - tempdisplayname = fname.reverseFind('\\') + 1; + tempdisplayname = getPathLeaf(fname); md.m_displayName.translate(tempdisplayname); if (md.m_numPlayers >= 2) { From 50c8fde71a8b50f0d21658cbd01339d5501ca009 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 11 Aug 2026 11:22:50 -0400 Subject: [PATCH 3/4] bugfix(map): Write and scan the map cache with the platform separator --- Core/GameEngine/Source/GameClient/MapUtil.cpp | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/Core/GameEngine/Source/GameClient/MapUtil.cpp b/Core/GameEngine/Source/GameClient/MapUtil.cpp index 8ac297c5804..9a30c686b0d 100644 --- a/Core/GameEngine/Source/GameClient/MapUtil.cpp +++ b/Core/GameEngine/Source/GameClient/MapUtil.cpp @@ -67,6 +67,26 @@ // PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// static const char *mapExtension = ".map"; +static void appendPathSeparator( AsciiString &path ) +{ + if (path.isNotEmpty()) + { + const char last = path.getCharAt(path.getLength() - 1); + if (last != '/' && last != '\\') + { + path.concat(getNativePathSeparator()); + } + } +} + +static AsciiString getPathInDirectory( const AsciiString &directory, const char *filename ) +{ + AsciiString path = directory; + appendPathSeparator(path); + path.concat(filename); + return path; +} + static const char *getPathLeaf( const AsciiString &path ) { const char *separator = getLastPathSeparator(path.str()); @@ -338,12 +358,10 @@ AsciiString MapCache::getMapExtension() const void MapCache::writeCacheINI( const AsciiString &mapDir ) { - AsciiString filepath = mapDir; - filepath.concat('\\'); + AsciiString filepath = getPathInDirectory(mapDir, m_mapCacheName); TheFileSystem->createDirectory(mapDir); - filepath.concat(m_mapCacheName); FILE *fp = fopen(filepath.str(), "w"); DEBUG_ASSERTCRASH(fp != nullptr, ("Failed to create %s", filepath.str())); if (fp == nullptr) { @@ -506,8 +524,7 @@ Bool MapCache::clearUnseenMaps( const AsciiString &mapDir ) void MapCache::loadMapsFromMapCacheINI( const AsciiString &mapDir ) { INI ini; - AsciiString fname; - fname.format("%s\\%s", mapDir.str(), m_mapCacheName); + AsciiString fname = getPathInDirectory(mapDir, m_mapCacheName); if (TheFileSystem->doesFileExist(fname.str())) { @@ -521,8 +538,8 @@ Bool MapCache::loadMapsFromDisk( const AsciiString &mapDir, Bool isOfficial, Boo FilenameList filepathList; FilenameListIter filepathIt; - AsciiString toplevelPattern; - toplevelPattern.format("%s\\", mapDir.str()); + AsciiString toplevelPattern = mapDir; + appendPathSeparator(toplevelPattern); Bool mapListChanged = FALSE; AsciiString filenamepattern; filenamepattern.format("*.%s", getMapExtension().str()); @@ -537,14 +554,13 @@ Bool MapCache::loadMapsFromDisk( const AsciiString &mapDir, Bool isOfficial, Boo AsciiString filepathLower = *filepathIt; filepathLower.toLower(); - const char *szFilenameLower = filepathLower.reverseFind('\\'); + const char *szFilenameLower = getLastPathSeparator(filepathLower.str()); if (!szFilenameLower) { - DEBUG_CRASH(("Couldn't find \\ in map name!")); + DEBUG_CRASH(("Couldn't find path separator in map name!")); continue; } - AsciiString endingStr; AsciiString filenameLower = szFilenameLower+1; filenameLower.truncateBy(strlen(mapExtension)); @@ -554,7 +570,8 @@ Bool MapCache::loadMapsFromDisk( const AsciiString &mapDir, Bool isOfficial, Boo continue; } - endingStr.format("%s\\%s%s", filenameLower.str(), filenameLower.str(), mapExtension); + AsciiString endingStr; + endingStr.format("%s%c%s%s", filenameLower.str(), *szFilenameLower, filenameLower.str(), mapExtension); if (!filepathLower.endsWithNoCase(endingStr.str())) { From 242bf4862ffd09f14a5b142b4bf035c8dae3f080 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Fri, 14 Aug 2026 07:53:18 -0400 Subject: [PATCH 4/4] bugfix(map): Read the map cache display name with either path separator --- Core/GameEngine/Source/Common/INI/INIMapCache.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Core/GameEngine/Source/Common/INI/INIMapCache.cpp b/Core/GameEngine/Source/Common/INI/INIMapCache.cpp index 5be4414bb70..40a8a8cd18a 100644 --- a/Core/GameEngine/Source/Common/INI/INIMapCache.cpp +++ b/Core/GameEngine/Source/Common/INI/INIMapCache.cpp @@ -31,6 +31,7 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine #include "Lib/BaseType.h" +#include "Lib/PathUtil.h" #include "Common/INI.h" #include "GameClient/MapUtil.h" #include "GameClient/GameText.h" @@ -148,8 +149,12 @@ void INI::parseMapCacheDefinition( INI* ini ) if (md.m_nameLookupTag.isEmpty()) { // maps without localized name tags + // TheSuperHackers @bugfix bobtista 14/08/2026 Read the leaf with either separator, because + // the map cache is written with the platform separator and a missing '\\' left this + // dereferencing nullptr + 1. AsciiString tempdisplayname; - tempdisplayname = name.reverseFind('\\') + 1; + const char *separator = getLastPathSeparator(name.str()); + tempdisplayname = separator ? separator + 1 : name.str(); md.m_displayName.translate(tempdisplayname); if (md.m_numPlayers >= 2) {