diff --git a/openrct2.vcxproj b/openrct2.vcxproj
index c390fa4819..33e2ee8ed9 100644
--- a/openrct2.vcxproj
+++ b/openrct2.vcxproj
@@ -25,6 +25,8 @@
+
+
@@ -191,8 +193,10 @@
+
+
@@ -201,6 +205,7 @@
+
diff --git a/openrct2.vcxproj.filters b/openrct2.vcxproj.filters
index 72b54f2aeb..468197b324 100644
--- a/openrct2.vcxproj.filters
+++ b/openrct2.vcxproj.filters
@@ -564,6 +564,8 @@
Source\argparse
+
+
@@ -860,5 +862,8 @@
Source\argparse
+
+
+
-
+
\ No newline at end of file
diff --git a/src/cmdline.c b/src/cmdline.c
index d2cb65b1fa..6c3e57081e 100644
--- a/src/cmdline.c
+++ b/src/cmdline.c
@@ -70,7 +70,7 @@ static const char *const usage[] = {
* will then terminate before any initialisation has even been done.
* @returns 1 if the game should run, otherwise 0.
*/
-int cmdline_run(const char **argv, int argc)
+int cmdline_run_old(const char **argv, int argc)
{
//
int version = 0, headless = 0, verbose = 0, width = 0, height = 0, port = 0;
diff --git a/src/cmdline/CommandLine.cpp b/src/cmdline/CommandLine.cpp
new file mode 100644
index 0000000000..cee374d595
--- /dev/null
+++ b/src/cmdline/CommandLine.cpp
@@ -0,0 +1,274 @@
+extern "C"
+{
+ #include "../platform/platform.h"
+}
+
+#include "../core/Console.hpp"
+#include "../core/Math.hpp"
+#include "../core/String.hpp"
+#include "CommandLine.hpp"
+
+CommandLineArgEnumerator::CommandLineArgEnumerator(const char * const * arguments, int count)
+{
+ _arguments = arguments;
+ _count = count;
+ _index = 0;
+}
+
+void CommandLineArgEnumerator::Reset()
+{
+ _index = 0;
+}
+
+bool CommandLineArgEnumerator::TryPop()
+{
+ if (_index < _count)
+ {
+ _index++;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+bool CommandLineArgEnumerator::TryPopInteger(sint32 * result)
+{
+ char const * arg;
+ if (TryPopString(&arg))
+ {
+ *result = (sint32)atol(arg);
+ return true;
+ }
+
+ return false;
+}
+
+bool CommandLineArgEnumerator::TryPopReal(float * result)
+{
+ char const * arg;
+ if (TryPopString(&arg))
+ {
+ *result = (float)atof(arg);
+ return true;
+ }
+
+ return false;
+}
+
+bool CommandLineArgEnumerator::TryPopString(const char * * result)
+{
+ if (_index < _count)
+ {
+ *result = _arguments[_index];
+ _index++;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+namespace CommandLine
+{
+ constexpr const char * HelpText = "openrct2 -ha shows help for all commands. "
+ "openrct2 -h will show help and details for a given command.";
+
+ static void PrintOptions(const CommandLineOptionDefinition *options);
+ static void PrintExamples(const CommandLineExample *examples);
+ static utf8 * GetOptionCaption(utf8 * buffer, size_t bufferSize, const CommandLineOptionDefinition *option);
+
+ void PrintHelp()
+ {
+ const CommandLineCommand * command;
+ size_t maxNameLength = 0;
+ size_t maxParamsLength = 0;
+
+ // Get the largest command name length and parameter length
+ for (command = RootCommands; command->Name != nullptr; command++)
+ {
+ maxNameLength = Math::Max(maxNameLength, String::LengthOf(command->Name));
+ maxParamsLength = Math::Max(maxParamsLength, String::LengthOf(command->Parameters));
+ }
+
+ // Print usage / main commands
+ const char * usageString = "usage: openrct2 ";
+ const size_t usageStringLength = String::LengthOf(usageString);
+
+ Console::Write(usageString);
+ for (command = RootCommands; command->Name != nullptr; command++)
+ {
+ if (command != RootCommands)
+ {
+ Console::WriteSpace(usageStringLength);
+ }
+
+ Console::Write(command->Name);
+ Console::WriteSpace(maxNameLength - String::LengthOf(command->Name) + 1);
+
+ if (command->SubCommands == nullptr)
+ {
+ Console::Write(command->Parameters);
+ Console::WriteSpace(maxParamsLength - String::LengthOf(command->Parameters));
+
+ if (command->Options != nullptr)
+ {
+ Console::Write(" [options]");
+ }
+ }
+ else
+ {
+ Console::Write("...");
+ }
+ Console::WriteLine();
+ }
+ Console::WriteLine();
+
+ PrintOptions(RootCommands->Options);
+ PrintExamples(RootExamples);
+
+ Console::WriteLine(HelpText);
+ }
+
+ static void PrintOptions(const CommandLineOptionDefinition *options)
+ {
+ // Print options for main commands
+ size_t maxOptionLength = 0;
+ const CommandLineOptionDefinition * option = options;
+ for (; option->Type != 255; option++)
+ {
+ char buffer[128];
+ GetOptionCaption(buffer, sizeof(buffer), option);
+ size_t optionCaptionLength = String::LengthOf(buffer);
+ maxOptionLength = Math::Max(maxOptionLength, optionCaptionLength);
+ }
+
+ option = RootCommands->Options;
+ for (; option->Type != 255; option++)
+ {
+ Console::WriteSpace(4);
+
+ char buffer[128];
+ GetOptionCaption(buffer, sizeof(buffer), option);
+ size_t optionCaptionLength = String::LengthOf(buffer);
+ Console::Write(buffer);
+
+ Console::WriteSpace(maxOptionLength - optionCaptionLength + 4);
+ Console::Write(option->Description);
+ Console::WriteLine();
+ }
+ Console::WriteLine();
+ }
+
+ static void PrintExamples(const CommandLineExample *examples)
+ {
+ size_t maxArgumentsLength = 0;
+
+ const CommandLineExample * example;
+ for (example = examples; example->Arguments != nullptr; example++)
+ {
+ size_t argumentsLength = String::LengthOf(example->Arguments);
+ maxArgumentsLength = Math::Max(maxArgumentsLength, argumentsLength);
+ }
+
+ Console::WriteLine("examples:");
+ for (example = examples; example->Arguments != nullptr; example++)
+ {
+ Console::Write(" openrct2 ");
+ Console::Write(example->Arguments);
+
+ size_t argumentsLength = String::LengthOf(example->Arguments);
+ Console::WriteSpace(maxArgumentsLength - argumentsLength + 4);
+ Console::Write(example->Description);
+ Console::WriteLine();
+ }
+
+ Console::WriteLine();
+ }
+
+ static utf8 * GetOptionCaption(utf8 * buffer, size_t bufferSize, const CommandLineOptionDefinition *option)
+ {
+ buffer[0] = 0;
+
+ if (option->ShortName != '\0')
+ {
+ String::AppendFormat(buffer, bufferSize, "-%c, ", option->ShortName);
+ }
+
+ String::Append(buffer, bufferSize, "--");
+ String::Append(buffer, bufferSize, option->LongName);
+
+ switch (option->Type) {
+ case CMDLINE_TYPE_INTEGER:
+ String::Append(buffer, bufferSize, "=");
+ break;
+ case CMDLINE_TYPE_REAL:
+ String::Append(buffer, bufferSize, "=");
+ break;
+ case CMDLINE_TYPE_STRING:
+ String::Append(buffer, bufferSize, "=");
+ break;
+ }
+
+ return buffer;
+ }
+
+ const CommandLineCommand * FindCommandFor(const CommandLineCommand * commands, const char * const * arguments, int count)
+ {
+ // Check if there are any arguments
+ if (count == 0)
+ {
+ return nullptr;
+ }
+
+ // Check if options have started
+ const char * firstArgument = arguments[0];
+ if (firstArgument[0] == '-')
+ {
+ return nullptr;
+ }
+
+ // Search through defined commands for one that matches
+ const CommandLineCommand * fallback = nullptr;
+ const CommandLineCommand * command = commands;
+ for (; command->Name != nullptr; command++)
+ {
+ if (command->Name[0] == '\0')
+ {
+ // If we don't find a command, this should be used
+ fallback = command;
+ }
+ else if (String::Equals(command->Name, firstArgument))
+ {
+ if (command->SubCommands == nullptr)
+ {
+ // Found matching command
+ return command;
+ }
+ else
+ {
+ // Recurse for the sub command table
+ return FindCommandFor(command->SubCommands, &arguments[1], count - 1);
+ }
+ }
+ }
+
+ return fallback;
+ }
+}
+
+void CommandLineDisplayUsageFor(const char * command)
+{
+
+}
+
+extern "C"
+{
+ int cmdline_run(const char * * argv, int argc)
+ {
+ CommandLine::PrintHelp();
+ return 0;
+ }
+}
diff --git a/src/cmdline/CommandLine.hpp b/src/cmdline/CommandLine.hpp
new file mode 100644
index 0000000000..234eb9ceb6
--- /dev/null
+++ b/src/cmdline/CommandLine.hpp
@@ -0,0 +1,83 @@
+#pragma once
+
+extern "C"
+{
+ #include "../common.h"
+}
+
+/**
+ * Class for enumerating and retrieving values for a set of command line arguments.
+ */
+class CommandLineArgEnumerator
+{
+private:
+ const char * const * _arguments;
+ uint16 _count;
+ uint16 _index;
+
+public:
+ uint16 GetCount() const { return _count; }
+ uint16 GetIndex() const { return _index; }
+
+ CommandLineArgEnumerator(const char * const * arguments, int count);
+
+ void Reset();
+ bool TryPop();
+ bool TryPopInteger(sint32 * result);
+ bool TryPopReal(float * result);
+ bool TryPopString(const char * * result);
+};
+
+typedef int exitcode_t;
+typedef exitcode_t (*CommandLineFunc)(CommandLineArgEnumerator *);
+
+struct CommandLineExample
+{
+ const char * Arguments;
+ const char * Description;
+};
+
+struct CommandLineOptionDefinition
+{
+ uint8 Type;
+ void * OutAddress;
+ char ShortName;
+ const char * LongName;
+ const char * Description;
+};
+
+struct CommandLineCommand
+{
+ const char * Name;
+ const char * Parameters;
+ const CommandLineOptionDefinition * Options;
+ const CommandLineCommand * SubCommands;
+ CommandLineFunc Func;
+};
+
+enum
+{
+ CMDLINE_TYPE_SWITCH,
+ CMDLINE_TYPE_INTEGER,
+ CMDLINE_TYPE_REAL,
+ CMDLINE_TYPE_STRING,
+};
+
+constexpr char NAC = '\0';
+
+#define ExampleTableEnd { NULL, NULL }
+#define OptionTableEnd { UINT8_MAX, NULL, NAC, NULL, NULL }
+#define CommandTableEnd { NULL, NULL, NULL, NULL }
+
+#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
+{
+ void PrintHelp();
+}
diff --git a/src/cmdline/CommandLineDefinitions.cpp b/src/cmdline/CommandLineDefinitions.cpp
new file mode 100644
index 0000000000..68a0a9cc80
--- /dev/null
+++ b/src/cmdline/CommandLineDefinitions.cpp
@@ -0,0 +1,104 @@
+#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", "