-
Notifications
You must be signed in to change notification settings - Fork 250
feat(system): Add startup working directory options #3149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
492f69c
eef3f61
acb2e49
3dc9021
c733abd
110ba3d
7ee4931
0b91889
c63651c
4bc1ee3
9b728cf
474f79e
3597861
6f4fce1
abdddd9
7b8edb9
92cada9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| /* | ||
| ** 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 <http://www.gnu.org/licenses/>. | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include "Lib/BaseType.h" | ||
|
|
||
| namespace rts | ||
| { | ||
|
|
||
| // TheSuperHackers @feature 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 any setter call, including a failed attempt. | ||
| static Bool hasSetWorkingDirectory(); | ||
|
|
||
| private: | ||
| static Bool saveStartupWorkingDirectory(); | ||
| static Bool setWorkingDirectory(const char *path); | ||
|
|
||
| static Bool s_hasSetWorkingDirectory; | ||
| static Char s_startupWorkingDirectory[]; | ||
| static const Bool s_startupWorkingDirectoryInitializer; | ||
| }; | ||
|
|
||
| } // namespace rts |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,33 @@ Int parseJobs(char *args[], int num) | |
| return 1; | ||
| } | ||
|
|
||
| Int parseUseCwd(char *[], int) | ||
| { | ||
| // TheSuperHackers @feature 14/08/2026 | ||
| // -useCwd restores the startup working directory. | ||
| if (!rts::WorkingDirectory::setStartupWorkingDirectory()) | ||
| rts::WorkingDirectory::setExecutableWorkingDirectory(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe do not call the fallback here? It is already called later. |
||
| return 1; | ||
| } | ||
|
|
||
| Int parseSetCwd(char *args[], int num) | ||
| { | ||
| // TheSuperHackers @bugfix CryoTheRenegade 29/08/2026 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not a bugfix, obsolete comment line. |
||
| // -setCwd <path> overrides the working directory. | ||
| if (num <= 1) | ||
| { | ||
| DEBUG_LOG(("-setCwd requires a directory path")); | ||
| rts::WorkingDirectory::setExecutableWorkingDirectory(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe call nothing here? |
||
| return 1; | ||
| } | ||
| if (rts::WorkingDirectory::setCustomWorkingDirectory(args[1])) | ||
| return 2; | ||
|
|
||
| rts::WorkingDirectory::setExecutableWorkingDirectory(); | ||
| // Leave a failed option-like value available for subsequent argument parsing. | ||
| return args[1][0] == '-' || args[1][0] == '/' ? 1 : 2; | ||
| } | ||
|
|
||
| Int parseXRes(char *args[], int num) | ||
| { | ||
| if (num > 1) | ||
|
|
@@ -1155,6 +1182,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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Author missing before date. |
||
| // 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 | ||
|
|
@@ -1326,71 +1359,12 @@ static CommandLineParam paramsForEngineInit[] = | |
|
|
||
| }; | ||
|
|
||
| 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 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) | ||
| static void parseCommandLine(const CommandLineParam* params, int numParams, BoolVector &parsedArguments) | ||
| { | ||
| std::vector<char*> 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; | ||
| // Preserve arguments recorded by the earlier parsing phase. | ||
| parsedArguments.resize(argc > 0 ? argc - 1 : 0, FALSE); | ||
|
|
||
| int arg = 1; | ||
|
|
||
|
|
@@ -1407,35 +1381,34 @@ static void parseCommandLine(const CommandLineParam* params, int numParams) | |
| arg = 1; | ||
| #endif // DEBUG_LOGGING | ||
|
|
||
| // To parse command-line parameters, we loop through a table holding arguments | ||
| // and functions to handle them. Comparisons can be case-(in)sensitive, and | ||
| // can check the entire string (for testing the presence of a flag) or check | ||
| // just the start (for a key=val argument). The handling function can also | ||
| // look at the next argument(s), to accommodate multi-arg parameters, e.g. "-p 1234". | ||
| while (arg<argc) | ||
| // Match complete option names without case sensitivity. Each handler returns | ||
| // the number of arguments consumed, including the option itself. | ||
| while (arg < argc) | ||
| { | ||
| // Look at arg #i | ||
| Bool found = false; | ||
| for (int param=0; !found && param<numParams; ++param) | ||
| int parsedArgCount = 1; | ||
| for (int param = 0; param < numParams; ++param) | ||
| { | ||
| int len = strlen(params[param].name); | ||
| int len2 = strlen(argv[arg]); | ||
| if (len2 != len) | ||
| if (stricmp(argv[arg], params[param].name) != 0) | ||
| continue; | ||
| if (strnicmp(argv[arg], params[param].name, len) == 0) | ||
| { | ||
| arg += params[param].func(&argv[0]+arg, argc-arg); | ||
| found = true; | ||
| break; | ||
| } | ||
| } | ||
| if (!found) | ||
| { | ||
| arg++; | ||
|
|
||
| parsedArgCount = params[param].func(argv + arg, argc - arg); | ||
| for (int i = 0; i < parsedArgCount && arg + i < argc; ++i) | ||
| parsedArguments[arg + i - 1] = TRUE; | ||
| break; | ||
| } | ||
| arg += parsedArgCount; | ||
| } | ||
| } | ||
|
|
||
| bool CommandLine::wasCommandLineArgumentParsed(int argIndex) | ||
| { | ||
| if (TheGlobalData == nullptr) | ||
| return false; | ||
|
|
||
| const BoolVector &parsedArguments = TheGlobalData->m_commandLineData.m_parsedArguments; | ||
| return argIndex >= 0 && argIndex < static_cast<int>(parsedArguments.size()) && parsedArguments[argIndex]; | ||
| } | ||
|
|
||
| void createGlobalData() | ||
| { | ||
| if (TheGlobalData == nullptr) | ||
|
|
@@ -1452,7 +1425,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 +1442,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); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Obsolete comment line