diff --git a/openrct2.vcxproj b/openrct2.vcxproj
index 33e2ee8ed9..1b663f40b6 100644
--- a/openrct2.vcxproj
+++ b/openrct2.vcxproj
@@ -26,9 +26,10 @@
-
+
+
diff --git a/openrct2.vcxproj.filters b/openrct2.vcxproj.filters
index 468197b324..a27804dbe2 100644
--- a/openrct2.vcxproj.filters
+++ b/openrct2.vcxproj.filters
@@ -564,8 +564,9 @@
Source\argparse
-
+
+
diff --git a/openrct2.vcxproj.user b/openrct2.vcxproj.user
index fb13b2fbe1..6e6c19d6fb 100644
--- a/openrct2.vcxproj.user
+++ b/openrct2.vcxproj.user
@@ -7,6 +7,7 @@
$(TargetDir)\openrct2.exe
WindowsLocalDebugger
$(TargetDir)
+ --port=233
$(TargetDir)
diff --git a/src/cmdline/CommandLine.cpp b/src/cmdline/CommandLine.cpp
index cee374d595..fdac6a6d7a 100644
--- a/src/cmdline/CommandLine.cpp
+++ b/src/cmdline/CommandLine.cpp
@@ -8,6 +8,8 @@ extern "C"
#include "../core/String.hpp"
#include "CommandLine.hpp"
+#pragma region CommandLineArgEnumerator
+
CommandLineArgEnumerator::CommandLineArgEnumerator(const char * const * arguments, int count)
{
_arguments = arguments;
@@ -20,6 +22,19 @@ void CommandLineArgEnumerator::Reset()
_index = 0;
}
+bool CommandLineArgEnumerator::Backtrack()
+{
+ if (_index > 0)
+ {
+ _index--;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
bool CommandLineArgEnumerator::TryPop()
{
if (_index < _count)
@@ -71,6 +86,8 @@ bool CommandLineArgEnumerator::TryPopString(const char * * result)
}
}
+#pragma endregion
+
namespace CommandLine
{
constexpr const char * HelpText = "openrct2 -ha shows help for all commands. "
@@ -80,6 +97,13 @@ namespace CommandLine
static void PrintExamples(const CommandLineExample *examples);
static utf8 * GetOptionCaption(utf8 * buffer, size_t bufferSize, const CommandLineOptionDefinition *option);
+ static const CommandLineOptionDefinition * FindOption(const CommandLineOptionDefinition * options, char shortName);
+ static const CommandLineOptionDefinition * FindOption(const CommandLineOptionDefinition * options, const char * longName);
+
+ static bool ParseShortOption(const CommandLineOptionDefinition * options, CommandLineArgEnumerator *argEnumerator, const char *argument);
+ static bool ParseLongOption(const CommandLineOptionDefinition * options, CommandLineArgEnumerator * argEnumerator, const char * argument);
+ static bool ParseOptionValue(const CommandLineOptionDefinition * option, const char * valueString);
+
void PrintHelp()
{
const CommandLineCommand * command;
@@ -215,19 +239,23 @@ namespace CommandLine
return buffer;
}
- const CommandLineCommand * FindCommandFor(const CommandLineCommand * commands, const char * const * arguments, int count)
+ void PrintUsageFor(const char * command)
{
- // Check if there are any arguments
- if (count == 0)
- {
- return nullptr;
- }
- // Check if options have started
- const char * firstArgument = arguments[0];
+ }
+
+ const CommandLineCommand * FindCommandFor(const CommandLineCommand * commands, CommandLineArgEnumerator *argEnumerator)
+ {
+ // Check if end of arguments or options have started
+ const char * firstArgument;
+ if (!argEnumerator->TryPopString(&firstArgument))
+ {
+ return commands;
+ }
if (firstArgument[0] == '-')
{
- return nullptr;
+ argEnumerator->Backtrack();
+ return commands;
}
// Search through defined commands for one that matches
@@ -250,25 +278,241 @@ namespace CommandLine
else
{
// Recurse for the sub command table
- return FindCommandFor(command->SubCommands, &arguments[1], count - 1);
+ return FindCommandFor(command->SubCommands, argEnumerator);
}
}
}
return fallback;
}
-}
-void CommandLineDisplayUsageFor(const char * command)
-{
+ static bool ParseOptions(const CommandLineOptionDefinition * options, CommandLineArgEnumerator *argEnumerator)
+ {
+ const char * argument;
+ if (argEnumerator->TryPopString(&argument))
+ {
+ if (argument[0] == '-')
+ {
+ if (argument[1] == '-')
+ {
+ if (!ParseLongOption(options, argEnumerator, &argument[2]))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (!ParseShortOption(options, argEnumerator, argument))
+ {
+ return false;
+ }
+ }
+ }
+ else
+ {
+ Console::WriteLineError("All options must be passed at the end of the command line.");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static bool ParseLongOption(const CommandLineOptionDefinition * options,
+ CommandLineArgEnumerator *argEnumerator,
+ const char *argument)
+ {
+ // Get just the option name
+ char optionName[64];
+ const char * equalsCh = strchr(argument, '=');
+ if (equalsCh != nullptr)
+ {
+ String::Set(optionName, sizeof(optionName), argument, equalsCh - argument);
+ }
+ else
+ {
+ String::Set(optionName, sizeof(optionName), argument);
+ }
+
+ // Find a matching option definition
+ const CommandLineOptionDefinition * option = FindOption(options, optionName);
+ if (option == nullptr)
+ {
+ Console::WriteError("Unknown option: --");
+ Console::WriteLineError(optionName);
+ return false;
+ }
+
+ if (equalsCh == nullptr)
+ {
+ if (option->Type == CMDLINE_TYPE_SWITCH)
+ {
+ ParseOptionValue(option, nullptr);
+ }
+ else
+ {
+ const char * valueString = nullptr;
+ if (!argEnumerator->TryPopString(&valueString))
+ {
+ Console::WriteError("Expected value for option: ");
+ Console::WriteLineError(optionName);
+ return false;
+ }
+
+ if (!ParseOptionValue(option, valueString))
+ {
+ return false;
+ }
+ }
+ }
+ else
+ {
+ if (option->Type == CMDLINE_TYPE_SWITCH)
+ {
+ Console::WriteError("Option is a switch: ");
+ Console::WriteLineError(optionName);
+ return false;
+ }
+ else
+ {
+ if (!ParseOptionValue(option, equalsCh + 1))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ static bool ParseShortOption(const CommandLineOptionDefinition * options,
+ CommandLineArgEnumerator *argEnumerator,
+ const char *argument)
+ {
+ const CommandLineOptionDefinition * option = nullptr;
+
+ const char * shortOption = &argument[1];
+ for (; *shortOption != '\0'; shortOption++)
+ {
+ option = FindOption(options, shortOption[0]);
+ if (option == nullptr)
+ {
+ Console::WriteError("Unknown option: -");
+ Console::WriteError(shortOption[0]);
+ Console::WriteLineError();
+ return false;
+ }
+ if (option->Type == CMDLINE_TYPE_SWITCH)
+ {
+ if (!ParseOptionValue(option, nullptr))
+ {
+ return false;
+ }
+ }
+ else if (shortOption[1] != '\0')
+ {
+ if (!ParseOptionValue(option, &shortOption[1]))
+ {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ if (option != nullptr && option->Type != CMDLINE_TYPE_SWITCH)
+ {
+ const char * valueString = nullptr;
+ if (!argEnumerator->TryPopString(&valueString))
+ {
+ Console::WriteError("Expected value for option: ");
+ Console::WriteError(option->ShortName);
+ Console::WriteLineError();
+ return false;
+ }
+
+ if (!ParseOptionValue(option, valueString))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ static bool ParseOptionValue(const CommandLineOptionDefinition * option, const char * valueString)
+ {
+ if (option->OutAddress == nullptr) return true;
+
+ switch (option->Type) {
+ case CMDLINE_TYPE_SWITCH:
+ *((bool *)option->OutAddress) = true;
+ return true;
+ case CMDLINE_TYPE_INTEGER:
+ *((sint32 *)option->OutAddress) = (sint32)atol(valueString);
+ return true;
+ case CMDLINE_TYPE_REAL:
+ *((float *)option->OutAddress) = (float)atof(valueString);
+ return true;
+ case CMDLINE_TYPE_STRING:
+ *((utf8 * *)option->OutAddress) = String::Duplicate(valueString);
+ return true;
+ default:
+ Console::WriteError("Unknown CMDLINE_TYPE for: ");
+ Console::WriteLineError(option->LongName);
+ return false;
+ }
+ }
+
+ const CommandLineOptionDefinition * FindOption(const CommandLineOptionDefinition * options, char shortName)
+ {
+ for (const CommandLineOptionDefinition * option = options; option->Type != 255; option++)
+ {
+ if (option->ShortName == shortName)
+ {
+ return option;
+ }
+ }
+ return nullptr;
+ }
+
+ const CommandLineOptionDefinition * FindOption(const CommandLineOptionDefinition * options, const char * longName)
+ {
+ for (const CommandLineOptionDefinition * option = options; option->Type != 255; option++)
+ {
+ if (String::Equals(option->LongName, longName))
+ {
+ return option;
+ }
+ }
+ return nullptr;
+ }
}
extern "C"
{
int cmdline_run(const char * * argv, int argc)
{
- CommandLine::PrintHelp();
- return 0;
+ auto argEnumerator = CommandLineArgEnumerator(argv, argc);
+
+ // Pop process path
+ argEnumerator.TryPop();
+
+ const CommandLineCommand * command = CommandLine::FindCommandFor(CommandLine::RootCommands, &argEnumerator);
+ if (command->Options != nullptr)
+ {
+ auto argEnumeratorForOptions = CommandLineArgEnumerator(argEnumerator);
+ if (!CommandLine::ParseOptions(command->Options, &argEnumeratorForOptions))
+ {
+ return EXITCODE_FAIL;
+ }
+ }
+ if (command == CommandLine::RootCommands || command->Func == nullptr)
+ {
+ return CommandLine::HandleCommandDefault();
+ }
+ else
+ {
+ return command->Func(&argEnumerator);
+ }
}
}
diff --git a/src/cmdline/CommandLine.hpp b/src/cmdline/CommandLine.hpp
index 234eb9ceb6..8a1c340c5b 100644
--- a/src/cmdline/CommandLine.hpp
+++ b/src/cmdline/CommandLine.hpp
@@ -22,6 +22,7 @@ public:
CommandLineArgEnumerator(const char * const * arguments, int count);
void Reset();
+ bool Backtrack();
bool TryPop();
bool TryPopInteger(sint32 * result);
bool TryPopReal(float * result);
@@ -31,6 +32,13 @@ public:
typedef int exitcode_t;
typedef exitcode_t (*CommandLineFunc)(CommandLineArgEnumerator *);
+enum
+{
+ EXITCODE_FAIL = -1,
+ EXITCODE_OK = 0,
+ EXITCODE_CONTINUE = 1,
+};
+
struct CommandLineExample
{
const char * Arguments;
@@ -72,12 +80,13 @@ constexpr char NAC = '\0';
#define DefineCommand(name, params, options, func) { name, params, options, NULL, func }
#define DefineSubCommand(name, subcommandtable) { name, "", NULL, subcommandtable, NULL }
-void CommandLineDisplayUsageFor(const char * command);
-
-extern const CommandLineCommand RootCommands[];
-extern const CommandLineExample RootExamples[];
-
namespace CommandLine
{
+ extern const CommandLineCommand RootCommands[];
+ extern const CommandLineExample RootExamples[];
+
void PrintHelp();
+ void PrintUsageFor(const char * command);
+
+ exitcode_t HandleCommandDefault();
}
diff --git a/src/cmdline/CommandLineDefinitions.cpp b/src/cmdline/CommandLineDefinitions.cpp
deleted file mode 100644
index 68a0a9cc80..0000000000
--- a/src/cmdline/CommandLineDefinitions.cpp
+++ /dev/null
@@ -1,104 +0,0 @@
-#include "CommandLine.hpp"
-
-const CommandLineCommand * ScreenshotCommandTable;
-const CommandLineCommand * SpriteCommandTable;
-
-static bool _help;
-static bool _version;
-static bool _noInstall;
-
-const CommandLineOptionDefinition StandardOptions[]
-{
- { CMDLINE_TYPE_SWITCH, &_help, 'h', "help", "show this help message and exit" },
- { CMDLINE_TYPE_SWITCH, &_version, 'v', "version", "show version information and exit" },
- { CMDLINE_TYPE_SWITCH, &_noInstall, 'n', "no-install", "do not install scenario if passed" },
- { CMDLINE_TYPE_SWITCH, nullptr, NAC, "no-install", "show information about " OPENRCT2_NAME },
- { CMDLINE_TYPE_SWITCH, nullptr, NAC, "verbose", "log verbose messages" },
- { CMDLINE_TYPE_SWITCH, nullptr, NAC, "headless", "run " OPENRCT2_NAME " headless" },
- { CMDLINE_TYPE_INTEGER, nullptr, NAC, "port", "port to use for hosting or joining a server" },
- { CMDLINE_TYPE_STRING, nullptr, NAC, "user-data-path", "path to the user data directory (containing config.ini)" },
- { CMDLINE_TYPE_STRING, nullptr, NAC, "openrct-data-path", "path to the OpenRCT2 data directory (containing languages)" },
- OptionTableEnd
-};
-
-const CommandLineCommand RootCommands[]
-{
- // Main commands
- DefineCommand("", "", StandardOptions, nullptr),
- DefineCommand("intro", "", StandardOptions, nullptr),
- DefineCommand("host", "", StandardOptions, nullptr),
- DefineCommand("join", "", StandardOptions, nullptr),
-
- // Sub-commands
- DefineSubCommand("screenshot", ScreenshotCommandTable),
- DefineSubCommand("sprite", SpriteCommandTable ),
-
- CommandTableEnd
-};
-
-const CommandLineExample RootExamples[]
-{
- { "./my_park.sv6", "open a saved park" },
- { "./SnowyPark.sc6", "install and open a scenario" },
- { "./ShuttleLoop.td6", "install a track" },
- { "http:/openrct2.website/files/SnowyPark.sv6", "download and open a saved park" },
- { "host ./my_park.sv6 --port 11753 --headless", "run a headless server for a saved park" },
- ExampleTableEnd
-};
-
-void HandleNoCommand(CommandLineArgEnumerator * enumerator)
-{
-
-}
-
-void HandleCommandIntro(CommandLineArgEnumerator * enumerator)
-{
-
-}
-
-void HandleCommandHost(CommandLineArgEnumerator * enumerator)
-{
- const char * parkUri;
-
- if (!enumerator->TryPopString(&parkUri))
- {
- fprintf(stderr, "Expected path or URL to a saved park.");
- CommandLineDisplayUsageFor("host");
- }
-}
-
-void HandleCommandJoin(CommandLineArgEnumerator * enumerator)
-{
- const char * hostname;
-
- if (!enumerator->TryPopString(&hostname))
- {
- fprintf(stderr, "Expected a hostname or IP address to the server to connect to.");
- CommandLineDisplayUsageFor("join");
- }
-}
-
-
-
-const CommandLineCommand ScreenshotCommands[]
-{
- // Main commands
- DefineCommand("", " [ ]", nullptr, nullptr),
- DefineCommand("", " giant ", nullptr, nullptr),
- CommandTableEnd
-};
-
-const CommandLineOptionDefinition SpriteOptions[]
-{
- { CMDLINE_TYPE_STRING, nullptr, 'm', "mode", "the type of sprite conversion " },
- OptionTableEnd
-};
-
-const CommandLineCommand SpriteCommands[]
-{
- // Main commands
- DefineCommand("details", " [idx]", SpriteOptions, nullptr),
- DefineCommand("export", "