Skip to content

Commit 5445eb0

Browse files
committed
bugfix(file): Case-correct the leading component of a relative path
Backported from bobtista/topic/trunk, which hit and fixed this on Linux on 29/07/2026. This branch forked before that and never picked it up. fixFilenameFromWindowsPath copied the first component of a relative path through verbatim before starting its case-insensitive traversal, so a mis-cased leading directory could never be repaired. SidesList spells the skirmish scripts path "data\Scripts\SkirmishScripts.scb" with a lowercase d while it is "Data" on disk, so on a case-sensitive filesystem the open failed. The skirmish team records are cleared immediately before that parse, so they stayed empty. Player::initFromDict renames a skirmish AI to Skirmish<Faction><StartIndex>, deletes the teams owned by its original player<N> name, then copies its replacement teams out of those records - so the AI ended up with no teams at all. setDefaultTeam found nothing (its assert is compiled out of a release build), the AI's default team stayed null, and its starting Command Center and units were created onto no team of its own. hasAnyObjects() was therefore false, the AI counted as defeated on the first frame, and the human won instantly. Windows, macOS (case-insensitive APFS) and WSL over /mnt/c all hid this. It reproduces on wasm, where MEMFS is case-sensitive, in this port and in the vibecode port alike - confirmed by making the lowercase path resolve at runtime and watching the same binary play normally. This reverts the path-literal spelling fix that preceded it, so the file system layer stays the single fix and this branch matches trunk exactly.
1 parent 665f0b7 commit 5445eb0

3 files changed

Lines changed: 12 additions & 23 deletions

File tree

Core/GameEngineDevice/Source/StdDevice/Common/StdLocalFileSystem.cpp

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,6 @@ static std::filesystem::path fixFilenameFromWindowsPath(const Char *filename, In
134134
for (auto p : path)
135135
{
136136
std::filesystem::path pathFixedPart;
137-
if (pathCurrent.empty())
138-
{
139-
// Load the first part of the path
140-
pathFixed /= p;
141-
pathCurrent /= p;
142-
continue;
143-
}
144137

145138
if (std::filesystem::exists(pathCurrent / p, ec))
146139
{
@@ -152,8 +145,16 @@ static std::filesystem::path fixFilenameFromWindowsPath(const Char *filename, In
152145
}
153146
else
154147
{
155-
// Check if the subpath exists using case-insensitive comparison
156-
for (auto& entry : std::filesystem::directory_iterator(pathFixed, ec))
148+
// Check if the subpath exists using case-insensitive comparison.
149+
// TheSuperHackers @bugfix bobtista 29/07/2026 Scan the working
150+
// directory for the leading component of a relative path. It used to
151+
// be copied through verbatim, so on a case-sensitive filesystem a
152+
// literal such as "data\Scripts\SkirmishScripts.scb" never matched an
153+
// on-disk "Data" and the open failed, leaving skirmish games with no
154+
// scripts and no teams on Linux. Windows and macOS hid the bug behind
155+
// a case-insensitive filesystem.
156+
const std::filesystem::path scanDir = pathFixed.empty() ? std::filesystem::path(".") : pathFixed;
157+
for (auto& entry : std::filesystem::directory_iterator(scanDir, ec))
157158
{
158159
if (strcasecmp(entry.path().filename().string().c_str(), p.string().c_str()) == 0)
159160
{

Generals/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -507,13 +507,7 @@ void SidesList::prepareForMP_or_Skirmish()
507507
}
508508
}
509509
if (!gotScripts) {
510-
// TheSuperHackers @bugfix 31/07/2026 Spell the directory "Data", the way it is on disk
511-
// and the way every other path literal in the engine spells it. Windows does not care,
512-
// but on a case-sensitive filesystem this open failed, and because the skirmish team
513-
// records are cleared just above, the skirmish AI was left with no teams at all. Its
514-
// default team then never resolved, its starting Command Center and units belonged to
515-
// no team of its own, and it counted as defeated on the first frame - an instant win.
516-
AsciiString path = "Data\\Scripts\\SkirmishScripts.scb";
510+
AsciiString path = "data\\Scripts\\SkirmishScripts.scb";
517511
DEBUG_LOG(("Skirmish map using standard scripts"));
518512
m_skirmishTeamrec.clear();
519513
CachedFileInputStream theInputStream;

GeneralsMD/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -526,13 +526,7 @@ void SidesList::prepareForMP_or_Skirmish()
526526
}
527527
}
528528
if (!gotScripts) {
529-
// TheSuperHackers @bugfix 31/07/2026 Spell the directory "Data", the way it is on disk
530-
// and the way every other path literal in the engine spells it. Windows does not care,
531-
// but on a case-sensitive filesystem this open failed, and because the skirmish team
532-
// records are cleared just above, the skirmish AI was left with no teams at all. Its
533-
// default team then never resolved, its starting Command Center and units belonged to
534-
// no team of its own, and it counted as defeated on the first frame - an instant win.
535-
AsciiString path = "Data\\Scripts\\SkirmishScripts.scb";
529+
AsciiString path = "data\\Scripts\\SkirmishScripts.scb";
536530
DEBUG_LOG(("Skirmish map using standard scripts"));
537531
m_skirmishTeamrec.clear();
538532
CachedFileInputStream theInputStream;

0 commit comments

Comments
 (0)