[Base/Core] Work on config/cvars. Properly escape string values when saving config.
This commit is contained in:
parent
82f85c98f0
commit
bee55ce5bb
|
@ -1,6 +1,7 @@
|
||||||
#include "cvar.h"
|
#include "cvar.h"
|
||||||
|
|
||||||
namespace cvar {
|
namespace cvar {
|
||||||
|
|
||||||
cxxopts::Options options("xenia", "Xbox 360 Emulator");
|
cxxopts::Options options("xenia", "Xbox 360 Emulator");
|
||||||
std::map<std::string, ICommandVar*>* CmdVars;
|
std::map<std::string, ICommandVar*>* CmdVars;
|
||||||
std::map<std::string, IConfigVar*>* ConfigVars;
|
std::map<std::string, IConfigVar*>* ConfigVars;
|
||||||
|
@ -53,4 +54,121 @@ void ParseLaunchArguments(int argc, char** argv) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace toml {
|
||||||
|
|
||||||
|
std::string EscapeBasicString(const std::string& str) {
|
||||||
|
std::string result;
|
||||||
|
for (auto c : str) {
|
||||||
|
if (c == '\b') {
|
||||||
|
result += "\\b";
|
||||||
|
} else if (c == '\t') {
|
||||||
|
result += "\\t";
|
||||||
|
} else if (c == '\n') {
|
||||||
|
result += "\\n";
|
||||||
|
} else if (c == '\f') {
|
||||||
|
result += "\\f";
|
||||||
|
} else if (c == '\r') {
|
||||||
|
result += "\\r";
|
||||||
|
} else if (c == '"') {
|
||||||
|
result += "\\\"";
|
||||||
|
} else if (c == '\\') {
|
||||||
|
result += "\\\\";
|
||||||
|
} else if (static_cast<uint32_t>(c) < 0x20 ||
|
||||||
|
static_cast<uint32_t>(c) == 0x7F) {
|
||||||
|
auto v = static_cast<uint32_t>(c);
|
||||||
|
int w;
|
||||||
|
if (v <= 0xFFFF) {
|
||||||
|
result += "\\u";
|
||||||
|
w = 4;
|
||||||
|
} else {
|
||||||
|
result += "\\U";
|
||||||
|
w = 8;
|
||||||
|
}
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::hex << std::setw(w) << std::setfill('0') << v;
|
||||||
|
result += ss.str();
|
||||||
|
} else {
|
||||||
|
result += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string EscapeMultilineBasicString(const std::string& str) {
|
||||||
|
const std::string three_escaped_quotes("\\\"\\\"\\\"");
|
||||||
|
std::string result;
|
||||||
|
char lc = '\0';
|
||||||
|
int quote_run = 0;
|
||||||
|
for (char c : str) {
|
||||||
|
if (c == '\b') {
|
||||||
|
result += "\\b";
|
||||||
|
} else if (c == '\t' || c == '\n') {
|
||||||
|
result += c;
|
||||||
|
} else if (c == '\f') {
|
||||||
|
result += "\\f";
|
||||||
|
} else if (c == '\r') {
|
||||||
|
// Silently drop \r.
|
||||||
|
// result += c;
|
||||||
|
} else if (c == '"') {
|
||||||
|
result += '"';
|
||||||
|
if (lc == '"') {
|
||||||
|
++quote_run;
|
||||||
|
if (quote_run >= 3) {
|
||||||
|
result.resize(result.size() - 3);
|
||||||
|
result += three_escaped_quotes;
|
||||||
|
quote_run = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (c == '\\') {
|
||||||
|
result += "\\\\";
|
||||||
|
} else if (static_cast<uint32_t>(c) < 0x20 ||
|
||||||
|
static_cast<uint32_t>(c) == 0x7F) {
|
||||||
|
auto v = static_cast<uint32_t>(c);
|
||||||
|
int w;
|
||||||
|
if (v <= 0xFFFF) {
|
||||||
|
result += "\\u";
|
||||||
|
w = 4;
|
||||||
|
} else {
|
||||||
|
result += "\\U";
|
||||||
|
w = 8;
|
||||||
|
}
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::hex << std::setw(w) << std::setfill('0') << v;
|
||||||
|
result += ss.str();
|
||||||
|
} else {
|
||||||
|
result += c;
|
||||||
|
}
|
||||||
|
lc = c;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string EscapeString(const std::string& val) {
|
||||||
|
const char multiline_chars[] = "\r\n";
|
||||||
|
const char escape_chars[] =
|
||||||
|
"\0\b\v\f"
|
||||||
|
"\x01\x02\x03\x04\x05\x06\x07\x0E\x0F"
|
||||||
|
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
|
||||||
|
"'"
|
||||||
|
"\x7F";
|
||||||
|
if (val.find_first_of("\r\n") == std::string::npos) {
|
||||||
|
// single line
|
||||||
|
if (val.find_first_of(multiline_chars) == std::string::npos) {
|
||||||
|
return "'" + val + "'";
|
||||||
|
} else {
|
||||||
|
return "\"" + toml::EscapeBasicString(val) + "\"";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// multi line
|
||||||
|
if (val.find_first_of(escape_chars) == std::string::npos &&
|
||||||
|
val.find("'''") == std::string::npos) {
|
||||||
|
return "'''\n" + val + "'''";
|
||||||
|
} else {
|
||||||
|
return "\"\"\"\n" + toml::EscapeMultilineBasicString(val) + "\"\"\"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace toml
|
||||||
|
|
||||||
} // namespace cvar
|
} // namespace cvar
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
#include "xenia/base/string_util.h"
|
#include "xenia/base/string_util.h"
|
||||||
namespace cvar {
|
namespace cvar {
|
||||||
|
|
||||||
|
namespace toml {
|
||||||
|
std::string EscapeString(const std::string& str);
|
||||||
|
}
|
||||||
|
|
||||||
class ICommandVar {
|
class ICommandVar {
|
||||||
public:
|
public:
|
||||||
virtual ~ICommandVar() = default;
|
virtual ~ICommandVar() = default;
|
||||||
|
@ -148,7 +152,7 @@ inline std::string CommandVar<bool>::ToString(bool val) {
|
||||||
}
|
}
|
||||||
template <>
|
template <>
|
||||||
inline std::string CommandVar<std::string>::ToString(std::string val) {
|
inline std::string CommandVar<std::string>::ToString(std::string val) {
|
||||||
return "\"" + val + "\"";
|
return toml::EscapeString(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
|
@ -203,52 +207,52 @@ inline void AddCommandVar(ICommandVar* cv) {
|
||||||
void ParseLaunchArguments(int argc, char** argv);
|
void ParseLaunchArguments(int argc, char** argv);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T* define_configvar(const char* name, T* defaultValue, const char* description,
|
T* define_configvar(const char* name, T* default_value, const char* description,
|
||||||
const char* category, bool is_transient) {
|
const char* category, bool is_transient) {
|
||||||
IConfigVar* cfgVar =
|
IConfigVar* cfgVar = new ConfigVar<T>(name, default_value, description,
|
||||||
new ConfigVar<T>(name, defaultValue, description, category, is_transient);
|
category, is_transient);
|
||||||
AddConfigVar(cfgVar);
|
AddConfigVar(cfgVar);
|
||||||
return defaultValue;
|
return default_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T* define_cmdvar(const char* name, T* defaultValue, const char* description) {
|
T* define_cmdvar(const char* name, T* default_value, const char* description) {
|
||||||
ICommandVar* cmdVar = new CommandVar<T>(name, defaultValue, description);
|
ICommandVar* cmdVar = new CommandVar<T>(name, default_value, description);
|
||||||
AddCommandVar(cmdVar);
|
AddCommandVar(cmdVar);
|
||||||
return defaultValue;
|
return default_value;
|
||||||
}
|
}
|
||||||
#define DEFINE_double(name, defaultValue, description, category) \
|
#define DEFINE_double(name, default_value, description, category) \
|
||||||
DEFINE_CVar(name, defaultValue, description, category, false, double)
|
DEFINE_CVar(name, default_value, description, category, false, double)
|
||||||
|
|
||||||
#define DEFINE_int32(name, defaultValue, description, category) \
|
#define DEFINE_int32(name, default_value, description, category) \
|
||||||
DEFINE_CVar(name, defaultValue, description, category, false, int32_t)
|
DEFINE_CVar(name, default_value, description, category, false, int32_t)
|
||||||
|
|
||||||
#define DEFINE_uint64(name, defaultValue, description, category) \
|
#define DEFINE_uint64(name, default_value, description, category) \
|
||||||
DEFINE_CVar(name, defaultValue, description, category, false, uint64_t)
|
DEFINE_CVar(name, default_value, description, category, false, uint64_t)
|
||||||
|
|
||||||
#define DEFINE_string(name, defaultValue, description, category) \
|
#define DEFINE_string(name, default_value, description, category) \
|
||||||
DEFINE_CVar(name, defaultValue, description, category, false, std::string)
|
DEFINE_CVar(name, default_value, description, category, false, std::string)
|
||||||
|
|
||||||
#define DEFINE_transient_string(name, defaultValue, description, category) \
|
#define DEFINE_transient_string(name, default_value, description, category) \
|
||||||
DEFINE_CVar(name, defaultValue, description, category, true, std::string)
|
DEFINE_CVar(name, default_value, description, category, true, std::string)
|
||||||
|
|
||||||
#define DEFINE_bool(name, defaultValue, description, category) \
|
#define DEFINE_bool(name, default_value, description, category) \
|
||||||
DEFINE_CVar(name, defaultValue, description, category, false, bool)
|
DEFINE_CVar(name, default_value, description, category, false, bool)
|
||||||
|
|
||||||
#define DEFINE_CVar(name, defaultValue, description, category, is_transient, \
|
#define DEFINE_CVar(name, default_value, description, category, is_transient, \
|
||||||
type) \
|
type) \
|
||||||
namespace cvars { \
|
namespace cvars { \
|
||||||
type name = defaultValue; \
|
type name = default_value; \
|
||||||
} \
|
} \
|
||||||
namespace cv { \
|
namespace cv { \
|
||||||
static auto cv_##name = cvar::define_configvar( \
|
static auto cv_##name = cvar::define_configvar( \
|
||||||
#name, &cvars::name, description, category, is_transient); \
|
#name, &cvars::name, description, category, is_transient); \
|
||||||
}
|
}
|
||||||
|
|
||||||
// CmdVars can only be strings for now, we don't need any others
|
// CmdVars can only be strings for now, we don't need any others
|
||||||
#define CmdVar(name, defaultValue, description) \
|
#define CmdVar(name, default_value, description) \
|
||||||
namespace cvars { \
|
namespace cvars { \
|
||||||
std::string name = defaultValue; \
|
std::string name = default_value; \
|
||||||
} \
|
} \
|
||||||
namespace cv { \
|
namespace cv { \
|
||||||
static auto cv_##name = \
|
static auto cv_##name = \
|
||||||
|
|
|
@ -43,10 +43,10 @@ std::shared_ptr<cpptoml::table> ParseConfig(const std::wstring& config_path) {
|
||||||
void ReadConfig(const std::wstring& file_path) {
|
void ReadConfig(const std::wstring& file_path) {
|
||||||
const auto config = ParseConfig(file_path);
|
const auto config = ParseConfig(file_path);
|
||||||
for (auto& it : *cvar::ConfigVars) {
|
for (auto& it : *cvar::ConfigVars) {
|
||||||
auto configVar = static_cast<cvar::IConfigVar*>(it.second);
|
auto config_var = static_cast<cvar::IConfigVar*>(it.second);
|
||||||
auto configKey = configVar->category() + "." + configVar->name();
|
auto config_key = config_var->category() + "." + config_var->name();
|
||||||
if (config->contains_qualified(configKey)) {
|
if (config->contains_qualified(config_key)) {
|
||||||
configVar->LoadConfigValue(config->get_qualified(configKey));
|
config_var->LoadConfigValue(config->get_qualified(config_key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
XELOGI("Loaded config: %S", file_path.c_str());
|
XELOGI("Loaded config: %S", file_path.c_str());
|
||||||
|
@ -55,10 +55,10 @@ void ReadConfig(const std::wstring& file_path) {
|
||||||
void ReadGameConfig(std::wstring file_path) {
|
void ReadGameConfig(std::wstring file_path) {
|
||||||
const auto config = ParseConfig(file_path);
|
const auto config = ParseConfig(file_path);
|
||||||
for (auto& it : *cvar::ConfigVars) {
|
for (auto& it : *cvar::ConfigVars) {
|
||||||
auto configVar = static_cast<cvar::IConfigVar*>(it.second);
|
auto config_var = static_cast<cvar::IConfigVar*>(it.second);
|
||||||
auto configKey = configVar->category() + "." + configVar->name();
|
auto config_key = config_var->category() + "." + config_var->name();
|
||||||
if (config->contains_qualified(configKey)) {
|
if (config->contains_qualified(config_key)) {
|
||||||
configVar->LoadGameConfigValue(config->get_qualified(configKey));
|
config_var->LoadGameConfigValue(config->get_qualified(config_key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
XELOGI("Loaded game config: %S", file_path.c_str());
|
XELOGI("Loaded game config: %S", file_path.c_str());
|
||||||
|
@ -71,22 +71,36 @@ void SaveConfig() {
|
||||||
// we use our own write logic because cpptoml doesn't
|
// we use our own write logic because cpptoml doesn't
|
||||||
// allow us to specify comments :(
|
// allow us to specify comments :(
|
||||||
std::ostringstream output;
|
std::ostringstream output;
|
||||||
std::string lastCategory;
|
std::string last_category;
|
||||||
for (auto configVar : vars) {
|
for (auto config_var : vars) {
|
||||||
if (configVar->is_transient()) {
|
if (config_var->is_transient()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (lastCategory != configVar->category()) {
|
if (last_category != config_var->category()) {
|
||||||
if (!lastCategory.empty()) {
|
if (!last_category.empty()) {
|
||||||
output << std::endl;
|
output << std::endl;
|
||||||
}
|
}
|
||||||
lastCategory = configVar->category();
|
last_category = config_var->category();
|
||||||
output << xe::format_string("[%s]\n", lastCategory.c_str());
|
output << xe::format_string("[%s]\n", last_category.c_str());
|
||||||
}
|
}
|
||||||
output << std::left << std::setw(40) << std::setfill(' ')
|
|
||||||
<< xe::format_string("%s = %s", configVar->name().c_str(),
|
auto value = config_var->config_value();
|
||||||
configVar->config_value().c_str());
|
if (value.find('\n') == std::string::npos) {
|
||||||
output << xe::format_string("\t# %s\n", configVar->description().c_str());
|
output << std::left << std::setw(40) << std::setfill(' ')
|
||||||
|
<< xe::format_string("%s = %s", config_var->name().c_str(),
|
||||||
|
config_var->config_value().c_str());
|
||||||
|
} else {
|
||||||
|
auto lines = xe::split_string(value, "\n");
|
||||||
|
auto first_it = lines.cbegin();
|
||||||
|
output << xe::format_string("%s = %s\n", config_var->name().c_str(),
|
||||||
|
*first_it);
|
||||||
|
auto last_it = std::prev(lines.cend());
|
||||||
|
for (auto it = std::next(first_it); it != last_it; ++it) {
|
||||||
|
output << *it << "\n";
|
||||||
|
}
|
||||||
|
output << std::left << std::setw(40) << std::setfill(' ') << *last_it;
|
||||||
|
}
|
||||||
|
output << xe::format_string("\t# %s\n", config_var->description().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (xe::filesystem::PathExists(config_path)) {
|
if (xe::filesystem::PathExists(config_path)) {
|
||||||
|
|
Loading…
Reference in New Issue