diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt
index 0f36ff63383..e50ba0d07a9 100644
--- a/Core/GameEngine/CMakeLists.txt
+++ b/Core/GameEngine/CMakeLists.txt
@@ -136,6 +136,7 @@ set(GAMEENGINE_SRC
Include/Common/version.h
# Include/Common/WellKnownKeys.h
Include/Common/WorkerProcess.h
+ Include/Common/WorkingDirectory.h
Include/Common/Xfer.h
Include/Common/XferCRC.h
Include/Common/XferDeepCRC.h
@@ -692,6 +693,7 @@ set(GAMEENGINE_SRC
Source/Common/UserPreferences.cpp
Source/Common/version.cpp
Source/Common/WorkerProcess.cpp
+ Source/Common/WorkingDirectory.cpp
Source/GameClient/ClientInstance.cpp
Source/GameClient/Color.cpp
Source/GameClient/Credits.cpp
diff --git a/Core/GameEngine/Include/Common/CommandLine.h b/Core/GameEngine/Include/Common/CommandLine.h
index 48e078dc3dd..e941c0130ba 100644
--- a/Core/GameEngine/Include/Common/CommandLine.h
+++ b/Core/GameEngine/Include/Common/CommandLine.h
@@ -32,6 +32,11 @@ class CommandLine
{
public:
+ // Parses startup flags and applies the process working directory.
static void parseCommandLineForStartup();
static void parseCommandLineForEngineInit();
+
+ // Returns true if command-line parsing consumed the zero-based argument index.
+ // The index excludes the executable name.
+ static bool wasCommandLineArgumentParsed(int argIndex);
};
diff --git a/Core/GameEngine/Include/Common/WorkingDirectory.h b/Core/GameEngine/Include/Common/WorkingDirectory.h
new file mode 100644
index 00000000000..0df55f0d84a
--- /dev/null
+++ b/Core/GameEngine/Include/Common/WorkingDirectory.h
@@ -0,0 +1,36 @@
+/*
+** Command & Conquer Generals Zero Hour(tm)
+** Copyright 2025 TheSuperHackers
+**
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published by
+** the Free Software Foundation, either version 3 of the License, or
+** (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program. If not, see .
+*/
+
+#pragma once
+
+#include "Lib/BaseType.h"
+
+namespace rts
+{
+
+// TheSuperHackers @feature 14/08/2026
+// Process working directory helpers. CommandLine::parseCommandLineForStartup()
+// applies these: default is the executable directory, -useCwd keeps the OS
+// directory, and -setCwd uses that path.
+
+Bool setCurrentDirectoryToExecutablePath();
+Bool setCurrentDirectoryToPath(const char *path);
+void keepCurrentDirectory();
+void setCurrentDirectoryToExecutablePathIfNotSet();
+
+} // namespace rts
diff --git a/Core/GameEngine/Source/Common/CommandLine.cpp b/Core/GameEngine/Source/Common/CommandLine.cpp
index 772830f0f67..3b0d9946fb6 100644
--- a/Core/GameEngine/Source/Common/CommandLine.cpp
+++ b/Core/GameEngine/Source/Common/CommandLine.cpp
@@ -28,6 +28,7 @@
#include "Common/ArchiveFileSystem.h"
#include "Common/CommandLine.h"
#include "Common/CRCDebug.h"
+#include "Common/WorkingDirectory.h"
#include "Common/LocalFileSystem.h"
#include "Common/Recorder.h"
#include "Common/version.h"
@@ -35,7 +36,6 @@
#include "GameClient/TerrainVisual.h" // for TERRAIN_LOD_MIN definition
#include "GameClient/GameText.h"
#include "GameNetwork/NetworkDefs.h"
-#include "WWLib/trim.h"
@@ -463,6 +463,28 @@ Int parseJobs(char *args[], int num)
return 1;
}
+Int parseUseCwd(char *[], int)
+{
+ // TheSuperHackers @feature 14/08/2026
+ // -useCwd keeps the OS working directory.
+ rts::keepCurrentDirectory();
+ return 1;
+}
+
+Int parseSetCwd(char *args[], int num)
+{
+ // TheSuperHackers @bugfix CryoTheRenegade 29/08/2026
+ // -setCwd overrides the working directory.
+ if (num <= 1 || args[1] == nullptr || args[1][0] == '-' || args[1][0] == '/')
+ {
+ rts::setCurrentDirectoryToExecutablePath();
+ return 1;
+ }
+ if (!rts::setCurrentDirectoryToPath(args[1]))
+ rts::setCurrentDirectoryToExecutablePath();
+ return 2;
+}
+
Int parseXRes(char *args[], int num)
{
if (num > 1)
@@ -1141,6 +1163,12 @@ static CommandLineParam paramsForStartup[] =
// (If you have 4 cores, call it with -jobs 4)
// If you do not call this, all replays will be simulated in sequence in the same process.
{ "-jobs", parseJobs },
+
+ // TheSuperHackers @feature 14/08/2026
+ // Use the current working directory as provided by the OS, or an explicit path.
+ // Without either flag the working directory is forced to the executable directory.
+ { "-setCwd", parseSetCwd },
+ { "-useCwd", parseUseCwd },
};
// These Params are parsed during Engine Init before INI data is loaded
@@ -1309,71 +1337,12 @@ static CommandLineParam paramsForEngineInit[] =
};
-char *nextParam(char *newSource, const char *seps)
+static void parseCommandLine(const CommandLineParam* params, int numParams, std::vector *parsedArguments = nullptr)
{
- static char *source = nullptr;
- if (newSource)
- {
- source = newSource;
- }
- if (!source)
- {
- return nullptr;
- }
-
- // find first separator
- char *first = source;//strpbrk(source, seps);
- if (first)
- {
- // go past separator
- char *firstSep = strpbrk(first, seps);
- char firstChar[2] = {0,0};
- if (firstSep == first)
- {
- firstChar[0] = *first;
- while (*first == firstChar[0]) first++;
- }
-
- // find end
- char *end;
- if (firstChar[0])
- end = strpbrk(first, firstChar);
- else
- end = strpbrk(first, seps);
-
- // trim string & save next start pos
- if (end)
- {
- source = end+1;
- *end = 0;
-
- if (!*source)
- source = nullptr;
- }
- else
- {
- source = nullptr;
- }
-
- if (first && !*first)
- first = nullptr;
- }
-
- return first;
-}
-
-static void parseCommandLine(const CommandLineParam* params, int numParams)
-{
- std::vector argv;
-
- std::string cmdLine = GetCommandLineA();
- char *token = nextParam(&cmdLine[0], "\" ");
- while (token != nullptr)
- {
- argv.push_back(strtrim(token));
- token = nextParam(nullptr, "\" ");
- }
- int argc = argv.size();
+ const int argc = __argc;
+ char **argv = __argv;
+ if (parsedArguments != nullptr && parsedArguments->size() < static_cast(argc > 0 ? argc - 1 : 0))
+ parsedArguments->resize(argc - 1, FALSE);
int arg = 1;
@@ -1407,7 +1376,14 @@ static void parseCommandLine(const CommandLineParam* params, int numParams)
continue;
if (strnicmp(argv[arg], params[param].name, len) == 0)
{
- arg += params[param].func(&argv[0]+arg, argc-arg);
+ const int parsedArg = arg;
+ const int parsedArgCount = params[param].func(&argv[0]+arg, argc-arg);
+ if (parsedArguments != nullptr)
+ {
+ for (int i = 0; i < parsedArgCount && parsedArg + i < argc; ++i)
+ (*parsedArguments)[parsedArg + i - 1] = TRUE;
+ }
+ arg += parsedArgCount;
found = true;
break;
}
@@ -1419,6 +1395,15 @@ static void parseCommandLine(const CommandLineParam* params, int numParams)
}
}
+bool CommandLine::wasCommandLineArgumentParsed(int argIndex)
+{
+ if (TheGlobalData == nullptr)
+ return false;
+
+ const BoolVector &parsedArguments = TheGlobalData->m_commandLineData.m_parsedArguments;
+ return argIndex >= 0 && argIndex < static_cast(parsedArguments.size()) && parsedArguments[argIndex];
+}
+
void createGlobalData()
{
if (TheGlobalData == nullptr)
@@ -1435,7 +1420,10 @@ void CommandLine::parseCommandLineForStartup()
return;
TheWritableGlobalData->m_commandLineData.m_hasParsedCommandLineForStartup = true;
- parseCommandLine(paramsForStartup, ARRAY_SIZE(paramsForStartup));
+ parseCommandLine(paramsForStartup, ARRAY_SIZE(paramsForStartup),
+ &TheWritableGlobalData->m_commandLineData.m_parsedArguments);
+
+ rts::setCurrentDirectoryToExecutablePathIfNotSet();
}
void CommandLine::parseCommandLineForEngineInit()
@@ -1448,5 +1436,6 @@ void CommandLine::parseCommandLineForEngineInit()
("parseCommandLineForEngineInit is expected to be called once only\n"));
TheWritableGlobalData->m_commandLineData.m_hasParsedCommandLineForEngineInit = true;
- parseCommandLine(paramsForEngineInit, ARRAY_SIZE(paramsForEngineInit));
+ parseCommandLine(paramsForEngineInit, ARRAY_SIZE(paramsForEngineInit),
+ &TheWritableGlobalData->m_commandLineData.m_parsedArguments);
}
diff --git a/Core/GameEngine/Source/Common/WorkingDirectory.cpp b/Core/GameEngine/Source/Common/WorkingDirectory.cpp
new file mode 100644
index 00000000000..703b4d02086
--- /dev/null
+++ b/Core/GameEngine/Source/Common/WorkingDirectory.cpp
@@ -0,0 +1,79 @@
+/*
+** Command & Conquer Generals Zero Hour(tm)
+** Copyright 2025 TheSuperHackers
+**
+** This program is free software: you can redistribute it and/or modify
+** it under the terms of the GNU General Public License as published by
+** the Free Software Foundation, either version 3 of the License, or
+** (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+** GNU General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program. If not, see .
+*/
+
+#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine
+
+#include "Common/WorkingDirectory.h"
+
+namespace rts
+{
+
+static Bool s_workingDirectorySet = FALSE;
+
+Bool setCurrentDirectoryToExecutablePath()
+{
+ Char buffer[_MAX_PATH];
+ const DWORD len = GetModuleFileName(nullptr, buffer, ARRAY_SIZE(buffer));
+ if (len == 0 || len >= ARRAY_SIZE(buffer))
+ {
+ DEBUG_LOG(("Failed to get executable path for working directory (error %d)", GetLastError()));
+ return FALSE;
+ }
+
+ if (Char *pEnd = strrchr(buffer, '\\'))
+ {
+ *pEnd = 0;
+ }
+
+ if (::SetCurrentDirectory(buffer) == 0)
+ {
+ DEBUG_LOG(("Failed to set working directory to executable path '%s' (error %d)", buffer, GetLastError()));
+ return FALSE;
+ }
+
+ s_workingDirectorySet = TRUE;
+ return TRUE;
+}
+
+Bool setCurrentDirectoryToPath(const char *path)
+{
+ if (path == nullptr || path[0] == '\0')
+ return FALSE;
+
+ if (::SetCurrentDirectory(path) == 0)
+ {
+ DEBUG_LOG(("Failed to set working directory to '%s' (error %d)", path, GetLastError()));
+ return FALSE;
+ }
+
+ s_workingDirectorySet = TRUE;
+ return TRUE;
+}
+
+void keepCurrentDirectory()
+{
+ s_workingDirectorySet = TRUE;
+}
+
+void setCurrentDirectoryToExecutablePathIfNotSet()
+{
+ if (!s_workingDirectorySet)
+ setCurrentDirectoryToExecutablePath();
+}
+
+} // namespace rts
diff --git a/Core/Tools/MapCacheBuilder/Source/WinMain.cpp b/Core/Tools/MapCacheBuilder/Source/WinMain.cpp
index 134f1465980..ac5aeb2e9b0 100644
--- a/Core/Tools/MapCacheBuilder/Source/WinMain.cpp
+++ b/Core/Tools/MapCacheBuilder/Source/WinMain.cpp
@@ -43,6 +43,7 @@
// USER INCLUDES //////////////////////////////////////////////////////////////
#include "Lib/BaseType.h"
+#include "Common/CommandLine.h"
#include "Common/Debug.h"
#include "Common/GameMemory.h"
#include "Common/GlobalData.h"
@@ -102,7 +103,6 @@
#include "Win32Device/GameClient/Win32Mouse.h"
#include "Win32Device/Common/Win32LocalFileSystem.h"
#include "Win32Device/Common/Win32BIGFileSystem.h"
-#include "WWLib/trim.h"
// DEFINES ////////////////////////////////////////////////////////////////////
@@ -141,65 +141,6 @@ const Char *g_csfFile = "data\\%s\\Generals.csf";
// PRIVATE FUNCTIONS //////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
-static char *nextParam(char *newSource, const char *seps)
-{
- static char *source = nullptr;
- if (newSource)
- {
- source = newSource;
- }
- if (!source)
- {
- return nullptr;
- }
-
- // find first separator
- char *first = source;//strpbrk(source, seps);
- if (first)
- {
- // go past initial spaces
- char *firstNonSpace = first;
- while (*firstNonSpace == ' ')
- ++firstNonSpace;
- first = firstNonSpace;
-
- // go past separator
- char *firstSep = strpbrk(first, seps);
- char firstChar[2] = {0,0};
- if (firstSep == first)
- {
- firstChar[0] = *first;
- while (*first == firstChar[0]) first++;
- }
-
- // find end
- char *end;
- if (firstChar[0])
- end = strpbrk(first, firstChar);
- else
- end = strpbrk(first, seps);
-
- // trim string & save next start pos
- if (end)
- {
- source = end+1;
- *end = 0;
-
- if (!*source)
- source = nullptr;
- }
- else
- {
- source = nullptr;
- }
-
- if (first && !*first)
- first = nullptr;
- }
-
- return first;
-}
-
///////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
@@ -220,26 +161,17 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
// save application instance
ApplicationHInstance = hInstance;
+ CommandLine::parseCommandLineForStartup();
- // Set the current directory to the app directory.
- char buf[_MAX_PATH];
- GetModuleFileName(nullptr, buf, sizeof(buf));
- if (char *pEnd = strrchr(buf, '\\')) {
- *pEnd = 0;
- }
- ::SetCurrentDirectory(buf);
-
- /*
- ** Convert WinMain arguments to simple main argc and argv
- */
+ // Collect CRT arguments not handled during startup parsing.
std::list argvSet;
- char *token;
- token = nextParam(lpCmdLine, "\" ");
- while (token != nullptr) {
- char * str = strtrim(token);
- argvSet.push_back(str);
- DEBUG_LOG(("Adding '%s'", str));
- token = nextParam(nullptr, "\" ");
+ for (int arg = 1; arg < __argc; ++arg)
+ {
+ if (!CommandLine::wasCommandLineArgumentParsed(arg - 1))
+ {
+ argvSet.push_back(__argv[arg]);
+ DEBUG_LOG(("Adding '%s'", __argv[arg]));
+ }
}
// not part of the subsystem list, because it should normally never be reset!
@@ -251,7 +183,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
initSubsystem(TheLocalFileSystem, (LocalFileSystem*)new Win32LocalFileSystem);
initSubsystem(TheArchiveFileSystem, (ArchiveFileSystem*)new Win32BIGFileSystem);
INI ini;
- initSubsystem(TheWritableGlobalData, new GlobalData(), "Data\\INI\\Default\\GameData", "Data\\INI\\GameData");
+ initSubsystem(TheWritableGlobalData, TheWritableGlobalData, "Data\\INI\\Default\\GameData", "Data\\INI\\GameData");
initSubsystem(TheGameText, CreateGameTextInterface());
initSubsystem(TheScienceStore, new ScienceStore(), "Data\\INI\\Default\\Science", "Data\\INI\\Science");
initSubsystem(TheMultiplayerSettings, new MultiplayerSettings(), "Data\\INI\\Default\\Multiplayer", "Data\\INI\\Multiplayer");
diff --git a/Generals/Code/GameEngine/Include/Common/GlobalData.h b/Generals/Code/GameEngine/Include/Common/GlobalData.h
index e631654250d..f20a9c2f35f 100644
--- a/Generals/Code/GameEngine/Include/Common/GlobalData.h
+++ b/Generals/Code/GameEngine/Include/Common/GlobalData.h
@@ -54,6 +54,8 @@ constexpr const Int MAX_GLOBAL_LIGHTS = 3;
constexpr const Int SIMULATE_REPLAYS_SEQUENTIAL = -1;
//-------------------------------------------------------------------------------------------------
+// Command-line parsing state is stored here instead of in CommandLine because
+// the parsing result belongs to the GlobalData instance created during startup.
class CommandLineData
{
friend class CommandLine;
@@ -66,6 +68,7 @@ class CommandLineData
Bool m_hasParsedCommandLineForStartup;
Bool m_hasParsedCommandLineForEngineInit;
+ BoolVector m_parsedArguments;
};
//-------------------------------------------------------------------------------------------------
diff --git a/Generals/Code/Main/WinMain.cpp b/Generals/Code/Main/WinMain.cpp
index c8e9bb9961d..e5ee8b9f254 100644
--- a/Generals/Code/Main/WinMain.cpp
+++ b/Generals/Code/Main/WinMain.cpp
@@ -817,14 +817,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
// initialize the memory manager early
initMemoryManager();
- /// @todo remove this force set of working directory later
- Char buffer[ _MAX_PATH ];
- GetModuleFileName( nullptr, buffer, sizeof( buffer ) );
- if (Char *pEnd = strrchr(buffer, '\\'))
- {
- *pEnd = 0;
- }
- ::SetCurrentDirectory(buffer);
+ CommandLine::parseCommandLineForStartup();
#ifdef RTS_DEBUG
@@ -845,8 +838,6 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
// Force to be loaded from a file, not a resource so same exe can be used in germany and retail.
gLoadScreenBitmap = (HBITMAP)LoadImage(hInstance, "Install_Final.bmp", IMAGE_BITMAP, 0, 0, LR_SHARED|LR_LOADFROMFILE);
- CommandLine::parseCommandLineForStartup();
-
#ifdef RTS_ENABLE_CRASHDUMP
// Initialize minidump facilities - requires TheGlobalData so performed after parseCommandLineForStartup
MiniDumper::initMiniDumper(TheGlobalData->getPath_UserData());
diff --git a/Generals/Code/Tools/GUIEdit/Source/GUIEdit.cpp b/Generals/Code/Tools/GUIEdit/Source/GUIEdit.cpp
index 0963b8a53c7..0109ea9bc60 100644
--- a/Generals/Code/Tools/GUIEdit/Source/GUIEdit.cpp
+++ b/Generals/Code/Tools/GUIEdit/Source/GUIEdit.cpp
@@ -508,8 +508,8 @@ void GUIEdit::init()
// Game engine specific initializations -------------------------------------
//---------------------------------------------------------------------------
- // create the global data
- TheWritableGlobalData = new GlobalData;
+ // GlobalData is created by CommandLine::parseCommandLineForStartup().
+ DEBUG_ASSERTCRASH(TheWritableGlobalData, ("TheWritableGlobalData expected to be created"));
TheWritableGlobalData->init();
// TheSuperHackers @info global language relies on global data being initialized
diff --git a/Generals/Code/Tools/GUIEdit/Source/WinMain.cpp b/Generals/Code/Tools/GUIEdit/Source/WinMain.cpp
index daf1402d74c..1b6f25bdf2a 100644
--- a/Generals/Code/Tools/GUIEdit/Source/WinMain.cpp
+++ b/Generals/Code/Tools/GUIEdit/Source/WinMain.cpp
@@ -49,6 +49,7 @@
#include
// USER INCLUDES //////////////////////////////////////////////////////////////
+#include "Common/CommandLine.h"
#include "Common/Debug.h"
#include "Common/FramePacer.h"
#include "Common/GameMemory.h"
@@ -184,18 +185,11 @@ Int APIENTRY WinMain(HINSTANCE hInstance,
HACCEL hAccelTable;
Bool quit = FALSE;
- /// @todo remove this force set of working directory later
- Char buffer[ _MAX_PATH ];
- GetModuleFileName( nullptr, buffer, sizeof( buffer ) );
- if (Char *pEnd = strrchr(buffer, '\\'))
- {
- *pEnd = 0;
- }
- ::SetCurrentDirectory(buffer);
-
// initialize the memory manager early
initMemoryManager();
+ CommandLine::parseCommandLineForStartup();
+
// register a class for our window with the OS
registerClass( hInstance );
diff --git a/Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp b/Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp
index c122151259d..066c697c274 100644
--- a/Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp
+++ b/Generals/Code/Tools/WorldBuilder/src/WorldBuilder.cpp
@@ -32,6 +32,7 @@
//#include
#include "W3DDevice/GameClient/W3DFileSystem.h"
+#include "Common/CommandLine.h"
#include "Common/FramePacer.h"
#include "Common/GlobalData.h"
#include "WHeightMapEdit.h"
@@ -151,8 +152,31 @@ FileClass * WB_W3DFileSystem::Get_File( char const *filename )
return pFile;
}
+/////////////////////////////////////////////////////////////////////////////
+// MFC parses the command line again to select a document to open. Skip the
+// arguments already handled by the startup parser so option values are not
+// mistaken for map filenames.
+
+class WBCommandLineInfo : public CCommandLineInfo
+{
+public:
+ WBCommandLineInfo() : m_argIndex(0) {}
+ virtual void ParseParam(const TCHAR* pszParam, BOOL bFlag, BOOL bLast) override
+ {
+ if (CommandLine::wasCommandLineArgumentParsed(m_argIndex++))
+ {
+ // MFC uses bLast to finalize its shell command, even when the final argument is skipped.
+ ParseLast(bLast);
+ return;
+ }
+ CCommandLineInfo::ParseParam(pszParam, bFlag, bLast);
+ }
+
+private:
+ int m_argIndex;
+};
/////////////////////////////////////////////////////////////////////////////
// The one and only CWorldBuilderApp object
@@ -277,6 +301,8 @@ BOOL CWorldBuilderApp::InitInstance()
// initialize the memory manager early
initMemoryManager();
+ CommandLine::parseCommandLineForStartup();
+
DEBUG_LOG(("starting Worldbuilder."));
#ifdef RTS_DEBUG
DEBUG_LOG(("RTS_DEBUG defined."));
@@ -305,14 +331,6 @@ BOOL CWorldBuilderApp::InitInstance()
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
- // Set the current directory to the app directory.
- char buf[_MAX_PATH];
- GetModuleFileName(nullptr, buf, sizeof(buf));
- if (char *pEnd = strrchr(buf, '\\')) {
- *pEnd = 0;
- }
- ::SetCurrentDirectory(buf);
-
TheFileSystem = new FileSystem;
initSubsystem(TheLocalFileSystem, (LocalFileSystem*)new Win32LocalFileSystem);
@@ -324,7 +342,8 @@ BOOL CWorldBuilderApp::InitInstance()
INI ini;
- initSubsystem(TheWritableGlobalData, new GlobalData(), "Data\\INI\\Default\\GameData", "Data\\INI\\GameData");
+ DEBUG_ASSERTCRASH(TheWritableGlobalData, ("TheWritableGlobalData expected to be created"));
+ initSubsystem(TheWritableGlobalData, TheWritableGlobalData, "Data\\INI\\Default\\GameData", "Data\\INI\\GameData");
TheFramePacer = new FramePacer();
@@ -336,6 +355,7 @@ BOOL CWorldBuilderApp::InitInstance()
TheWritableGlobalData->m_debugIgnoreAsserts = true;
#endif
+ char buf[_MAX_PATH];
#if 1
// srj sez: put INI into our user data folder, not the ap dir
free((void*)m_pszProfileName);
@@ -427,7 +447,7 @@ BOOL CWorldBuilderApp::InitInstance()
#endif
// Parse command line for standard shell commands, DDE, file open
- CCommandLineInfo cmdInfo;
+ WBCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Dispatch commands specified on the command line
diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h
index 7f484111672..fbf8f97f1ae 100644
--- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h
+++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h
@@ -54,6 +54,8 @@ constexpr const Int MAX_GLOBAL_LIGHTS = 3;
constexpr const Int SIMULATE_REPLAYS_SEQUENTIAL = -1;
//-------------------------------------------------------------------------------------------------
+// Command-line parsing state is stored here instead of in CommandLine because
+// the parsing result belongs to the GlobalData instance created during startup.
class CommandLineData
{
friend class CommandLine;
@@ -66,6 +68,7 @@ class CommandLineData
Bool m_hasParsedCommandLineForStartup;
Bool m_hasParsedCommandLineForEngineInit;
+ BoolVector m_parsedArguments;
};
//-------------------------------------------------------------------------------------------------
diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp
index 0d37cab5933..9e53037e441 100644
--- a/GeneralsMD/Code/Main/WinMain.cpp
+++ b/GeneralsMD/Code/Main/WinMain.cpp
@@ -824,14 +824,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
// initialize the memory manager early
initMemoryManager();
- /// @todo remove this force set of working directory later
- Char buffer[ _MAX_PATH ];
- GetModuleFileName( nullptr, buffer, sizeof( buffer ) );
- if (Char *pEnd = strrchr(buffer, '\\'))
- {
- *pEnd = 0;
- }
- ::SetCurrentDirectory(buffer);
+ CommandLine::parseCommandLineForStartup();
#ifdef RTS_DEBUG
@@ -872,7 +865,6 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
gLoadScreenBitmap = (HBITMAP)LoadImage(hInstance, "Install_Final.bmp", IMAGE_BITMAP, 0, 0, LR_SHARED|LR_LOADFROMFILE);
#endif
- CommandLine::parseCommandLineForStartup();
#ifdef RTS_ENABLE_CRASHDUMP
// Initialize minidump facilities - requires TheGlobalData so performed after parseCommandLineForStartup
MiniDumper::initMiniDumper(TheGlobalData->getPath_UserData());
diff --git a/GeneralsMD/Code/Tools/GUIEdit/Source/GUIEdit.cpp b/GeneralsMD/Code/Tools/GUIEdit/Source/GUIEdit.cpp
index 23b315cc5f0..0647552baae 100644
--- a/GeneralsMD/Code/Tools/GUIEdit/Source/GUIEdit.cpp
+++ b/GeneralsMD/Code/Tools/GUIEdit/Source/GUIEdit.cpp
@@ -508,8 +508,8 @@ void GUIEdit::init()
// Game engine specific initializations -------------------------------------
//---------------------------------------------------------------------------
- // create the global data
- TheWritableGlobalData = new GlobalData;
+ // GlobalData is created by CommandLine::parseCommandLineForStartup().
+ DEBUG_ASSERTCRASH(TheWritableGlobalData, ("TheWritableGlobalData expected to be created"));
TheWritableGlobalData->init();
// TheSuperHackers @info global language relies on global data being initialized
diff --git a/GeneralsMD/Code/Tools/GUIEdit/Source/WinMain.cpp b/GeneralsMD/Code/Tools/GUIEdit/Source/WinMain.cpp
index 493d5aecc4e..84ed8192793 100644
--- a/GeneralsMD/Code/Tools/GUIEdit/Source/WinMain.cpp
+++ b/GeneralsMD/Code/Tools/GUIEdit/Source/WinMain.cpp
@@ -49,6 +49,7 @@
#include
// USER INCLUDES //////////////////////////////////////////////////////////////
+#include "Common/CommandLine.h"
#include "Common/Debug.h"
#include "Common/FramePacer.h"
#include "Common/GameMemory.h"
@@ -184,18 +185,11 @@ Int APIENTRY WinMain(HINSTANCE hInstance,
HACCEL hAccelTable;
Bool quit = FALSE;
- /// @todo remove this force set of working directory later
- Char buffer[ _MAX_PATH ];
- GetModuleFileName( nullptr, buffer, sizeof( buffer ) );
- if (Char *pEnd = strrchr(buffer, '\\'))
- {
- *pEnd = 0;
- }
- ::SetCurrentDirectory(buffer);
-
// initialize the memory manager early
initMemoryManager();
+ CommandLine::parseCommandLineForStartup();
+
// register a class for our window with the OS
registerClass( hInstance );
diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp
index 5f19126638a..189111925d4 100644
--- a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp
+++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilder.cpp
@@ -32,6 +32,7 @@
//#include
#include "W3DDevice/GameClient/W3DFileSystem.h"
+#include "Common/CommandLine.h"
#include "Common/FramePacer.h"
#include "Common/GlobalData.h"
#include "WHeightMapEdit.h"
@@ -151,8 +152,31 @@ FileClass * WB_W3DFileSystem::Get_File( char const *filename )
return pFile;
}
+/////////////////////////////////////////////////////////////////////////////
+// MFC parses the command line again to select a document to open. Skip the
+// arguments already handled by the startup parser so option values are not
+// mistaken for map filenames.
+
+class WBCommandLineInfo : public CCommandLineInfo
+{
+public:
+ WBCommandLineInfo() : m_argIndex(0) {}
+ virtual void ParseParam(const TCHAR* pszParam, BOOL bFlag, BOOL bLast) override
+ {
+ if (CommandLine::wasCommandLineArgumentParsed(m_argIndex++))
+ {
+ // MFC uses bLast to finalize its shell command, even when the final argument is skipped.
+ ParseLast(bLast);
+ return;
+ }
+ CCommandLineInfo::ParseParam(pszParam, bFlag, bLast);
+ }
+
+private:
+ int m_argIndex;
+};
/////////////////////////////////////////////////////////////////////////////
// The one and only CWorldBuilderApp object
@@ -281,6 +305,8 @@ BOOL CWorldBuilderApp::InitInstance()
// initialize the memory manager early
initMemoryManager();
+ CommandLine::parseCommandLineForStartup();
+
#ifdef DEBUG_LOGGING
// Turn on console output jba [3/20/2003]
DebugSetFlags(DebugGetFlags() | DEBUG_FLAG_LOG_TO_CONSOLE);
@@ -315,14 +341,6 @@ BOOL CWorldBuilderApp::InitInstance()
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
- // Set the current directory to the app directory.
- char buf[_MAX_PATH];
- GetModuleFileName(nullptr, buf, sizeof(buf));
- if (char *pEnd = strrchr(buf, '\\')) {
- *pEnd = 0;
- }
- ::SetCurrentDirectory(buf);
-
TheFileSystem = new FileSystem;
initSubsystem(TheLocalFileSystem, (LocalFileSystem*)new Win32LocalFileSystem);
@@ -334,7 +352,8 @@ BOOL CWorldBuilderApp::InitInstance()
INI ini;
- initSubsystem(TheWritableGlobalData, new GlobalData(), "Data\\INI\\Default\\GameData", "Data\\INI\\GameData");
+ DEBUG_ASSERTCRASH(TheWritableGlobalData, ("TheWritableGlobalData expected to be created"));
+ initSubsystem(TheWritableGlobalData, TheWritableGlobalData, "Data\\INI\\Default\\GameData", "Data\\INI\\GameData");
TheFramePacer = new FramePacer();
@@ -347,6 +366,7 @@ BOOL CWorldBuilderApp::InitInstance()
#endif
DEBUG_LOG(("TheWritableGlobalData %x", TheWritableGlobalData));
+ char buf[_MAX_PATH];
#if 1
// srj sez: put INI into our user data folder, not the ap dir
free((void*)m_pszProfileName);
@@ -444,7 +464,7 @@ BOOL CWorldBuilderApp::InitInstance()
#endif
// Parse command line for standard shell commands, DDE, file open
- CCommandLineInfo cmdInfo;
+ WBCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Dispatch commands specified on the command line