[App/Base] Rewrite Config and CVar implementation

Adds support for "requires restart" config values

[App/Base] Rewrite Config and CVar implementation

Adds support for "requires restart" config values
This commit is contained in:
Satori 2020-07-04 11:58:07 +01:00
parent 9f789e01b6
commit 29ea7eb28e
10 changed files with 511 additions and 360 deletions

View File

@ -228,7 +228,7 @@ int xenia_main(const std::vector<std::string>& args) {
storage_root = std::filesystem::absolute(storage_root);
XELOGI("Storage root: {}", xe::path_to_utf8(storage_root));
config::SetupConfig(storage_root);
Config::Instance().SetupConfig(storage_root);
std::filesystem::path content_root = cvars::content_root;
if (content_root.empty()) {

View File

@ -18,70 +18,9 @@ namespace utfcpp = utf8;
using u8_citer = utfcpp::iterator<std::string_view::const_iterator>;
namespace xe {
namespace cvar {
cxxopts::Options options("xenia", "Xbox 360 Emulator");
std::map<std::string, ICommandVar*>* CmdVars;
std::map<std::string, IConfigVar*>* ConfigVars;
void PrintHelpAndExit() {
std::cout << options.help({""}) << std::endl;
std::cout << "For the full list of command line arguments, see xenia.cfg."
<< std::endl;
exit(0);
}
void ParseLaunchArguments(int& argc, char**& argv,
const std::string_view positional_help,
const std::vector<std::string>& positional_options) {
options.add_options()("help", "Prints help and exit.");
if (!CmdVars) {
CmdVars = new std::map<std::string, ICommandVar*>();
}
if (!ConfigVars) {
ConfigVars = new std::map<std::string, IConfigVar*>();
}
for (auto& it : *CmdVars) {
auto cmdVar = it.second;
cmdVar->AddToLaunchOptions(&options);
}
for (const auto& it : *ConfigVars) {
auto configVar = it.second;
configVar->AddToLaunchOptions(&options);
}
try {
options.positional_help(std::string(positional_help));
options.parse_positional(positional_options);
auto result = options.parse(argc, argv);
if (result.count("help")) {
PrintHelpAndExit();
}
for (auto& it : *CmdVars) {
auto cmdVar = static_cast<ICommandVar*>(it.second);
if (result.count(cmdVar->name())) {
cmdVar->LoadFromLaunchOptions(&result);
}
}
for (auto& it : *ConfigVars) {
auto configVar = static_cast<IConfigVar*>(it.second);
if (result.count(configVar->name())) {
configVar->LoadFromLaunchOptions(&result);
}
}
} catch (const cxxopts::OptionException& e) {
std::cout << e.what() << std::endl;
PrintHelpAndExit();
}
}
namespace toml {
std::string EscapeBasicString(const std::string_view view) {
@ -199,3 +138,4 @@ std::string EscapeString(const std::string_view view) {
} // namespace toml
} // namespace cvar
} // namespace xe

View File

@ -17,13 +17,16 @@
#include "cpptoml/include/cpptoml.h"
#include "cxxopts/include/cxxopts.hpp"
#include "xenia/base/filesystem.h"
#include "xenia/base/string_util.h"
#include "xenia/config.h"
namespace xe {
namespace cvar {
namespace toml {
std::string EscapeString(const std::string_view str);
std::string EscapeString(const std::string_view view);
}
class ICommandVar {
@ -40,6 +43,7 @@ class IConfigVar : virtual public ICommandVar {
public:
virtual const std::string& category() const = 0;
virtual bool is_transient() const = 0;
virtual bool requires_restart() const = 0;
virtual std::string config_value() const = 0;
virtual void LoadConfigValue(std::shared_ptr<cpptoml::base> result) = 0;
virtual void LoadGameConfigValue(std::shared_ptr<cpptoml::base> result) = 0;
@ -48,282 +52,173 @@ class IConfigVar : virtual public ICommandVar {
template <class T>
class CommandVar : virtual public ICommandVar {
public:
CommandVar<T>(const char* name, T* default_value, const char* description);
const std::string& name() const override;
const std::string& description() const override;
CommandVar<T>(std::string_view name, T* default_value,
std::string_view description);
const std::string& name() const override { return name_; }
const std::string& description() const override { return description_; }
void AddToLaunchOptions(cxxopts::Options* options) override;
void LoadFromLaunchOptions(cxxopts::ParseResult* result) override;
T* current_value() { return current_value_; }
T* command_line_value() { return commandline_value_.get(); }
const T& default_value() const { return default_value_; }
protected:
void set_current_value(const T& val) { *current_value_ = val; }
void set_command_line_value(const T& val) {
commandline_value_ = std::make_unique<T>(val);
}
T Convert(std::string_view val) const {
return xe::string_util::from_string<T>(val);
}
static std::string ToString(const T& val) { return std::to_string(val); }
void UpdateValue() override;
private:
std::string name_;
T default_value_;
T* current_value_;
std::unique_ptr<T> commandline_value_;
std::string description_;
T Convert(std::string val);
static std::string ToString(T val);
void SetValue(T val);
void SetCommandLineValue(T val);
void UpdateValue() override;
};
#pragma warning(push)
#pragma warning(disable : 4250)
template <class T>
class ConfigVar : public CommandVar<T>, virtual public IConfigVar {
class ConfigVar final : public CommandVar<T>, virtual public IConfigVar {
public:
ConfigVar<T>(const char* name, T* default_value, const char* description,
const char* category, bool is_transient);
std::string config_value() const override;
const std::string& category() const override;
bool is_transient() const override;
ConfigVar<T>(std::string_view name, T* default_value,
std::string_view description, std::string_view category,
bool is_transient, bool requires_restart);
std::string config_value() const override {
if (config_value_) return this->ToString(*config_value_);
return this->ToString(this->default_value());
}
const std::string& category() const override { return category_; }
bool is_transient() const override { return is_transient_; }
bool requires_restart() const override { return requires_restart_; }
void set_config_value(const T& val) {
config_value_ = std::make_unique<T>(val);
UpdateValue();
}
void set_game_config_value(const T& val) {
game_config_value_ = std::make_unique<T>(val);
UpdateValue();
}
void AddToLaunchOptions(cxxopts::Options* options) override;
void LoadConfigValue(std::shared_ptr<cpptoml::base> result) override;
void LoadGameConfigValue(std::shared_ptr<cpptoml::base> result) override;
void SetConfigValue(T val);
void SetGameConfigValue(T val);
private:
void UpdateValue() override;
std::string category_;
bool is_transient_;
bool requires_restart_;
std::unique_ptr<T> config_value_ = nullptr;
std::unique_ptr<T> game_config_value_ = nullptr;
void UpdateValue() override;
};
#pragma warning(pop)
template <class T>
const std::string& CommandVar<T>::name() const {
return name_;
}
template <class T>
const std::string& CommandVar<T>::description() const {
return description_;
}
template <class T>
void CommandVar<T>::AddToLaunchOptions(cxxopts::Options* options) {
options->add_options()(name_, description_, cxxopts::value<T>());
}
template <>
inline void CommandVar<std::filesystem::path>::AddToLaunchOptions(
cxxopts::Options* options) {
options->add_options()(name_, description_, cxxopts::value<std::string>());
}
template <class T>
void ConfigVar<T>::AddToLaunchOptions(cxxopts::Options* options) {
options->add_options(category_)(this->name_, this->description_,
cxxopts::value<T>());
}
template <>
inline void ConfigVar<std::filesystem::path>::AddToLaunchOptions(
cxxopts::Options* options) {
options->add_options(category_)(this->name_, this->description_,
cxxopts::value<std::string>());
}
template <class T>
void CommandVar<T>::LoadFromLaunchOptions(cxxopts::ParseResult* result) {
T value = (*result)[name_].template as<T>();
SetCommandLineValue(value);
}
template <>
inline void CommandVar<std::filesystem::path>::LoadFromLaunchOptions(
cxxopts::ParseResult* result) {
std::string value = (*result)[name_].template as<std::string>();
SetCommandLineValue(value);
}
template <class T>
void ConfigVar<T>::LoadConfigValue(std::shared_ptr<cpptoml::base> result) {
SetConfigValue(*cpptoml::get_impl<T>(result));
}
template <>
inline void ConfigVar<std::filesystem::path>::LoadConfigValue(
std::shared_ptr<cpptoml::base> result) {
SetConfigValue(
xe::utf8::fix_path_separators(*cpptoml::get_impl<std::string>(result)));
}
template <class T>
void ConfigVar<T>::LoadGameConfigValue(std::shared_ptr<cpptoml::base> result) {
SetGameConfigValue(*cpptoml::get_impl<T>(result));
}
template <>
inline void ConfigVar<std::filesystem::path>::LoadGameConfigValue(
std::shared_ptr<cpptoml::base> result) {
SetGameConfigValue(
xe::utf8::fix_path_separators(*cpptoml::get_impl<std::string>(result)));
}
template <class T>
CommandVar<T>::CommandVar(const char* name, T* default_value,
const char* description)
: name_(name),
default_value_(*default_value),
description_(description),
current_value_(default_value) {}
template <class T>
ConfigVar<T>::ConfigVar(const char* name, T* default_value,
const char* description, const char* category,
bool is_transient)
: CommandVar<T>(name, default_value, description),
category_(category),
is_transient_(is_transient) {}
template <class T>
void CommandVar<T>::UpdateValue() {
if (commandline_value_) return SetValue(*commandline_value_);
return SetValue(default_value_);
}
template <class T>
void ConfigVar<T>::UpdateValue() {
if (this->commandline_value_) {
return this->SetValue(*this->commandline_value_);
}
if (game_config_value_) return this->SetValue(*game_config_value_);
if (config_value_) return this->SetValue(*config_value_);
return this->SetValue(this->default_value_);
}
template <class T>
T CommandVar<T>::Convert(std::string val) {
return xe::string_util::from_string<T>(val);
}
template <>
inline std::string CommandVar<std::string>::Convert(std::string val) {
return val;
}
template <>
inline std::filesystem::path CommandVar<std::filesystem::path>::Convert(
std::string val) {
return xe::to_path(val);
}
template <>
inline std::string CommandVar<bool>::ToString(bool val) {
return val ? "true" : "false";
}
template <>
inline std::string CommandVar<std::string>::ToString(std::string val) {
return toml::EscapeString(val);
}
template <>
inline std::string CommandVar<std::filesystem::path>::ToString(
std::filesystem::path val) {
return toml::EscapeString(
xe::utf8::fix_path_separators(xe::path_to_utf8(val), '/'));
}
template <class T>
std::string CommandVar<T>::ToString(T val) {
return std::to_string(val);
}
template <class T>
void CommandVar<T>::SetValue(T val) {
*current_value_ = val;
}
template <class T>
const std::string& ConfigVar<T>::category() const {
return category_;
}
template <class T>
bool ConfigVar<T>::is_transient() const {
return is_transient_;
}
template <class T>
std::string ConfigVar<T>::config_value() const {
if (config_value_) return this->ToString(*config_value_);
return this->ToString(this->default_value_);
}
template <class T>
void CommandVar<T>::SetCommandLineValue(const T val) {
commandline_value_ = std::make_unique<T>(val);
UpdateValue();
}
template <class T>
void ConfigVar<T>::SetConfigValue(T val) {
config_value_ = std::make_unique<T>(val);
UpdateValue();
}
template <class T>
void ConfigVar<T>::SetGameConfigValue(T val) {
game_config_value_ = std::make_unique<T>(val);
UpdateValue();
}
extern std::map<std::string, ICommandVar*>* CmdVars;
extern std::map<std::string, IConfigVar*>* ConfigVars;
inline void AddConfigVar(IConfigVar* cv) {
if (!ConfigVars) ConfigVars = new std::map<std::string, IConfigVar*>();
ConfigVars->insert(std::pair<std::string, IConfigVar*>(cv->name(), cv));
}
inline void AddCommandVar(ICommandVar* cv) {
if (!CmdVars) CmdVars = new std::map<std::string, ICommandVar*>();
CmdVars->insert(std::pair<std::string, ICommandVar*>(cv->name(), cv));
}
void ParseLaunchArguments(int& argc, char**& argv,
const std::string_view positional_help,
const std::vector<std::string>& positional_options);
template <typename T>
T* define_configvar(const char* name, T* default_value, const char* description,
const char* category, bool is_transient) {
IConfigVar* cfgVar = new ConfigVar<T>(name, default_value, description,
category, is_transient);
AddConfigVar(cfgVar);
return default_value;
ConfigVar<T>* register_configvar(std::string_view name, T* default_value,
std::string_view description,
std::string_view category, bool is_transient,
bool requires_restart) {
// config instance claims ownership of this pointer
ConfigVar<T>* var =
new ConfigVar<T>{name, default_value, description,
category, is_transient, requires_restart};
return Config::Instance().RegisterConfigVar<T>(var);
}
template <typename T>
T* define_cmdvar(const char* name, T* default_value, const char* description) {
ICommandVar* cmdVar = new CommandVar<T>(name, default_value, description);
AddCommandVar(cmdVar);
return default_value;
CommandVar<T>* register_commandvar(std::string_view name, T* default_value,
std::string_view description) {
// config instance claims ownership of this pointer
CommandVar<T>* var = new CommandVar<T>{name, default_value, description};
return Config::Instance().RegisterCommandVar<T>(var);
}
#define DEFINE_bool(name, default_value, description, category) \
DEFINE_CVar(name, default_value, description, category, false, bool)
DEFINE_CVar(name, default_value, description, category, false, false, bool)
#define DEFINE_int32(name, default_value, description, category) \
DEFINE_CVar(name, default_value, description, category, false, int32_t)
DEFINE_CVar(name, default_value, description, category, false, false, int32_t)
#define DEFINE_uint64(name, default_value, description, category) \
DEFINE_CVar(name, default_value, description, category, false, uint64_t)
#define DEFINE_uint64(name, default_value, description, category) \
DEFINE_CVar(name, default_value, description, category, false, false, \
uint64_t)
#define DEFINE_double(name, default_value, description, category) \
DEFINE_CVar(name, default_value, description, category, false, double)
DEFINE_CVar(name, default_value, description, category, false, false, double)
#define DEFINE_string(name, default_value, description, category) \
DEFINE_CVar(name, default_value, description, category, false, std::string)
#define DEFINE_string(name, default_value, description, category) \
DEFINE_CVar(name, default_value, description, category, false, false, \
std::string)
#define DEFINE_path(name, default_value, description, category) \
DEFINE_CVar(name, default_value, description, category, false, \
#define DEFINE_path(name, default_value, description, category) \
DEFINE_CVar(name, default_value, description, category, false, false, \
std::filesystem::path)
#define DEFINE_unrestartable_bool(name, default_value, description, category) \
DEFINE_CVar(name, default_value, description, category, false, true, bool)
#define DEFINE_unrestartable_int32(name, default_value, description, category) \
DEFINE_CVar(name, default_value, description, category, false, true, int32_t)
#define DEFINE_unrestartable_uint64(name, default_value, description, \
category) \
DEFINE_CVar(name, default_value, description, category, false, true, uint64_t)
#define DEFINE_unrestartable_double(name, default_value, description, \
category) \
DEFINE_CVar(name, default_value, description, category, false, true, double)
#define DEFINE_unrestartable_string(name, default_value, description, \
category) \
DEFINE_CVar(name, default_value, description, category, false, true, \
std::string)
#define DEFINE_unrestartable_path(name, default_value, description, category) \
DEFINE_CVar(name, default_value, description, category, false, true, \
std::filesystem::path)
#define DEFINE_transient_string(name, default_value, description, category) \
DEFINE_CVar(name, default_value, description, category, true, std::string)
DEFINE_CVar(name, default_value, description, category, true, false, \
std::string)
#define DEFINE_transient_path(name, default_value, description, category) \
DEFINE_CVar(name, default_value, description, category, true, \
DEFINE_CVar(name, default_value, description, category, true, false, \
std::filesystem::path)
#define DEFINE_CVar(name, default_value, description, category, is_transient, \
type) \
namespace cvars { \
requires_restart, type) \
namespace xe::cvars { \
type name = default_value; \
} \
namespace cv { \
static auto cv_##name = cvar::define_configvar( \
#name, &cvars::name, description, category, is_transient); \
}
static auto cv_##name = xe::cvar::register_configvar<type>( \
#name, &cvars::name, description, category, is_transient, \
requires_restart); \
} // namespace cvars
// CmdVars can only be strings for now, we don't need any others
#define CmdVar(name, default_value, description) \
namespace cvars { \
std::string name = default_value; \
} \
namespace cv { \
static auto cv_##name = \
cvar::define_cmdvar(#name, &cvars::name, description); \
}
#define CmdVar(name, default_value, description) \
namespace xe::cvars { \
std::string name = default_value; \
static auto cmdvar_##name = xe::cvar::register_commandvar<std::string>( \
#name, &cvars::name, description); \
} // namespace cvars
#define DECLARE_bool(name) DECLARE_CVar(name, bool)
@ -338,10 +233,14 @@ T* define_cmdvar(const char* name, T* default_value, const char* description) {
#define DECLARE_path(name) DECLARE_CVar(name, std::filesystem::path)
#define DECLARE_CVar(name, type) \
namespace cvars { \
namespace xe::cvars { \
extern type name; \
}
// load template implementations
#include "cvar.inc"
} // namespace cvar
} // namespace xe
#endif // XENIA_CVAR_H_

117
src/xenia/base/cvar.inc Normal file
View File

@ -0,0 +1,117 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
template <class T>
void CommandVar<T>::AddToLaunchOptions(cxxopts::Options* options) {
options->add_options()(name_, description_, cxxopts::value<T>());
}
template <>
inline void CommandVar<std::filesystem::path>::AddToLaunchOptions(
cxxopts::Options* options) {
options->add_options()(name_, description_, cxxopts::value<std::string>());
}
template <class T>
void ConfigVar<T>::AddToLaunchOptions(cxxopts::Options* options) {
options->add_options(category_)(this->name(), this->description(),
cxxopts::value<T>());
}
template <>
inline void ConfigVar<std::filesystem::path>::AddToLaunchOptions(
cxxopts::Options* options) {
options->add_options(category_)(this->name(), this->description(),
cxxopts::value<std::string>());
}
template <class T>
void CommandVar<T>::LoadFromLaunchOptions(cxxopts::ParseResult* result) {
T value = (*result)[name_].as<T>();
set_command_line_value(value);
}
template <>
inline void CommandVar<std::filesystem::path>::LoadFromLaunchOptions(
cxxopts::ParseResult* result) {
std::string value = (*result)[name_].as<std::string>();
set_command_line_value(value);
}
template <class T>
void ConfigVar<T>::LoadConfigValue(std::shared_ptr<cpptoml::base> result) {
set_config_value(*cpptoml::get_impl<T>(result));
}
template <>
inline void ConfigVar<std::filesystem::path>::LoadConfigValue(
std::shared_ptr<cpptoml::base> result) {
set_config_value(
xe::utf8::fix_path_separators(*cpptoml::get_impl<std::string>(result)));
}
template <class T>
void ConfigVar<T>::LoadGameConfigValue(std::shared_ptr<cpptoml::base> result) {
set_game_config_value(*cpptoml::get_impl<T>(result));
}
template <>
inline void ConfigVar<std::filesystem::path>::LoadGameConfigValue(
std::shared_ptr<cpptoml::base> result) {
set_game_config_value(
xe::utf8::fix_path_separators(*cpptoml::get_impl<std::string>(result)));
}
template <class T>
CommandVar<T>::CommandVar(std::string_view name, T* default_value,
std::string_view description)
: name_(name),
default_value_(*default_value),
description_(description),
current_value_(default_value) {}
template <class T>
ConfigVar<T>::ConfigVar(std::string_view name, T* default_value,
std::string_view description, std::string_view category,
bool is_transient, bool requires_restart)
: CommandVar<T>(name, default_value, description),
category_(category),
is_transient_(is_transient),
requires_restart_(requires_restart) {}
template <class T>
void CommandVar<T>::UpdateValue() {
if (commandline_value_) return set_current_value(*commandline_value_);
return set_current_value(default_value_);
}
template <class T>
void ConfigVar<T>::UpdateValue() {
if (this->command_line_value()) {
return this->set_config_value(*this->command_line_value());
}
if (game_config_value_) return this->set_current_value(*game_config_value_);
if (config_value_) return this->set_current_value(*config_value_);
return this->set_current_value(this->default_value());
}
template <>
inline std::string CommandVar<std::string>::Convert(std::string_view val) const {
return std::string(val);
}
template <>
inline std::filesystem::path CommandVar<std::filesystem::path>::Convert(
std::string_view val) const {
return xe::to_path(val);
}
template <>
inline std::string CommandVar<bool>::ToString(const bool& val) {
return val ? "true" : "false";
}
template <>
inline std::string CommandVar<std::string>::ToString(const std::string& val) {
return toml::EscapeString(val);
}
template <>
inline std::string CommandVar<std::filesystem::path>::ToString(const
std::filesystem::path& val) {
return toml::EscapeString(
xe::utf8::fix_path_separators(xe::path_to_utf8(val), '/'));
}

View File

@ -9,6 +9,7 @@
#include "xenia/base/cvar.h"
#include "xenia/base/main.h"
#include "xenia/config.h"
#include "xenia/base/filesystem.h"
#include "xenia/base/logging.h"
@ -23,8 +24,8 @@ bool has_console_attached() { return true; }
extern "C" int main(int argc, char** argv) {
auto entry_info = xe::GetEntryInfo();
cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage,
entry_info.positional_options);
Config::Instance().ParseLaunchArguments(
argc, argv, entry_info.positional_usage, entry_info.positional_options);
std::vector<std::string> args;
for (int n = 0; n < argc; n++) {

View File

@ -26,6 +26,7 @@
// Includes Windows headers, so it goes here.
#include "third_party/xbyak/xbyak/xbyak_util.h"
#include "xenia/config.h"
DEFINE_bool(win32_high_freq, true,
"Requests high performance from the NT kernel", "Kernel");
@ -106,7 +107,8 @@ static bool parse_launch_arguments(const xe::EntryInfo& entry_info,
LocalFree(wargv);
cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage,
Config::Instance().ParseLaunchArguments(
argc, argv, entry_info.positional_usage,
entry_info.positional_options);
args.clear();

View File

@ -17,6 +17,8 @@
#include "xenia/base/string.h"
#include "xenia/base/string_buffer.h"
CmdVar(config, "", "Specifies the target config to load.");
std::shared_ptr<cpptoml::table> ParseFile(
const std::filesystem::path& filename) {
std::ifstream file(filename);
@ -28,54 +30,61 @@ std::shared_ptr<cpptoml::table> ParseFile(
return p.parse();
}
CmdVar(config, "", "Specifies the target config to load.");
namespace config {
std::string config_name = "xenia.config.toml";
std::filesystem::path config_folder;
std::filesystem::path config_path;
std::string game_config_suffix = ".config.toml";
namespace xe {
Config& Config::Instance() {
static Config config;
return config;
}
Config::Config() : options_("xenia", "Xbox 30 Emulator") {
}
std::shared_ptr<cpptoml::table> ParseConfig(
const std::filesystem::path& config_path) {
try {
return ParseFile(config_path);
} catch (cpptoml::parse_exception e) {
} catch (const cpptoml::parse_exception& e) {
xe::FatalError(fmt::format("Failed to parse config file '{}':\n\n{}",
xe::path_to_utf8(config_path), e.what()));
return nullptr;
}
}
void ReadConfig(const std::filesystem::path& file_path) {
const auto config = ParseConfig(file_path);
for (auto& it : *cvar::ConfigVars) {
auto config_var = static_cast<cvar::IConfigVar*>(it.second);
auto config_key = config_var->category() + "." + config_var->name();
if (config->contains_qualified(config_key)) {
config_var->LoadConfigValue(config->get_qualified(config_key));
void Config::SetupConfig(const std::filesystem::path& config_folder) {
Config::config_folder_ = config_folder;
// check if the user specified a specific config to load
if (!cvars::config.empty()) {
config_path_ = xe::to_path(cvars::config);
if (std::filesystem::exists(config_path_)) {
ReadConfig(config_path_);
return;
}
}
XELOGI("Loaded config: {}", xe::path_to_utf8(file_path));
}
void ReadGameConfig(const std::filesystem::path& file_path) {
const auto config = ParseConfig(file_path);
for (auto& it : *cvar::ConfigVars) {
auto config_var = static_cast<cvar::IConfigVar*>(it.second);
auto config_key = config_var->category() + "." + config_var->name();
if (config->contains_qualified(config_key)) {
config_var->LoadGameConfigValue(config->get_qualified(config_key));
// if the user specified a --config argument, but the file doesn't exist,
// let's also load the default config
if (!config_folder.empty()) {
config_path_ = config_folder / config_name_;
if (std::filesystem::exists(config_path_)) {
ReadConfig(config_path_);
}
// we only want to save the config if the user is using the default
// config, we don't want to override a user created specific config
SaveConfig();
}
XELOGI("Loaded game config: {}", xe::path_to_utf8(file_path));
}
void SaveConfig() {
SaveStatus Config::SaveConfig() {
std::vector<cvar::IConfigVar*> vars;
for (const auto& s : *cvar::ConfigVars) {
vars.push_back(s.second);
SaveStatus status = SaveStatus::Saved;
for (const auto& s : config_vars_) {
vars.push_back(s.second.get());
}
std::sort(vars.begin(), vars.end(), [](auto a, auto b) {
std::sort(vars.begin(), vars.end(), [](auto a, auto b)
{
if (a->category() < b->category()) return true;
if (a->category() > b->category()) return false;
if (a->name() < b->name()) return true;
@ -87,11 +96,15 @@ void SaveConfig() {
std::string last_category;
bool last_multiline_description = false;
xe::StringBuffer sb;
for (auto config_var : vars) {
for (const cvar::IConfigVar* config_var : vars) {
if (config_var->is_transient()) {
continue;
}
if (config_var->requires_restart()) {
status = SaveStatus::RequiresRestart;
}
if (last_category != config_var->category()) {
if (!last_category.empty()) {
sb.Append('\n', 2);
@ -150,47 +163,120 @@ void SaveConfig() {
}
// save the config file
xe::filesystem::CreateParentFolder(config_path);
xe::filesystem::CreateParentFolder(config_path_);
auto handle = xe::filesystem::OpenFile(config_path, "wb");
auto handle = xe::filesystem::OpenFile(config_path_, "wb");
if (!handle) {
XELOGE("Failed to open '{}' for writing.", xe::path_to_utf8(config_path));
} else {
fwrite(sb.buffer(), 1, sb.length(), handle);
fclose(handle);
XELOGE("Failed to open '{}' for writing.", xe::path_to_utf8(config_path_));
return SaveStatus::CouldNotSave;
}
fwrite(sb.buffer(), 1, sb.length(), handle);
fclose(handle);
return status;
}
void SetupConfig(const std::filesystem::path& config_folder) {
config::config_folder = config_folder;
// check if the user specified a specific config to load
if (!cvars::config.empty()) {
config_path = xe::to_path(cvars::config);
if (std::filesystem::exists(config_path)) {
ReadConfig(config_path);
return;
}
}
// if the user specified a --config argument, but the file doesn't exist,
// let's also load the default config
if (!config_folder.empty()) {
config_path = config_folder / config_name;
if (std::filesystem::exists(config_path)) {
ReadConfig(config_path);
}
// we only want to save the config if the user is using the default
// config, we don't want to override a user created specific config
SaveConfig();
}
}
void LoadGameConfig(const std::string_view title_id) {
const auto game_config_folder = config_folder / "config";
void Config::LoadGameConfig(const std::string_view title_id) {
const auto game_config_folder = Config::config_folder_ / "config";
const auto game_config_path =
game_config_folder / (std::string(title_id) + game_config_suffix);
game_config_folder / (std::string(title_id) + Config::game_config_suffix_);
if (std::filesystem::exists(game_config_path)) {
ReadGameConfig(game_config_path);
}
}
} // namespace config
cvar::IConfigVar* Config::FindConfigVar(const std::string& name) {
const auto it = config_vars_.find(name);
if (it != config_vars_.end()) {
return it->second.get();
}
return nullptr;
}
cvar::ICommandVar* Config::FindCommandVar(const std::string& name) {
const auto it = command_vars_.find(name);
if (it != command_vars_.end()) {
return it->second.get();
}
return nullptr;
}
void Config::ParseLaunchArguments(int& argc, char**& argv,
const std::string_view positional_help,
const std::vector<std::string>& positional_options) {
options_.add_options()("help", "Prints help and exit.");
for (auto& it : command_vars_) {
const auto& cmdVar = it.second;
cmdVar->AddToLaunchOptions(&options_);
}
for (const auto& it : config_vars_) {
const auto& configVar = it.second;
configVar->AddToLaunchOptions(&options_);
}
try {
options_.positional_help(std::string(positional_help));
options_.parse_positional(positional_options);
auto result = options_.parse(argc, argv);
if (result.count("help")) {
PrintHelpAndExit();
}
for (auto& it : command_vars_) {
const auto& cmdVar = it.second;
if (result.count(cmdVar->name())) {
cmdVar->LoadFromLaunchOptions(&result);
}
}
for (auto& it : config_vars_) {
const auto& configVar = it.second;
if (result.count(configVar->name())) {
configVar->LoadFromLaunchOptions(&result);
}
}
} catch (const cxxopts::OptionException& e) {
std::cout << e.what() << std::endl;
PrintHelpAndExit();
}
}
void Config::ReadConfig(const std::filesystem::path& file_path) {
const auto config = ParseConfig(file_path);
for (auto& it : config_vars_) {
const auto& config_var = it.second;
auto config_key = config_var->category() + "." + config_var->name();
if (config->contains_qualified(config_key)) {
config_var->LoadConfigValue(config->get_qualified(config_key));
}
}
XELOGI("Loaded config: {}", xe::path_to_utf8(file_path));
}
void Config::ReadGameConfig(const std::filesystem::path& file_path) {
const auto config = ParseConfig(file_path);
for (auto& it : config_vars_) {
const auto& config_var = it.second;
auto config_key = config_var->category() + "." + config_var->name();
if (config->contains_qualified(config_key)) {
config_var->LoadGameConfigValue(config->get_qualified(config_key));
}
}
XELOGI("Loaded game config: {}", xe::path_to_utf8(file_path));
}
void Config::PrintHelpAndExit() {
std::cout << options_.help({""}) << std::endl;
std::cout << "For the full list of command line arguments, see xenia.cfg."
<< std::endl;
exit(0);
}
} // namespace xe

View File

@ -11,10 +11,113 @@
#define XENIA_CONFIG_H_
#include <filesystem>
#include <unordered_map>
#include "cxxopts/include/cxxopts.hpp"
namespace config {
void SetupConfig(const std::filesystem::path& config_folder);
void LoadGameConfig(const std::string_view title_id);
} // namespace config
namespace xe {
namespace cvar {
class IConfigVar;
class ICommandVar;
template <typename T>
class ConfigVar;
template <typename T>
class CommandVar;
} // namespace cvar
enum class SaveStatus : int {
Saved = 1 << 0,
RequiresRestart = 1 << 0 | 1 << 1,
CouldNotSave = 1 << 2
};
using IConfigVarRef = std::unique_ptr<cvar::IConfigVar>;
using ICommandVarRef = std::unique_ptr<cvar::ICommandVar>;
class Config {
public:
static Config& Instance();
/**
* Populates the global config variables from a config file
*/
void SetupConfig(const std::filesystem::path& config_folder);
/**
* Save the current config variable state to the config file
*/
SaveStatus SaveConfig();
/**
* Overrides config variables with those from a game-specific config file (if
* any)
*/
void LoadGameConfig(std::string_view title_id);
/**
* Find a config variable for a given config field
*/
cvar::IConfigVar* FindConfigVar(const std::string& name);
/**
* Find a command variable for a given name
*/
cvar::ICommandVar* FindCommandVar(const std::string& name);
/**
* Parse launch arguments and update cvar values based on provided launch
* parameters
*/
void ParseLaunchArguments(int& argc, char**& argv,
std::string_view positional_help,
const std::vector<std::string>& positional_options);
/**
* Register a config var to the config system
* Claims ownership of the provided pointer
*/
template <typename T>
cvar::ConfigVar<T>* RegisterConfigVar(cvar::ConfigVar<T>* var);
/**
* Register a command var to the config system
* Claims ownership of the provided pointer
*/
template <typename T>
cvar::CommandVar<T>* RegisterCommandVar(cvar::CommandVar<T>* var);
private:
Config();
void ReadConfig(const std::filesystem::path& file_path);
void ReadGameConfig(const std::filesystem::path& file_path);
void PrintHelpAndExit();
std::unordered_map<std::string, IConfigVarRef> config_vars_;
std::unordered_map<std::string, ICommandVarRef> command_vars_;
std::filesystem::path config_folder_;
std::filesystem::path config_path_;
std::string config_name_ = "xenia.config.toml";
std::string game_config_suffix_ = ".config.toml";
cxxopts::Options options_;
};
template <typename T>
cvar::ConfigVar<T>* Config::RegisterConfigVar(cvar::ConfigVar<T>* var) {
const std::string& name = var->name();
config_vars_[name] = std::unique_ptr<cvar::ConfigVar<T>>(var);
return dynamic_cast<cvar::ConfigVar<T>*>(config_vars_[name].get());
}
template <typename T>
cvar::CommandVar<T>* Config::RegisterCommandVar(cvar::CommandVar<T>* var) {
const std::string& name = var->name();
command_vars_[name] = std::unique_ptr<cvar::CommandVar<T>>(var);
return dynamic_cast<cvar::CommandVar<T>*>(command_vars_[name].get());
}
} // namespace xe
#endif // XENIA_CONFIG_H_

View File

@ -664,7 +664,7 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
// Try and load the resource database (xex only).
if (module->title_id()) {
auto title_id = fmt::format("{:08X}", module->title_id());
config::LoadGameConfig(title_id);
Config::Instance().LoadGameConfig(title_id);
uint32_t resource_data = 0;
uint32_t resource_size = 0;
if (XSUCCEEDED(module->GetSection(title_id.c_str(), &resource_data,

View File

@ -14,8 +14,10 @@
#include <vector>
#define CATCH_CONFIG_RUNNER
#include "third_party/catch/include/catch.hpp"
#include "xenia/base/cvar.h"
#include "xenia/config.h"
namespace xe {
@ -23,7 +25,8 @@ bool has_console_attached() { return true; }
// Used in console mode apps; automatically picked based on subsystem.
int Main(int argc, char* argv[]) {
cvar::ParseLaunchArguments(argc, argv, "", std::vector<std::string>());
Config::Instance().ParseLaunchArguments(argc, argv, "",
std::vector<std::string>());
// Run Catch.
int result = Catch::Session().run(argc, argv);