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", " ", SpriteOptions, nullptr), + DefineCommand("build", " [silent]", SpriteOptions, nullptr), + CommandTableEnd +}; diff --git a/src/core/Console.hpp b/src/core/Console.hpp new file mode 100644 index 0000000000..ae7ee378aa --- /dev/null +++ b/src/core/Console.hpp @@ -0,0 +1,32 @@ +#pragma once + +extern "C" +{ + #include "../common.h" +} + +namespace Console +{ + void Write(const utf8 * str) + { + fputs(str, stdout); + } + + void WriteSpace(size_t count) + { + for (size_t i = 0; i < count; i++) + { + fputc(' ', stdout); + } + } + + void WriteLine() + { + puts(""); + } + + void WriteLine(const utf8 * str) + { + puts(str); + } +} diff --git a/src/core/String.hpp b/src/core/String.hpp new file mode 100644 index 0000000000..ae0219c28f --- /dev/null +++ b/src/core/String.hpp @@ -0,0 +1,80 @@ +#pragma once + +extern "C" +{ + #include "../common.h" + #include "../localisation/localisation.h" + #include "../util/util.h" +} + +namespace String +{ + bool Equals(const utf8 * a, const utf8 * b, bool ignoreCase = false) + { + if (a == b) return true; + if (a == nullptr || b == nullptr) return false; + + if (ignoreCase) + { + return _strcmpi(a, b) == 0; + } + else + { + return strcmp(a, b) == 0; + } + } + + size_t LengthOf(const utf8 * str) + { + return utf8_length(str); + } + + size_t SizeOf(const utf8 * str) + { + return strlen(str); + } + + utf8 * Append(utf8 * buffer, size_t bufferSize, const utf8 * src) + { + return safe_strcat(buffer, src, bufferSize); + } + + utf8 * Format(utf8 * buffer, size_t bufferSize, const utf8 * format, ...) + { + va_list args; + + va_start(args, format); + vsnprintf(buffer, bufferSize, format, args); + va_end(args); + + // Terminate buffer in case formatted string overflowed + buffer[bufferSize - 1] = '\0'; + + return buffer; + } + + utf8 * AppendFormat(utf8 * buffer, size_t bufferSize, const utf8 * format, ...) + { + va_list args; + + utf8 * dst = buffer; + size_t i; + for (i = 0; i < bufferSize; i++) + { + if (*dst == '\0') break; + dst++; + } + + if (i < bufferSize - 1) + { + va_start(args, format); + vsnprintf(buffer, bufferSize - i - 1, format, args); + va_end(args); + + // Terminate buffer in case formatted string overflowed + buffer[bufferSize - 1] = '\0'; + } + + return buffer; + } +}