From 54957e2eaae45025d50b2b4ff1fe54ad350d49ca 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 | 36 +++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/Core/Libraries/Include/Lib/PathUtil.h b/Core/Libraries/Include/Lib/PathUtil.h index 683f6fee130..ad3adc557ea 100644 --- a/Core/Libraries/Include/Lib/PathUtil.h +++ b/Core/Libraries/Include/Lib/PathUtil.h @@ -55,6 +55,38 @@ inline bool isAbsolutePath(const char* path) return false; } +inline char getNativePathSeparator() +{ +#ifdef _WIN32 + return '\\'; +#else + return '/'; +#endif +} + +inline Bool isAnyPathSeparator(char c) +{ + return c == '/' || c == '\\'; +} + +inline const char* getLastPathSeparator(const char* path) +{ + return maxPtr(strrchr(path, '/'), strrchr(path, '\\')); +} + +inline const wchar_t* getLastPathSeparator(const wchar_t* path) +{ + return maxPtr(wcsrchr(path, L'/'), wcsrchr(path, L'\\')); +} + +// 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 +96,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 +116,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 57b1ba7c86d3de7cf2236df3f0960bcb0389d6e9 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Mon, 14 Sep 2026 16:28:01 -0500 Subject: [PATCH 2/4] refactor(lib): Name the separator predicates for what they match and share the append helper --- Core/GameEngine/Include/Common/FileSystem.h | 2 ++ .../Source/Common/System/FileSystem.cpp | 11 +++++++++ Core/Libraries/Include/Lib/PathUtil.h | 23 ++++++++++--------- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Core/GameEngine/Include/Common/FileSystem.h b/Core/GameEngine/Include/Common/FileSystem.h index 2aaa30a61f1..a91c86bb2bd 100644 --- a/Core/GameEngine/Include/Common/FileSystem.h +++ b/Core/GameEngine/Include/Common/FileSystem.h @@ -163,6 +163,8 @@ class FileSystem : public SubsystemInterface static bool removeExtension(AsciiString& path); static bool removeExtension(UnicodeString& path); + static void appendPathSeparator(AsciiString& path); ///< appends the platform separator, unless the path is empty or already ends with either separator. + protected: #if ENABLE_FILESYSTEM_EXISTENCE_CACHE struct FileExistData diff --git a/Core/GameEngine/Source/Common/System/FileSystem.cpp b/Core/GameEngine/Source/Common/System/FileSystem.cpp index b8e4c4695b6..654e7575500 100644 --- a/Core/GameEngine/Source/Common/System/FileSystem.cpp +++ b/Core/GameEngine/Source/Common/System/FileSystem.cpp @@ -395,6 +395,17 @@ bool FileSystem::removeExtension(AsciiString& path) return false; } +//============================================================================ +// FileSystem::appendPathSeparator +//============================================================================ +void FileSystem::appendPathSeparator(AsciiString& path) +{ + if (path.isNotEmpty() && !isPathSeparator(path.getCharAt(path.getLength() - 1))) + { + path.concat(getNativePathSeparator()); + } +} + //============================================================================ // FileSystem::removeExtension - Unicode handling variant //============================================================================ diff --git a/Core/Libraries/Include/Lib/PathUtil.h b/Core/Libraries/Include/Lib/PathUtil.h index ad3adc557ea..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; } @@ -64,26 +71,20 @@ inline char getNativePathSeparator() #endif } -inline Bool isAnyPathSeparator(char c) -{ - return c == '/' || c == '\\'; -} - inline const char* getLastPathSeparator(const char* path) { - return maxPtr(strrchr(path, '/'), strrchr(path, '\\')); + return path ? maxPtr(strrchr(path, '/'), strrchr(path, '\\')) : nullptr; } inline const wchar_t* getLastPathSeparator(const wchar_t* path) { - return maxPtr(wcsrchr(path, L'/'), wcsrchr(path, L'\\')); + 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; } From 3767abf954b4348d162b1d999db22219827f1d9d Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Tue, 11 Aug 2026 11:20:07 -0400 Subject: [PATCH 3/4] bugfix(network): Build map transfer paths with the platform separator --- .../Source/GameNetwork/FileTransfer.cpp | 60 ++++++++----------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/FileTransfer.cpp b/Core/GameEngine/Source/GameNetwork/FileTransfer.cpp index 5d9cc7dfe64..b137c4c0c35 100644 --- a/Core/GameEngine/Source/GameNetwork/FileTransfer.cpp +++ b/Core/GameEngine/Source/GameNetwork/FileTransfer.cpp @@ -34,6 +34,7 @@ #include "GameClient/Shell.h" #include "GameNetwork/FileTransfer.h" #include "GameNetwork/networkutil.h" +#include "Lib/PathUtil.h" //------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------- @@ -137,7 +138,7 @@ static Bool doFileTransfer( AsciiString filename, MapTransferLoadScreen *ls, Int AsciiString GetBasePathFromPath( AsciiString path ) { - const char *s = path.reverseFind('\\'); + const char *s = getLastPathSeparator(path.str()); if (s) { Int len = s - path.str(); @@ -153,10 +154,7 @@ AsciiString GetBasePathFromPath( AsciiString path ) AsciiString GetFileFromPath( AsciiString path ) { - const char *s = path.reverseFind('\\'); - if (s) - return s+1; - return path; + return getFileName(path.str()); } AsciiString GetExtensionFromFile( AsciiString fname ) @@ -183,59 +181,51 @@ AsciiString GetBaseFileFromFile( AsciiString fname ) return AsciiString::TheEmptyString; } +static AsciiString GetFileInMapDirectory( const AsciiString &mapPath, const AsciiString &filename ) +{ + AsciiString base = GetBasePathFromPath(mapPath); + if (base.isEmpty()) + { + return filename; + } + + const char *separator = getLastPathSeparator(mapPath.str()); + AsciiString path; + path.format("%s%c%s", base.str(), separator ? *separator : getNativePathSeparator(), filename.str()); + return path; +} + AsciiString GetPreviewFromMap( AsciiString path ) { AsciiString fname = GetBaseFileFromFile(GetFileFromPath(path)); - AsciiString base = GetBasePathFromPath(path); - - AsciiString out; - out.format("%s\\%s.tga", base.str(), fname.str()); - return out; + AsciiString preview; + preview.format("%s.tga", fname.str()); + return GetFileInMapDirectory(path, preview); } AsciiString GetINIFromMap( AsciiString path ) { - AsciiString base = GetBasePathFromPath(path); - - AsciiString out; - out.format("%s\\map.ini", base.str()); - return out; + return GetFileInMapDirectory(path, "map.ini"); } AsciiString GetStrFileFromMap( AsciiString path ) { - AsciiString base = GetBasePathFromPath(path); - - AsciiString out; - out.format("%s\\map.str", base.str()); - return out; + return GetFileInMapDirectory(path, "map.str"); } AsciiString GetSoloINIFromMap( AsciiString path ) { - AsciiString base = GetBasePathFromPath(path); - - AsciiString out; - out.format("%s\\solo.ini", base.str()); - return out; + return GetFileInMapDirectory(path, "solo.ini"); } AsciiString GetAssetUsageFromMap( AsciiString path ) { - AsciiString base = GetBasePathFromPath(path); - - AsciiString out; - out.format("%s\\assetusage.txt", base.str()); - return out; + return GetFileInMapDirectory(path, "assetusage.txt"); } AsciiString GetReadmeFromMap( AsciiString path ) { - AsciiString base = GetBasePathFromPath(path); - - AsciiString out; - out.format("%s\\readme.txt", base.str()); - return out; + return GetFileInMapDirectory(path, "readme.txt"); } //------------------------------------------------------------------------------------- From f51efd04daad9c1162dcd9f48708c6584f097201 Mon Sep 17 00:00:00 2001 From: Bobby Battista Date: Mon, 14 Sep 2026 20:36:52 -0400 Subject: [PATCH 4/4] refactor(network): Simplify map transfer path construction --- .../Source/GameNetwork/FileTransfer.cpp | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/Core/GameEngine/Source/GameNetwork/FileTransfer.cpp b/Core/GameEngine/Source/GameNetwork/FileTransfer.cpp index b137c4c0c35..3f10ecb5e5b 100644 --- a/Core/GameEngine/Source/GameNetwork/FileTransfer.cpp +++ b/Core/GameEngine/Source/GameNetwork/FileTransfer.cpp @@ -30,6 +30,7 @@ #include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine +#include "Common/FileSystem.h" #include "GameClient/LoadScreen.h" #include "GameClient/Shell.h" #include "GameNetwork/FileTransfer.h" @@ -183,24 +184,18 @@ AsciiString GetBaseFileFromFile( AsciiString fname ) static AsciiString GetFileInMapDirectory( const AsciiString &mapPath, const AsciiString &filename ) { - AsciiString base = GetBasePathFromPath(mapPath); - if (base.isEmpty()) - { - return filename; - } - - const char *separator = getLastPathSeparator(mapPath.str()); + const char *file = getFileName(mapPath.str()); AsciiString path; - path.format("%s%c%s", base.str(), separator ? *separator : getNativePathSeparator(), filename.str()); + path.set(mapPath.str(), file - mapPath.str()); + path.concat(filename); return path; } AsciiString GetPreviewFromMap( AsciiString path ) { - AsciiString fname = GetBaseFileFromFile(GetFileFromPath(path)); - AsciiString preview; - preview.format("%s.tga", fname.str()); - return GetFileInMapDirectory(path, preview); + FileSystem::removeExtension(path); + path.concat(".tga"); + return path; } AsciiString GetINIFromMap( AsciiString path )