2019-04-17 19:40:31 +00:00
|
|
|
#include "config.h"
|
|
|
|
#include "cpptoml/include/cpptoml.h"
|
|
|
|
#include "xenia/base/cvar.h"
|
|
|
|
#include "xenia/base/filesystem.h"
|
|
|
|
#include "xenia/base/logging.h"
|
|
|
|
#include "xenia/base/string.h"
|
|
|
|
|
2019-08-04 08:30:57 +00:00
|
|
|
namespace cpptoml {
|
|
|
|
inline std::shared_ptr<table> parse_file(const std::wstring& filename) {
|
|
|
|
std::ifstream file(filename);
|
|
|
|
if (!file.is_open()) {
|
|
|
|
throw parse_exception(xe::to_string(filename) +
|
|
|
|
" could not be opened for parsing");
|
|
|
|
}
|
|
|
|
parser p(file);
|
|
|
|
return p.parse();
|
|
|
|
}
|
|
|
|
} // namespace cpptoml
|
|
|
|
|
2019-04-17 19:40:31 +00:00
|
|
|
CmdVar(config, "", "Specifies the target config to load.");
|
|
|
|
namespace config {
|
|
|
|
std::wstring config_name = L"xenia.config.toml";
|
|
|
|
std::wstring config_folder;
|
|
|
|
std::wstring config_path;
|
|
|
|
|
|
|
|
bool sortCvar(cvar::IConfigVar* a, cvar::IConfigVar* b) {
|
2019-08-04 07:17:22 +00:00
|
|
|
if (a->category() < b->category()) return true;
|
|
|
|
if (a->category() > b->category()) return false;
|
|
|
|
if (a->name() < b->name()) return true;
|
2019-04-17 19:40:31 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-08-04 08:30:57 +00:00
|
|
|
std::shared_ptr<cpptoml::table> ParseConfig(const std::wstring& config_path) {
|
2019-04-17 19:40:31 +00:00
|
|
|
try {
|
|
|
|
return cpptoml::parse_file(config_path);
|
2019-08-04 06:22:01 +00:00
|
|
|
} catch (cpptoml::parse_exception e) {
|
2019-08-04 08:30:57 +00:00
|
|
|
xe::FatalError(L"Failed to parse config file '%s':\n\n%s",
|
|
|
|
config_path.c_str(), xe::to_wstring(e.what()).c_str());
|
2019-04-17 19:40:31 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ReadConfig(const std::wstring& file_path) {
|
2019-08-04 08:30:57 +00:00
|
|
|
const auto config = ParseConfig(file_path);
|
2019-04-17 19:40:31 +00:00
|
|
|
for (auto& it : *cvar::ConfigVars) {
|
|
|
|
auto configVar = static_cast<cvar::IConfigVar*>(it.second);
|
2019-08-04 07:17:22 +00:00
|
|
|
auto configKey = configVar->category() + "." + configVar->name();
|
2019-04-17 19:40:31 +00:00
|
|
|
if (config->contains_qualified(configKey)) {
|
|
|
|
configVar->LoadConfigValue(config->get_qualified(configKey));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
XELOGI("Loaded config: %S", file_path.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
void ReadGameConfig(std::wstring file_path) {
|
2019-08-04 08:30:57 +00:00
|
|
|
const auto config = ParseConfig(file_path);
|
2019-04-17 19:40:31 +00:00
|
|
|
for (auto& it : *cvar::ConfigVars) {
|
|
|
|
auto configVar = static_cast<cvar::IConfigVar*>(it.second);
|
2019-08-04 07:17:22 +00:00
|
|
|
auto configKey = configVar->category() + "." + configVar->name();
|
2019-04-17 19:40:31 +00:00
|
|
|
if (config->contains_qualified(configKey)) {
|
|
|
|
configVar->LoadGameConfigValue(config->get_qualified(configKey));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
XELOGI("Loaded game config: %S", file_path.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
void SaveConfig() {
|
|
|
|
std::vector<cvar::IConfigVar*> vars;
|
|
|
|
for (const auto& s : *cvar::ConfigVars) vars.push_back(s.second);
|
|
|
|
std::sort(vars.begin(), vars.end(), sortCvar);
|
|
|
|
// we use our own write logic because cpptoml doesn't
|
|
|
|
// allow us to specify comments :(
|
|
|
|
std::ostringstream output;
|
|
|
|
std::string lastCategory;
|
|
|
|
for (auto configVar : vars) {
|
2019-08-04 07:17:22 +00:00
|
|
|
if (configVar->is_transient()) {
|
2019-08-04 07:12:46 +00:00
|
|
|
continue;
|
|
|
|
}
|
2019-08-04 07:17:22 +00:00
|
|
|
if (lastCategory != configVar->category()) {
|
2019-04-17 19:40:31 +00:00
|
|
|
if (!lastCategory.empty()) {
|
|
|
|
output << std::endl;
|
|
|
|
}
|
2019-08-04 07:17:22 +00:00
|
|
|
lastCategory = configVar->category();
|
2019-04-17 19:40:31 +00:00
|
|
|
output << xe::format_string("[%s]\n", lastCategory.c_str());
|
|
|
|
}
|
|
|
|
output << std::left << std::setw(40) << std::setfill(' ')
|
2019-08-04 07:17:22 +00:00
|
|
|
<< xe::format_string("%s = %s", configVar->name().c_str(),
|
|
|
|
configVar->config_value().c_str());
|
|
|
|
output << xe::format_string("\t# %s\n", configVar->description().c_str());
|
2019-04-17 19:40:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (xe::filesystem::PathExists(config_path)) {
|
|
|
|
std::ifstream existingConfigStream(xe::to_string(config_path).c_str());
|
|
|
|
const std::string existingConfig(
|
|
|
|
(std::istreambuf_iterator<char>(existingConfigStream)),
|
|
|
|
std::istreambuf_iterator<char>());
|
|
|
|
// if the config didn't change, no need to modify the file
|
|
|
|
if (existingConfig == output.str()) return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// save the config file
|
|
|
|
xe::filesystem::CreateParentFolder(config_path);
|
|
|
|
std::ofstream file;
|
2019-08-04 08:30:57 +00:00
|
|
|
file.open(config_path, std::ios::out | std::ios::trunc);
|
2019-04-17 19:40:31 +00:00
|
|
|
file << output.str();
|
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SetupConfig(const std::wstring& 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_wstring(cvars::config);
|
|
|
|
if (xe::filesystem::PathExists(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 = xe::join_paths(config_folder, config_name);
|
|
|
|
if (xe::filesystem::PathExists(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::wstring& title_id) {
|
|
|
|
const auto content_folder = xe::join_paths(config_folder, L"content");
|
|
|
|
const auto game_folder = xe::join_paths(content_folder, title_id);
|
|
|
|
const auto game_config_path = xe::join_paths(game_folder, config_name);
|
|
|
|
if (xe::filesystem::PathExists(game_config_path)) {
|
|
|
|
ReadGameConfig(game_config_path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace config
|