diff --git a/Core/GameEngine/CMakeLists.txt b/Core/GameEngine/CMakeLists.txt
index af39c615d37..c462ee691f2 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
@@ -693,6 +694,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..e76efc40edf
--- /dev/null
+++ b/Core/GameEngine/Include/Common/WorkingDirectory.h
@@ -0,0 +1,48 @@
+/*
+** Command & Conquer Generals Zero Hour(tm)
+** Copyright 2026 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 CryoTheRenegade 14/08/2026
+// Saves and restores the process working directory.
+class WorkingDirectory
+{
+public:
+ static Bool setStartupWorkingDirectory();
+ static Bool setExecutableWorkingDirectory();
+ // Relative paths are resolved from the current working directory.
+ static Bool setCustomWorkingDirectory(const char *path);
+ // Returns true after a setter successfully changes the working directory.
+ static Bool hasSetWorkingDirectory();
+
+private:
+ friend struct WorkingDirectoryInitializer;
+
+ static Bool saveStartupWorkingDirectory();
+ static Bool setWorkingDirectory(const char *path);
+
+ static Bool s_hasSetWorkingDirectory;
+ static Char s_startupWorkingDirectory[];
+};
+
+} // namespace rts
diff --git a/Core/GameEngine/Source/Common/CommandLine.cpp b/Core/GameEngine/Source/Common/CommandLine.cpp
index 96a6f475d30..0daeb20e9ff 100644
--- a/Core/GameEngine/Source/Common/CommandLine.cpp
+++ b/Core/GameEngine/Source/Common/CommandLine.cpp
@@ -31,11 +31,11 @@
#include "Common/LocalFileSystem.h"
#include "Common/Recorder.h"
#include "Common/version.h"
+#include "Common/WorkingDirectory.h"
#include "GameClient/ClientInstance.h"
#include "GameClient/TerrainVisual.h" // for TERRAIN_LOD_MIN definition
#include "GameClient/GameText.h"
#include "GameNetwork/NetworkDefs.h"
-#include "WWLib/trim.h"
@@ -463,6 +463,24 @@ Int parseJobs(char *args[], int num)
return 1;
}
+Int parseUseCwd(char *[], int)
+{
+ // -useCwd restores the startup working directory.
+ rts::WorkingDirectory::setStartupWorkingDirectory();
+ return 1;
+}
+
+Int parseSetCwd(char *args[], int num)
+{
+ // -setCwd overrides the working directory.
+ if (num > 1)
+ {
+ rts::WorkingDirectory::setCustomWorkingDirectory(args[1]);
+ return 2;
+ }
+ return 1;
+}
+
Int parseXRes(char *args[], int num)
{
if (num > 1)
@@ -1155,6 +1173,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 CryoTheRenegade 14/08/2026
+ // Use the current working directory as provided by the OS, or an explicit path.
+ // The last successful selection wins; otherwise use the executable directory.
+ { "-setCwd", parseSetCwd },
+ { "-useCwd", parseUseCwd },
};
// These Params are parsed during Engine Init before INI data is loaded
@@ -1326,116 +1350,63 @@ static CommandLineParam paramsForEngineInit[] =
};
-char *nextParam(char *newSource, const char *seps)
+static void parseCommandLine(const CommandLineParam* params, int numParams, BoolVector &parsedArguments)
{
- static char *source = nullptr;
- if (newSource)
+ // Startup parsing can run from static constructors, before WinMain.
+ int argc = __argc;
+ char **argv = __argv;
+ if (argc > 0)
{
- source = newSource;
- }
- if (!source)
- {
- return nullptr;
+ // Skip the first argument which is the executable file name.
+ argc -= 1;
+ argv += 1;
}
-
- // 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();
-
- int arg = 1;
+ // Preserve arguments recorded by the earlier parsing phase.
+ parsedArguments.resize(argc, FALSE);
#ifdef DEBUG_LOGGING
DEBUG_LOG(("Command-line args:"));
int debugFlags = DebugGetFlags();
DebugSetFlags(debugFlags & ~DEBUG_FLAG_PREPEND_TIME); // turn off timestamps
- for (arg=1; argm_commandLineData.m_parsedArguments;
+ return argIndex >= 0 && argIndex < static_cast(parsedArguments.size()) && parsedArguments[argIndex];
+}
+
void createGlobalData()
{
if (TheGlobalData == nullptr)
@@ -1452,7 +1423,11 @@ 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);
+
+ if (!rts::WorkingDirectory::hasSetWorkingDirectory())
+ rts::WorkingDirectory::setExecutableWorkingDirectory();
}
void CommandLine::parseCommandLineForEngineInit()
@@ -1465,5 +1440,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..7c927bf9bbe
--- /dev/null
+++ b/Core/GameEngine/Source/Common/WorkingDirectory.cpp
@@ -0,0 +1,117 @@
+/*
+** Command & Conquer Generals Zero Hour(tm)
+** Copyright 2026 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
+{
+
+Bool WorkingDirectory::s_hasSetWorkingDirectory = FALSE;
+Char WorkingDirectory::s_startupWorkingDirectory[_MAX_PATH] = "";
+
+struct WorkingDirectoryInitializer
+{
+ WorkingDirectoryInitializer()
+ {
+ WorkingDirectory::saveStartupWorkingDirectory();
+ }
+};
+
+// Request capture at startup, even if no setter is called.
+static WorkingDirectoryInitializer s_workingDirectoryInitializer;
+
+Bool WorkingDirectory::saveStartupWorkingDirectory()
+{
+ // An earlier static constructor may call a setter before our initializer runs.
+ // Cache the first capture, including failure, without logging or allocating.
+ static const DWORD len = ::GetCurrentDirectory(ARRAY_SIZE(s_startupWorkingDirectory), s_startupWorkingDirectory);
+ return len > 0 && len < ARRAY_SIZE(s_startupWorkingDirectory);
+}
+
+Bool WorkingDirectory::setWorkingDirectory(const char *path)
+{
+ if (path == nullptr || path[0] == '\0')
+ {
+ DEBUG_LOG(("Cannot set an empty working directory"));
+ return FALSE;
+ }
+
+ if (::SetCurrentDirectory(path) == 0)
+ {
+ DEBUG_LOG(("Failed to set working directory to '%s' (error %d)", path, GetLastError()));
+ return FALSE;
+ }
+
+ s_hasSetWorkingDirectory = TRUE;
+ return TRUE;
+}
+
+Bool WorkingDirectory::setStartupWorkingDirectory()
+{
+ if (!saveStartupWorkingDirectory())
+ {
+ DEBUG_LOG(("Startup working directory is unavailable"));
+ return FALSE;
+ }
+ return setWorkingDirectory(s_startupWorkingDirectory);
+}
+
+Bool WorkingDirectory::setExecutableWorkingDirectory()
+{
+ saveStartupWorkingDirectory();
+
+ Char buffer[_MAX_PATH];
+ const DWORD len = GetModuleFileName(nullptr, buffer, ARRAY_SIZE(buffer));
+ if (len == 0)
+ {
+ DEBUG_LOG(("Failed to get executable path for working directory (error %d)", GetLastError()));
+ return FALSE;
+ }
+ if (len >= ARRAY_SIZE(buffer))
+ {
+ DEBUG_LOG(("Executable path exceeds the working directory path buffer"));
+ return FALSE;
+ }
+
+ Char *pEnd = strrchr(buffer, '\\');
+ if (pEnd == nullptr)
+ {
+ DEBUG_LOG(("Executable path has no directory: '%s'", buffer));
+ return FALSE;
+ }
+ // TheSuperHackers @bugfix For "C:\game.exe", retain "C:\" as the directory.
+ // Removing the backslash leaves "C:", which refers to that drive's current directory.
+ pEnd[1] = '\0';
+
+ return setWorkingDirectory(buffer);
+}
+
+Bool WorkingDirectory::setCustomWorkingDirectory(const char *path)
+{
+ saveStartupWorkingDirectory();
+ return setWorkingDirectory(path);
+}
+
+Bool WorkingDirectory::hasSetWorkingDirectory()
+{
+ return s_hasSetWorkingDirectory;
+}
+
+} // namespace rts
diff --git a/Core/Tools/MapCacheBuilder/Source/WinMain.cpp b/Core/Tools/MapCacheBuilder/Source/WinMain.cpp
index 1ba344f7c01..4ffad1f5959 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 0c8e8820660..c00862a980b 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 b3287235361..807beebcd80 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 89a5fa08f9d..92761fd3c33 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 d49589ee0c8..b0307ed0261 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