diff --git a/Core/GameEngine/Include/Common/FileSystem.h b/Core/GameEngine/Include/Common/FileSystem.h index 2aaa30a61f1..b71bcaf028a 100644 --- a/Core/GameEngine/Include/Common/FileSystem.h +++ b/Core/GameEngine/Include/Common/FileSystem.h @@ -163,6 +163,10 @@ class FileSystem : public SubsystemInterface static bool removeExtension(AsciiString& path); static bool removeExtension(UnicodeString& path); + /// Appends the native separator to nonempty paths unless either separator is already at the end. + /// Existing separators are not converted. + static void appendPathSeparator(AsciiString& path); + protected: #if ENABLE_FILESYSTEM_EXISTENCE_CACHE struct FileExistData diff --git a/Core/GameEngine/Source/Common/System/ArchiveFile.cpp b/Core/GameEngine/Source/Common/System/ArchiveFile.cpp index 1f982b65a1d..c9a4282d271 100644 --- a/Core/GameEngine/Source/Common/System/ArchiveFile.cpp +++ b/Core/GameEngine/Source/Common/System/ArchiveFile.cpp @@ -30,6 +30,7 @@ #include "Common/ArchiveFile.h" #include "Common/ArchiveFileSystem.h" +#include "Common/FileSystem.h" #include "Common/file.h" #include "Common/PerfTimer.h" @@ -157,9 +158,8 @@ void ArchiveFile::getFileListInDirectory(const DetailedArchivedDirectoryInfo *di const DetailedArchivedDirectoryInfo *tempDirInfo = &(diriter->second); AsciiString tempdirname; tempdirname = currentDirectory; - if ((!tempdirname.isEmpty()) && (!tempdirname.endsWith("\\"))) { - tempdirname.concat('\\'); - } + // TheSuperHackers @fix bobtista 14/08/2026 Avoid adding a separator when either separator is already present. + FileSystem::appendPathSeparator(tempdirname); tempdirname.concat(tempDirInfo->m_directoryName); getFileListInDirectory(tempDirInfo, tempdirname, searchName, filenameList, searchSubdirectories); diriter++; @@ -170,9 +170,7 @@ void ArchiveFile::getFileListInDirectory(const DetailedArchivedDirectoryInfo *di if (SearchStringMatches(fileiter->second.m_filename, searchName)) { AsciiString tempfilename; tempfilename = currentDirectory; - if ((!tempfilename.isEmpty()) && (!tempfilename.endsWith("\\"))) { - tempfilename.concat('\\'); - } + FileSystem::appendPathSeparator(tempfilename); tempfilename.concat(fileiter->second.m_filename); if (filenameList.find(tempfilename) == filenameList.end()) { // only insert into the list if its not already in there. diff --git a/Core/GameEngine/Source/Common/System/FileSystem.cpp b/Core/GameEngine/Source/Common/System/FileSystem.cpp index b8e4c4695b6..39761e41dca 100644 --- a/Core/GameEngine/Source/Common/System/FileSystem.cpp +++ b/Core/GameEngine/Source/Common/System/FileSystem.cpp @@ -358,13 +358,9 @@ Bool FileSystem::isPathInDirectory(const AsciiString& testPath, const AsciiStrin return false; } -#ifdef _WIN32 - const char* pathSep = "\\"; -#else - const char* pathSep = "/"; -#endif + const char pathSep = getNativePathSeparator(); - if (!basePathNormalized.endsWith(pathSep)) + if (basePathNormalized.getCharAt(basePathNormalized.getLength() - 1) != pathSep) { basePathNormalized.concat(pathSep); } @@ -408,3 +404,14 @@ bool FileSystem::removeExtension(UnicodeString& path) return false; } + +//============================================================================ +// FileSystem::appendPathSeparator +//============================================================================ +void FileSystem::appendPathSeparator(AsciiString& path) +{ + if (path.isNotEmpty() && !isPathSeparator(path.getCharAt(path.getLength() - 1))) + { + path.concat(getNativePathSeparator()); + } +} diff --git a/Core/GameEngine/Source/GameClient/MapUtil.cpp b/Core/GameEngine/Source/GameClient/MapUtil.cpp index 1806c50741a..d6a69a6d181 100644 --- a/Core/GameEngine/Source/GameClient/MapUtil.cpp +++ b/Core/GameEngine/Source/GameClient/MapUtil.cpp @@ -60,6 +60,7 @@ #include "GameLogic/FPUControl.h" #include "GameNetwork/GameInfo.h" #include "GameNetwork/NetworkDefs.h" +#include "Lib/PathUtil.h" //------------------------------------------------------------------------------- @@ -530,14 +531,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)); @@ -547,7 +547,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())) { @@ -591,8 +592,8 @@ Bool MapCache::addMap( if (md.m_nameLookupTag.isEmpty()) { // unofficial maps or maps without names - AsciiString tempdisplayname; - tempdisplayname = fname.reverseFind('\\') + 1; + // TheSuperHackers @bugfix bobtista 14/09/2026 Handle map filenames with either separator or no separator. + AsciiString tempdisplayname = getFileName(fname.str()); (*this)[lowerFname].m_displayName.translate(tempdisplayname); if (md.m_numPlayers >= 2) { @@ -653,8 +654,7 @@ Bool MapCache::addMap( if (!exists || nameLookupTag.isEmpty()) { DEBUG_LOG(("Missing TheKey_mapName!")); - AsciiString tempdisplayname; - tempdisplayname = fname.reverseFind('\\') + 1; + AsciiString tempdisplayname = getFileName(fname.str()); md.m_displayName.translate(tempdisplayname); if (md.m_numPlayers >= 2) { diff --git a/Core/Libraries/Include/Lib/PathUtil.h b/Core/Libraries/Include/Lib/PathUtil.h index 683f6fee130..ad6c00d6283 100644 --- a/Core/Libraries/Include/Lib/PathUtil.h +++ b/Core/Libraries/Include/Lib/PathUtil.h @@ -23,7 +23,8 @@ #include "BaseType.h" #include -inline bool isPathSeparator(char ch) +// Returns true for a separator the host platform uses to open files. +inline bool isNativePathSeparator(char ch) { #ifdef _WIN32 return ch == '\\' || ch == '/'; @@ -32,6 +33,12 @@ inline bool isPathSeparator(char ch) #endif } +// Returns true for either separator. Game data paths carry '\\' on every platform. +inline bool isPathSeparator(char ch) +{ + return ch == '/' || ch == '\\'; +} + inline bool isAbsolutePath(const char* path) { if (path == nullptr) @@ -39,14 +46,14 @@ inline bool isAbsolutePath(const char* path) return false; } - if (isPathSeparator(path[0])) + if (isNativePathSeparator(path[0])) { return true; } #ifdef _WIN32 const bool hasDriveLetter = (path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z'); - if (hasDriveLetter && path[1] == ':' && isPathSeparator(path[2])) + if (hasDriveLetter && path[1] == ':' && isNativePathSeparator(path[2])) { return true; } @@ -55,6 +62,32 @@ inline bool isAbsolutePath(const char* path) return false; } +inline char getNativePathSeparator() +{ +#ifdef _WIN32 + return '\\'; +#else + return '/'; +#endif +} + +inline const char* getLastPathSeparator(const char* path) +{ + return path ? maxPtr(strrchr(path, '/'), strrchr(path, '\\')) : nullptr; +} + +inline const wchar_t* getLastPathSeparator(const wchar_t* path) +{ + return path ? maxPtr(wcsrchr(path, L'/'), wcsrchr(path, L'\\')) : nullptr; +} + +// Returns the whole path when it contains no separator +inline const char* getFileName(const char* path) +{ + const char* lastSeparator = getLastPathSeparator(path); + return lastSeparator ? lastSeparator + 1 : path; +} + inline const char* getExtension(const char* path) { const char* lastDot = strrchr(path, '.'); @@ -64,7 +97,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) @@ -84,7 +117,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)