diff --git a/FEXCore/Scripts/config_generator.py b/FEXCore/Scripts/config_generator.py index 320b425394..073960fb87 100644 --- a/FEXCore/Scripts/config_generator.py +++ b/FEXCore/Scripts/config_generator.py @@ -351,81 +351,57 @@ def print_config_option(type, group_name, json_name, default_value, short, choic output_argloader.write("\n"); -def print_argloader_options(options): - output_argloader.write("#ifdef BEFORE_PARSE\n") - output_argloader.write("#undef BEFORE_PARSE\n") +def print_argloader_options_map(options): + output_argloader.write("#ifdef ARG_TO_CONFIG\n") + output_argloader.write("#undef ARG_TO_CONFIG\n") for op_group, group_vals in options.items(): for op_key, op_vals in group_vals.items(): - default = op_vals["Default"] - if (op_vals["Type"] == "str" or op_vals["Type"] == "strarray" or op_vals["Type"] == "strenum"): - # Wrap the string argument in quotes - default = "\"" + default + "\"" + value_type = op_vals["Type"] - # Textual default rather than enum based - if ("TextDefault" in op_vals): - default = "\"" + op_vals["TextDefault"] + "\"" + ParserType = "ParserTypes::String" - short = None - choices = None + if value_type == "bool": + ParserType = "ParserTypes::BoolTrue" + + if value_type == "strenum": + ParserType = "ParserTypes::StringEnum" + + if value_type == "strarray": + ParserType = "ParserTypes::StringArray" + short = None if ("ShortArg" in op_vals): short = op_vals["ShortArg"] - if ("Choices" in op_vals): - choices = op_vals["Choices"] - print_config_option( - op_vals["Type"], - op_group, - op_key, - default, - short, - choices, - op_vals["Desc"]) + if short != None: + output_argloader.write("{{\"-{}\", FEXCore::Config::ConfigOption::CONFIG_{}, {}}},\n".format(short, op_key.upper(), ParserType)) - output_argloader.write("\n") + output_argloader.write("{{\"--{}\", FEXCore::Config::ConfigOption::CONFIG_{}, {}}},\n".format(op_key.lower(), op_key.upper(), ParserType)) + + if value_type == "bool": + output_argloader.write("{{\"--no-{}\", FEXCore::Config::ConfigOption::CONFIG_{}, ParserTypes::BoolFalse}},\n".format(op_key, op_key.upper())) output_argloader.write("#endif\n") -def print_parse_argloader_options(options): - output_argloader.write("#ifdef AFTER_PARSE\n") - output_argloader.write("#undef AFTER_PARSE\n") +def print_parse_argloader_enumparser_new(options): + output_argloader.write("#ifdef STR_ENUM_PARSE\n") + output_argloader.write("#undef STR_ENUM_PARSE\n") for op_group, group_vals in options.items(): for op_key, op_vals in group_vals.items(): - output_argloader.write("if (Options.is_set_by_user(\"{0}\")) {{\n".format(op_key)) - value_type = op_vals["Type"] - NeedsString = False - conversion_func = "fextl::fmt::format(\"{}\", " - if ("ArgumentHandler" in op_vals): - NeedsString = True - conversion_func = "FEXCore::Config::Handler::{0}(".format(op_vals["ArgumentHandler"]) - if (value_type == "str"): - NeedsString = True - conversion_func = "std::move(" - if (value_type == "bool"): - # boolean values need a decimal specifier. Otherwise fmt prints strings. - conversion_func = "fextl::fmt::format(\"{:d}\", " if (value_type == "strenum"): - output_argloader.write("\tfextl::string UserValue = Options[\"{0}\"];\n".format(op_key)) - output_argloader.write("\tSet(FEXCore::Config::ConfigOption::CONFIG_{}, FEXCore::Config::EnumParser(FEXCore::Config::{}_EnumPairs, UserValue));\n".format(op_key.upper(), op_key, op_key, op_key)) - elif (value_type == "strarray"): - # these need a bit more help - output_argloader.write("\tauto Array = Options.all(\"{0}\");\n".format(op_key)) - output_argloader.write("\tfor (auto iter = Array.begin(); iter != Array.end(); ++iter) {\n") - output_argloader.write("\t\tSet(FEXCore::Config::ConfigOption::CONFIG_{0}, *iter);\n".format(op_key.upper())) - output_argloader.write("\t}\n") - else: - if (NeedsString): - output_argloader.write("\tfextl::string UserValue = Options[\"{0}\"];\n".format(op_key)) - else: - output_argloader.write("\t{0} UserValue = Options.get(\"{1}\");\n".format(value_type, op_key)) + output_argloader.write("case FEXCore::Config::ConfigOption::CONFIG_{}: {{\n".format(op_key.upper())) + output_argloader.write("\tauto Converted = FEXCore::Config::EnumParser(FEXCore::Config::{}_EnumPairs, SecondArg);\n".format(op_key, op_key, op_key)) + output_argloader.write("\tif (Converted) { SecondArg = *Converted; }\n") + output_argloader.write("\tfextl::fmt::print(\"enum:{}\\n\", SecondArg);\n") - output_argloader.write("\tSet(FEXCore::Config::ConfigOption::CONFIG_{0}, {1}UserValue));\n".format(op_key.upper(), conversion_func)) - output_argloader.write("}\n") + output_argloader.write("\tLoader->SetArg(FEXCore::Config::ConfigOption::CONFIG_{}, SecondArg);\n".format(op_key.upper())) + output_argloader.write("break;\n".format(op_key.upper())) + output_argloader.write("}\n") - output_argloader.write("#endif\n") + output_argloader.write("#endif\n") def print_parse_envloader_options(options): output_argloader.write("#ifdef ENVLOADER\n") @@ -574,8 +550,8 @@ def check_for_duplicate_options(options): # Generate argument loader code output_argloader = open(output_argumentloader_filename, "w") -print_argloader_options(options); -print_parse_argloader_options(options); +print_argloader_options_map(options); +print_parse_argloader_enumparser_new(options); # Generate environment loader code print_parse_envloader_options(options); diff --git a/Source/Common/ArgumentLoader.cpp b/Source/Common/ArgumentLoader.cpp index e4ea65addd..2e3921f25f 100644 --- a/Source/Common/ArgumentLoader.cpp +++ b/Source/Common/ArgumentLoader.cpp @@ -5,47 +5,240 @@ #include #include -#include "cpp-optparse/OptionParser.h" #include "git_version.h" -#include - namespace FEX::ArgLoader { -void FEX::ArgLoader::ArgLoader::Load() { - RemainingArgs.clear(); - ProgramArguments.clear(); - if (Type == LoadType::WITHOUT_FEXLOADER_PARSER) { - LoadWithoutArguments(); - return; - } +enum class ParserTypes : uint32_t { + String, + BoolTrue, + BoolFalse, + StringEnum, + StringArray, + Max, +}; - optparse::OptionParser Parser {}; - Parser.version("FEX-Emu (" GIT_DESCRIBE_STRING ") "); - optparse::OptionGroup CPUGroup(Parser, "CPU Core options"); - optparse::OptionGroup EmulationGroup(Parser, "Emulation options"); - optparse::OptionGroup DebugGroup(Parser, "Debug options"); - optparse::OptionGroup HacksGroup(Parser, "Hacks options"); - optparse::OptionGroup MiscGroup(Parser, "Miscellaneous options"); - optparse::OptionGroup LoggingGroup(Parser, "Logging options"); +struct OptionDetails { + std::string_view Arg; + FEXCore::Config::ConfigOption Option; + ParserTypes ParseType; +}; -#define BEFORE_PARSE +constexpr static OptionDetails ArgToOption[] = { +#define ARG_TO_CONFIG #include +}; + +const OptionDetails* FindOption(std::string_view Argument) { + for (auto& Arg : ArgToOption) { + if (Arg.Arg == Argument) { + return &Arg; + } + } + + return nullptr; +} + +void PrintHelp() { + const char* Arguments[3] {}; + Arguments[0] = "man"; + Arguments[1] = "FEX"; + Arguments[2] = nullptr; + + execvp("man", (char* const*)Arguments); +} + +void ExitWithError(std::string_view Error) { + fextl::fmt::print("Error: {}\n", Error); + std::exit(1); +} + +class ArgParser final { +public: + ArgParser(FEX::ArgLoader::ArgLoader* Loader) + : Loader {Loader} {} + + void Parse(int argc, char** argv); + void Version(std::string_view version) { + _Version = version; + } + + fextl::vector GetGuestArgs() { + return std::move(RemainingArgs); + } + fextl::vector GetParsedArgs() { + return std::move(ProgramArguments); + } - Parser.add_option_group(CPUGroup); - Parser.add_option_group(EmulationGroup); - Parser.add_option_group(DebugGroup); - Parser.add_option_group(HacksGroup); - Parser.add_option_group(MiscGroup); - Parser.add_option_group(LoggingGroup); +private: + FEX::ArgLoader::ArgLoader* Loader; + std::string_view _Version {}; + fextl::vector RemainingArgs {}; + fextl::vector ProgramArguments {}; + using ParseArgHandler = void (ArgParser::*)(std::string_view Arg, std::string_view SecondArg, const OptionDetails* Details); + + void ParseArgument_String(std::string_view Arg, std::string_view SecondArg, const OptionDetails* Details) { + Loader->SetArg(Details->Option, SecondArg); + } + + void ParseArgument_BoolTrue(std::string_view Arg, std::string_view SecondArg, const OptionDetails* Details) { + using namespace std::literals; + Loader->SetArg(Details->Option, "1"sv); + } - optparse::Values Options = Parser.parse_args(argc, argv); + void ParseArgument_BoolFalse(std::string_view Arg, std::string_view SecondArg, const OptionDetails* Details) { + using namespace std::literals; + Loader->SetArg(Details->Option, "0"sv); + } - using int32 = int32_t; - using uint32 = uint32_t; -#define AFTER_PARSE + void ParseArgument_StringEnum(std::string_view Arg, std::string_view SecondArg, const OptionDetails* Details) { + switch (Details->Option) { +#define STR_ENUM_PARSE #include - RemainingArgs = Parser.args(); - ProgramArguments = Parser.parsed_args(); + [[unlikely]] default: + ExitWithError(fextl::fmt::format("Unknown strenum argument: {}", Arg)); + break; + } + } + + constexpr static std::array Parser = { + &ArgParser::ParseArgument_String, + &ArgParser::ParseArgument_BoolTrue, + &ArgParser::ParseArgument_BoolFalse, + &ArgParser::ParseArgument_StringEnum, + // Behaves the same as string but appends multiple. + &ArgParser::ParseArgument_String, + }; + + void ParseArgument(std::string_view Arg, std::string_view SecondArg, const OptionDetails* Details) { + std::invoke(Parser[FEXCore::ToUnderlying(Details->ParseType)], this, Arg, SecondArg, Details); + } +}; + +bool NeedsArg(const OptionDetails* Details) { + return Details->ParseType == ParserTypes::String || Details->ParseType == ParserTypes::StringEnum || Details->ParseType == ParserTypes::StringArray; +} + +void ArgParser::Parse(int argc, char** argv) { + // Skip argv[0] + int ArgParsed = 1; + for (; ArgParsed < argc; ++ArgParsed) { + std::string_view Arg = argv[ArgParsed]; + + // Special case version and help + if (Arg == "--version") [[unlikely]] { + fextl::fmt::print("{}\n", _Version); + std::exit(0); + } + + if (Arg == "-h" || Arg == "--help") [[unlikely]] { + PrintHelp(); + std::exit(0); + } + + if (Arg == "--") { + // Special case break. Remaining arguments get passed to guest. + ++ArgParsed; + break; + } + + const bool IsShort = Arg.find("--", 0, 2) == Arg.npos && Arg.find("-", 0, 1) == 0; + const bool IsLong = Arg.find("--", 0, 2) == 0; + + std::string_view ArgFirst {}; + std::string_view ArgSecond {}; + + const OptionDetails* OptionDetails {}; + + if (IsShort) { + ArgFirst = Arg; + + OptionDetails = FindOption(ArgFirst); + + if (OptionDetails == nullptr) [[unlikely]] { + ExitWithError(fextl::fmt::format("Unsupported argument: {}", Arg)); + } + + if (NeedsArg(OptionDetails)) { + ++ArgParsed; + ArgSecond = argv[ArgParsed]; + } + } else if (IsLong) { + const auto Split = Arg.find_first_of('='); + bool NeedsSplitArg {}; + if (Split == Arg.npos) { + ArgFirst = Arg; + OptionDetails = FindOption(Arg); + + if (OptionDetails == nullptr) [[unlikely]] { + ExitWithError(fextl::fmt::format("Unsupported argument: {}", Arg)); + } + + NeedsSplitArg = NeedsArg(OptionDetails); + } else { + ArgFirst = Arg.substr(0, Split); + OptionDetails = FindOption(ArgFirst); + + if (OptionDetails == nullptr) [[unlikely]] { + ExitWithError(fextl::fmt::format("Unsupported argument: {}", Arg)); + } + + NeedsSplitArg = NeedsArg(OptionDetails); + } + + if (NeedsSplitArg && Split == Arg.npos) [[unlikely]] { + ExitWithError(fextl::fmt::format("{} needs argument", Arg)); + } + + if (!NeedsSplitArg && Split != Arg.npos) [[unlikely]] { + ExitWithError(fextl::fmt::format("{} can't have argument", Arg)); + } + + if (NeedsSplitArg) { + ArgSecond = Arg.substr(Split + 1, Arg.size()); + + if (ArgSecond.empty()) [[unlikely]] { + ExitWithError(fextl::fmt::format("{} needs argument", Arg)); + } + } + } + + if (ProgramArguments.empty() && OptionDetails == nullptr) { + // In the special case that we hit a parse error and we haven't parsed any arguments, pass everything. + // This handles the typical case eg: `FEXLoader /usr/bin/ls /`. + // Some would claim that `--` should be used to split FEX arguments from sub-application arguments. + break; + } + + // Unsupported FEX argument. Error. + if (ProgramArguments.empty() && OptionDetails == nullptr) [[unlikely]] { + ExitWithError(fextl::fmt::format("Unsupported argument: {}", Arg)); + } + + // Save the FEX argument. + ProgramArguments.emplace_back(argv[ArgParsed]); + + // Now parse the argument. + ParseArgument(Arg, ArgSecond, OptionDetails); + } + + // Pass any remaining arguments to guest application + for (; ArgParsed < argc; ++ArgParsed) { + RemainingArgs.emplace_back(argv[ArgParsed]); + } +} + +void FEX::ArgLoader::ArgLoader::Load() { + RemainingArgs.clear(); + ProgramArguments.clear(); + ArgParser Parser(this); + Parser.Version("FEX-Emu (" GIT_DESCRIBE_STRING ") "); + Parser.Parse(argc, argv); + RemainingArgs = Parser.GetGuestArgs(); + ProgramArguments = Parser.GetParsedArgs(); +} + +void FEX::ArgLoader::ArgLoader::SetArg(FEXCore::Config::ConfigOption Option, std::string_view Arg) { + Set(Option, Arg); } void FEX::ArgLoader::ArgLoader::LoadWithoutArguments() { diff --git a/Source/Common/ArgumentLoader.h b/Source/Common/ArgumentLoader.h index 459fe72c36..a7e01247c7 100644 --- a/Source/Common/ArgumentLoader.h +++ b/Source/Common/ArgumentLoader.h @@ -15,10 +15,14 @@ class ArgLoader final : public FEXCore::Config::Layer { explicit ArgLoader(LoadType Type, int argc, char** argv) : FEXCore::Config::Layer(FEXCore::Config::LayerType::LAYER_ARGUMENTS) - , Type {Type} , argc {argc} , argv {argv} { - Load(); + + if (Type == LoadType::WITHOUT_FEXLOADER_PARSER) { + LoadWithoutArguments(); + } else { + Load(); + } } void Load() override; @@ -26,7 +30,8 @@ class ArgLoader final : public FEXCore::Config::Layer { fextl::vector Get() { return RemainingArgs; } - fextl::vector GetParsedArgs() { + + fextl::vector GetParsedArgs() { return ProgramArguments; } @@ -34,13 +39,15 @@ class ArgLoader final : public FEXCore::Config::Layer { return Type; } + void SetArg(FEXCore::Config::ConfigOption Option, std::string_view Arg); + private: LoadType Type; int argc {}; char** argv {}; fextl::vector RemainingArgs {}; - fextl::vector ProgramArguments {}; + fextl::vector ProgramArguments {}; }; } // namespace FEX::ArgLoader diff --git a/Source/Common/CMakeLists.txt b/Source/Common/CMakeLists.txt index 241df8e5aa..b8e926dc13 100644 --- a/Source/Common/CMakeLists.txt +++ b/Source/Common/CMakeLists.txt @@ -16,7 +16,6 @@ if (NOT MINGW_BUILD) endif() add_library(${NAME} STATIC ${SRCS}) -target_link_libraries(${NAME} FEXCore_Base cpp-optparse tiny-json FEXHeaderUtils) -target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/External/cpp-optparse/) +target_link_libraries(${NAME} FEXCore_Base tiny-json FEXHeaderUtils) target_include_directories(${NAME} PRIVATE ${CMAKE_BINARY_DIR}/generated) target_include_directories(${NAME} PRIVATE ${CMAKE_SOURCE_DIR}/External/xbyak/) diff --git a/Source/Tools/FEXGetConfig/CMakeLists.txt b/Source/Tools/FEXGetConfig/CMakeLists.txt index c858bd9566..2a792e0e11 100644 --- a/Source/Tools/FEXGetConfig/CMakeLists.txt +++ b/Source/Tools/FEXGetConfig/CMakeLists.txt @@ -3,7 +3,7 @@ set(SRCS Main.cpp) add_executable(${NAME} ${SRCS}) -list(APPEND LIBS Common) +list(APPEND LIBS Common cpp-optparse) if (CMAKE_BUILD_TYPE MATCHES "RELEASE") target_link_options(${NAME} diff --git a/Source/Tools/FEXLoader/ELFCodeLoader.h b/Source/Tools/FEXLoader/ELFCodeLoader.h index 8e24f75b83..7298a6554b 100644 --- a/Source/Tools/FEXLoader/ELFCodeLoader.h +++ b/Source/Tools/FEXLoader/ELFCodeLoader.h @@ -232,7 +232,7 @@ class ELFCodeLoader final : public FEX::CodeLoader { fextl::vector Sections; ELFCodeLoader(const fextl::string& Filename, int ProgramFDFromEnv, const fextl::string& RootFS, - [[maybe_unused]] const fextl::vector& args, const fextl::vector& ParsedArgs, + [[maybe_unused]] const fextl::vector& args, const fextl::vector& ParsedArgs, char** const envp = nullptr, FEXCore::Config::Value* AdditionalEnvp = nullptr) : Args {args} { @@ -328,9 +328,7 @@ class ELFCodeLoader final : public FEX::CodeLoader { EnvironmentBackingSize += EnvironmentVariables[i].size() + 1; } - for (auto& Arg : ParsedArgs) { - LoaderArgs.emplace_back(Arg.c_str()); - } + std::move(ParsedArgs.begin(), ParsedArgs.end(), std::back_inserter(LoaderArgs)); } void FreeSections() { diff --git a/Source/Tools/FEXRootFSFetcher/CMakeLists.txt b/Source/Tools/FEXRootFSFetcher/CMakeLists.txt index 69fdf8418f..8c0d04b660 100644 --- a/Source/Tools/FEXRootFSFetcher/CMakeLists.txt +++ b/Source/Tools/FEXRootFSFetcher/CMakeLists.txt @@ -3,7 +3,7 @@ set(SRCS Main.cpp XXFileHash.cpp) add_executable(${NAME} ${SRCS}) -list(APPEND LIBS FEXCore Common xxHash::xxhash) +list(APPEND LIBS FEXCore Common xxHash::xxhash cpp-optparse) target_include_directories(${NAME} PRIVATE ${CMAKE_SOURCE_DIR}/Source/) diff --git a/Source/Tools/FEXServer/CMakeLists.txt b/Source/Tools/FEXServer/CMakeLists.txt index 1190618d19..d6ebaa0319 100644 --- a/Source/Tools/FEXServer/CMakeLists.txt +++ b/Source/Tools/FEXServer/CMakeLists.txt @@ -12,7 +12,7 @@ target_include_directories(${NAME} PRIVATE ${CMAKE_BINARY_DIR}/generated ${CMAKE_SOURCE_DIR}/Source/) -target_link_libraries(${NAME} PRIVATE FEXCore Common ${PTHREAD_LIB}) +target_link_libraries(${NAME} PRIVATE FEXCore Common cpp-optparse ${PTHREAD_LIB}) if (CMAKE_BUILD_TYPE MATCHES "RELEASE") target_link_options(${NAME}