implement config db

This commit is contained in:
Megamouse
2025-06-16 07:09:59 +02:00
committed by Ani
parent 7d41bbdd2b
commit 50d6396f99
30 changed files with 607 additions and 123 deletions
+1
View File
@@ -24,6 +24,7 @@ mkdir ./bin/config
mkdir ./bin/config/input_configs mkdir ./bin/config/input_configs
curl -fsSL 'https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/master/gamecontrollerdb.txt' 1> ./bin/config/input_configs/gamecontrollerdb.txt curl -fsSL 'https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/master/gamecontrollerdb.txt' 1> ./bin/config/input_configs/gamecontrollerdb.txt
curl -fsSL 'https://rpcs3.net/compatibility?api=v1&export' | iconv -f ISO-8859-1 -t UTF-8 1> ./bin/GuiConfigs/compat_database.dat curl -fsSL 'https://rpcs3.net/compatibility?api=v1&export' | iconv -f ISO-8859-1 -t UTF-8 1> ./bin/GuiConfigs/compat_database.dat
curl -fsSL 'https://api.rpcs3.net/config/?api=v1' | iconv -f ISO-8859-1 -t UTF-8 1> ./bin/GuiConfigs/config_database.dat
# Download translations # Download translations
mkdir -p ./bin/share/qt6/translations mkdir -p ./bin/share/qt6/translations
+1
View File
@@ -14,6 +14,7 @@ mkdir ./bin/config
mkdir ./bin/config/input_configs mkdir ./bin/config/input_configs
curl -fsSL 'https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/master/gamecontrollerdb.txt' 1> ./bin/config/input_configs/gamecontrollerdb.txt curl -fsSL 'https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/master/gamecontrollerdb.txt' 1> ./bin/config/input_configs/gamecontrollerdb.txt
curl -fsSL 'https://rpcs3.net/compatibility?api=v1&export' | iconv -t UTF-8 1> ./bin/GuiConfigs/compat_database.dat curl -fsSL 'https://rpcs3.net/compatibility?api=v1&export' | iconv -t UTF-8 1> ./bin/GuiConfigs/compat_database.dat
curl -fsSL 'https://api.rpcs3.net/config/?api=v1' | iconv -t UTF-8 1> ./bin/GuiConfigs/config_database.dat
# Download translations # Download translations
mkdir -p ./bin/qt6/translations mkdir -p ./bin/qt6/translations
+62 -36
View File
@@ -40,7 +40,7 @@ namespace cfg
owner->m_nodes.emplace_back(this); owner->m_nodes.emplace_back(this);
} }
bool _base::from_string(std::string_view, bool) bool _base::from_string(std::string_view /*value*/, bool /*dynamic*/)
{ {
cfg_log.fatal("cfg::_base::from_string() purecall"); cfg_log.fatal("cfg::_base::from_string() purecall");
return false; return false;
@@ -68,7 +68,7 @@ namespace cfg
// Incrementally load config entries from YAML::Node. // Incrementally load config entries from YAML::Node.
// The config value is preserved if the corresponding YAML node doesn't exist. // The config value is preserved if the corresponding YAML node doesn't exist.
static void decode(const YAML::Node& data, class _base& rhs, bool dynamic = false); [[nodiscard]] static bool decode(const YAML::Node& data, class _base& rhs, bool dynamic, bool strict);
} }
std::vector<std::string> cfg::make_int_range(s64 min, s64 max) std::vector<std::string> cfg::make_int_range(s64 min, s64 max)
@@ -76,11 +76,11 @@ std::vector<std::string> cfg::make_int_range(s64 min, s64 max)
return {std::to_string(min), std::to_string(max)}; return {std::to_string(min), std::to_string(max)};
} }
bool try_to_int64(s64* out, std::string_view value, s64 min, s64 max) bool try_to_int64(s64* out, std::string_view value, s64 min, s64 max, std::string_view name)
{ {
if (value.empty()) if (value.empty())
{ {
if (out) cfg_log.error("cfg::try_to_int64(): called with an empty string"); if (out) cfg_log.error("cfg::try_to_int64('%s'): called with an empty string", name);
return false; return false;
} }
@@ -107,7 +107,7 @@ bool try_to_int64(s64* out, std::string_view value, s64 min, s64 max)
if (ret.ec != std::errc() || ret.ptr != end || (start[0] == '-' && sign < 0)) if (ret.ec != std::errc() || ret.ptr != end || (start[0] == '-' && sign < 0))
{ {
if (out) cfg_log.error("cfg::try_to_int64('%s'): invalid integer", value); if (out) cfg_log.error("cfg::try_to_int64('%s', '%s'): invalid integer", value, name);
return false; return false;
} }
@@ -115,7 +115,7 @@ bool try_to_int64(s64* out, std::string_view value, s64 min, s64 max)
if (result < min || result > max) if (result < min || result > max)
{ {
if (out) cfg_log.error("cfg::try_to_int64('%s'): out of bounds (val=%d, min=%d, max=%d)", value, result, min, max); if (out) cfg_log.error("cfg::try_to_int64('%s', '%s'): out of bounds (val=%d, min=%d, max=%d)", value, name, result, min, max);
return false; return false;
} }
@@ -128,11 +128,11 @@ std::vector<std::string> cfg::make_uint_range(u64 min, u64 max)
return {std::to_string(min), std::to_string(max)}; return {std::to_string(min), std::to_string(max)};
} }
bool try_to_uint64(u64* out, std::string_view value, u64 min, u64 max) bool try_to_uint64(u64* out, std::string_view value, u64 min, u64 max, std::string_view name)
{ {
if (value.empty()) if (value.empty())
{ {
if (out) cfg_log.error("cfg::try_to_uint64(): called with an empty string"); if (out) cfg_log.error("cfg::try_to_uint64('%s'): called with an empty string", name);
return false; return false;
} }
@@ -152,13 +152,13 @@ bool try_to_uint64(u64* out, std::string_view value, u64 min, u64 max)
if (ret.ec != std::errc() || ret.ptr != end) if (ret.ec != std::errc() || ret.ptr != end)
{ {
if (out) cfg_log.error("cfg::try_to_uint64('%s'): invalid integer", value); if (out) cfg_log.error("cfg::try_to_uint64('%s', '%s'): invalid integer", value, name);
return false; return false;
} }
if (result < min || result > max) if (result < min || result > max)
{ {
if (out) cfg_log.error("cfg::try_to_uint64('%s'): out of bounds (val=%u, min=%u, max=%u)", value, result, min, max); if (out) cfg_log.error("cfg::try_to_uint64('%s', '%s'): out of bounds (val=%u, min=%u, max=%u)", value, name, result, min, max);
return false; return false;
} }
@@ -166,11 +166,11 @@ bool try_to_uint64(u64* out, std::string_view value, u64 min, u64 max)
return true; return true;
} }
bool try_to_uint128(u128* out, std::string_view value) bool try_to_uint128(u128* out, std::string_view value, std::string_view name)
{ {
if (value.empty()) if (value.empty())
{ {
if (out) cfg_log.error("cfg::try_to_uint128(): called with an empty string"); if (out) cfg_log.error("cfg::try_to_uint128('%s'): called with an empty string", name);
return false; return false;
} }
@@ -193,7 +193,7 @@ bool try_to_uint128(u128* out, std::string_view value)
if (ret.ec != std::errc() || ret.ptr != end) if (ret.ec != std::errc() || ret.ptr != end)
{ {
if (out) cfg_log.error("cfg::try_to_uint128('%s'): invalid integer", value); if (out) cfg_log.error("cfg::try_to_uint128('%s', '%s'): invalid integer", value, name);
return false; return false;
} }
@@ -207,7 +207,7 @@ bool try_to_uint128(u128* out, std::string_view value)
if (ret.ec != std::errc() || ret.ptr != start_low64) if (ret.ec != std::errc() || ret.ptr != start_low64)
{ {
if (out) cfg_log.error("cfg::try_to_uint128('%s'): invalid integer", value); if (out) cfg_log.error("cfg::try_to_uint128('%s', '%s'): invalid integer", value, name);
return false; return false;
} }
@@ -220,11 +220,11 @@ std::vector<std::string> cfg::make_float_range(f64 min, f64 max)
return {std::to_string(min), std::to_string(max)}; return {std::to_string(min), std::to_string(max)};
} }
bool try_to_float(f64* out, std::string_view value, f64 min, f64 max) bool try_to_float(f64* out, std::string_view value, f64 min, f64 max, std::string_view name)
{ {
if (value.empty()) if (value.empty())
{ {
if (out) cfg_log.error("cfg::try_to_float(): called with an empty string"); if (out) cfg_log.error("cfg::try_to_float('%s'): called with an empty string", name);
return false; return false;
} }
@@ -237,13 +237,13 @@ bool try_to_float(f64* out, std::string_view value, f64 min, f64 max)
if (end_check != str.data() + str.size()) if (end_check != str.data() + str.size())
{ {
if (out) cfg_log.error("cfg::try_to_float('%s'): invalid float", value); if (out) cfg_log.error("cfg::try_to_float('%s', '%s'): invalid float", value, name);
return false; return false;
} }
if (result < min || result > max) if (result < min || result > max)
{ {
if (out) cfg_log.error("cfg::try_to_float('%s'): out of bounds (val=%f, min=%f, max=%f)", value, result, min, max); if (out) cfg_log.error("cfg::try_to_float('%s', '%s'): out of bounds (val=%f, min=%f, max=%f)", value, name, result, min, max);
return false; return false;
} }
@@ -251,7 +251,7 @@ bool try_to_float(f64* out, std::string_view value, f64 min, f64 max)
return true; return true;
} }
bool try_to_string(std::string* out, const f64& value) bool try_to_string(std::string* out, f64 value, std::string_view name)
{ {
#ifdef __APPLE__ #ifdef __APPLE__
if (out) *out = std::to_string(value); if (out) *out = std::to_string(value);
@@ -266,13 +266,13 @@ bool try_to_string(std::string* out, const f64& value)
} }
else else
{ {
if (out) cfg_log.error("cfg::try_to_string(): could not convert value '%f' to string. error='%s'", value, std::make_error_code(ec).message()); if (out) cfg_log.error("cfg::try_to_string('%s'): could not convert value '%f' to string. error='%s'", name, value, std::make_error_code(ec).message());
return false; return false;
} }
#endif #endif
} }
bool cfg::try_to_enum_value(u64* out, decltype(&fmt_class_string<int>::format) func, std::string_view value) bool cfg::try_to_enum_value(u64* out, decltype(&fmt_class_string<int>::format) func, std::string_view value, std::string_view name)
{ {
u64 max = umax; u64 max = umax;
@@ -313,13 +313,13 @@ bool cfg::try_to_enum_value(u64* out, decltype(&fmt_class_string<int>::format) f
if (ret.ec != std::errc() || ret.ptr != end) if (ret.ec != std::errc() || ret.ptr != end)
{ {
if (out) cfg_log.error("cfg::try_to_enum_value('%s'): invalid enum or integer", value); if (out) cfg_log.error("cfg::try_to_enum_value('%s', '%s'): invalid enum or integer", value, name);
return false; return false;
} }
if (result > max) if (result > max)
{ {
if (out) cfg_log.error("cfg::try_to_enum_value('%s'): out of bounds(val=%u, min=0, max=%u)", value, result, max); if (out) cfg_log.error("cfg::try_to_enum_value('%s', '%s'): out of bounds(val=%u, min=0, max=%u)", value, name, result, max);
return false; return false;
} }
@@ -468,11 +468,11 @@ void cfg::encode(YAML::Emitter& out, const cfg::_base& rhs)
} }
} }
void cfg::decode(const YAML::Node& data, cfg::_base& rhs, bool dynamic) bool cfg::decode(const YAML::Node& data, cfg::_base& rhs, bool dynamic, bool strict)
{ {
if (dynamic && !rhs.get_is_dynamic()) if (dynamic && !rhs.get_is_dynamic())
{ {
return; return true;
} }
switch (rhs.get_type()) switch (rhs.get_type())
@@ -481,9 +481,11 @@ void cfg::decode(const YAML::Node& data, cfg::_base& rhs, bool dynamic)
{ {
if (data.IsScalar() || data.IsSequence()) if (data.IsScalar() || data.IsSequence())
{ {
return; // ??? return false;
} }
bool success = true;
for (const auto& pair : data) for (const auto& pair : data)
{ {
if (!pair.first.IsScalar()) continue; if (!pair.first.IsScalar()) continue;
@@ -493,12 +495,17 @@ void cfg::decode(const YAML::Node& data, cfg::_base& rhs, bool dynamic)
{ {
if (node->get_name() == pair.first.Scalar()) if (node->get_name() == pair.first.Scalar())
{ {
decode(pair.second, *node, dynamic); if (!decode(pair.second, *node, dynamic, strict) && strict)
{
success = false;
}
break;
} }
} }
} }
break; return success;
} }
case type::set: case type::set:
{ {
@@ -516,7 +523,7 @@ void cfg::decode(const YAML::Node& data, cfg::_base& rhs, bool dynamic)
{ {
if (!data.IsMap()) if (!data.IsMap())
{ {
return; return false;
} }
map_of_type<std::string> values; map_of_type<std::string> values;
@@ -535,7 +542,7 @@ void cfg::decode(const YAML::Node& data, cfg::_base& rhs, bool dynamic)
{ {
if (data.IsScalar() || data.IsSequence()) if (data.IsScalar() || data.IsSequence())
{ {
return; // ??? return false;
} }
map_of_type<logs::level> values; map_of_type<logs::level> values;
@@ -545,10 +552,12 @@ void cfg::decode(const YAML::Node& data, cfg::_base& rhs, bool dynamic)
if (!pair.first.IsScalar() || !pair.second.IsScalar()) continue; if (!pair.first.IsScalar() || !pair.second.IsScalar()) continue;
u64 value; u64 value;
if (cfg::try_to_enum_value(&value, &fmt_class_string<logs::level>::format, pair.second.Scalar())) if (!cfg::try_to_enum_value(&value, &fmt_class_string<logs::level>::format, pair.second.Scalar(), pair.first.Scalar()) && strict)
{ {
values.emplace(pair.first.Scalar(), static_cast<logs::level>(static_cast<int>(value))); return false;
} }
values.emplace(pair.first.Scalar(), static_cast<logs::level>(static_cast<int>(value)));
} }
static_cast<log_entry&>(rhs).set_map(std::move(values)); static_cast<log_entry&>(rhs).set_map(std::move(values));
@@ -558,7 +567,7 @@ void cfg::decode(const YAML::Node& data, cfg::_base& rhs, bool dynamic)
{ {
if (!data.IsMap()) if (!data.IsMap())
{ {
return; // ??? return false;
} }
map_of_type<device_info> values; map_of_type<device_info> values;
@@ -598,12 +607,17 @@ void cfg::decode(const YAML::Node& data, cfg::_base& rhs, bool dynamic)
if (YAML::convert<std::string>::decode(data, value)) if (YAML::convert<std::string>::decode(data, value))
{ {
rhs.from_string(value, dynamic); if (!rhs.from_string(value, dynamic) && strict)
{
return false;
}
} }
break; // ??? break; // ???
} }
} }
return true;
} }
std::string cfg::node::to_string() const std::string cfg::node::to_string() const
@@ -620,8 +634,7 @@ bool cfg::node::from_string(std::string_view value, bool dynamic)
if (error.empty()) if (error.empty())
{ {
cfg::decode(result, *this, dynamic); return cfg::decode(result, *this, dynamic, false);
return true;
} }
cfg_log.error("Failed to load node: %s", error); cfg_log.error("Failed to load node: %s", error);
@@ -644,6 +657,19 @@ void cfg::node::restore_defaults()
} }
} }
bool cfg::node::validate(std::string_view value)
{
auto [result, error] = yaml_load(std::string(value));
if (error.empty())
{
return cfg::decode(result, *this, false, true);
}
cfg_log.error("Failed to load node: %s", error);
return false;
}
std::string cfg::map_entry::get_value(std::string_view key) std::string cfg::map_entry::get_value(std::string_view key)
{ {
if (auto it = m_map.find(key); it != m_map.end()) if (auto it = m_map.find(key); it != m_map.end())
+12 -9
View File
@@ -25,7 +25,7 @@ namespace cfg
std::vector<std::string> make_float_range(f64 min, f64 max); std::vector<std::string> make_float_range(f64 min, f64 max);
// Internal hack // Internal hack
bool try_to_enum_value(u64* out, decltype(&fmt_class_string<int>::format) func, std::string_view); bool try_to_enum_value(u64* out, decltype(&fmt_class_string<int>::format) func, std::string_view value, std::string_view name = {});
// Internal hack // Internal hack
std::vector<std::string> try_to_enum_list(decltype(&fmt_class_string<int>::format) func); std::vector<std::string> try_to_enum_list(decltype(&fmt_class_string<int>::format) func);
@@ -110,7 +110,7 @@ namespace cfg
} }
// Try to convert from string (optional) // Try to convert from string (optional)
virtual bool from_string(std::string_view, bool /*dynamic*/ = false); virtual bool from_string(std::string_view value, bool dynamic = false);
// Get string list (optional) // Get string list (optional)
virtual std::vector<std::string> to_list() const virtual std::vector<std::string> to_list() const
@@ -161,6 +161,9 @@ namespace cfg
// Restore default members // Restore default members
void restore_defaults() override; void restore_defaults() override;
// Try to convert from string and validate
bool validate(std::string_view value);
}; };
class _bool final : public _base class _bool final : public _base
@@ -301,7 +304,7 @@ namespace cfg
{ {
u64 result; u64 result;
if (try_to_enum_value(&result, &fmt_class_string<T>::format, value)) if (try_to_enum_value(&result, &fmt_class_string<T>::format, value, m_name))
{ {
// No narrowing check, it's hard to do right there // No narrowing check, it's hard to do right there
m_value = static_cast<T>(static_cast<std::underlying_type_t<T>>(result)); m_value = static_cast<T>(static_cast<std::underlying_type_t<T>>(result));
@@ -382,7 +385,7 @@ namespace cfg
bool from_string(std::string_view value, bool /*dynamic*/ = false) override bool from_string(std::string_view value, bool /*dynamic*/ = false) override
{ {
s64 result; s64 result;
if (try_to_int64(&result, value, Min, Max)) if (try_to_int64(&result, value, Min, Max, m_name))
{ {
m_value = static_cast<int_type>(result); m_value = static_cast<int_type>(result);
return true; return true;
@@ -451,7 +454,7 @@ namespace cfg
std::string to_string() const override std::string to_string() const override
{ {
std::string result; std::string result;
if (try_to_string(&result, m_value)) if (try_to_string(&result, m_value, m_name))
{ {
return result; return result;
} }
@@ -462,7 +465,7 @@ namespace cfg
std::string def_to_string() const override std::string def_to_string() const override
{ {
std::string result; std::string result;
if (try_to_string(&result, def)) if (try_to_string(&result, def, m_name))
{ {
return result; return result;
} }
@@ -473,7 +476,7 @@ namespace cfg
bool from_string(std::string_view value, bool /*dynamic*/ = false) override bool from_string(std::string_view value, bool /*dynamic*/ = false) override
{ {
f64 result; f64 result;
if (try_to_float(&result, value, Min, Max)) if (try_to_float(&result, value, Min, Max, m_name))
{ {
m_value = static_cast<float_type>(result); m_value = static_cast<float_type>(result);
return true; return true;
@@ -560,7 +563,7 @@ namespace cfg
bool from_string(std::string_view value, bool /*dynamic*/ = false) override bool from_string(std::string_view value, bool /*dynamic*/ = false) override
{ {
u64 result; u64 result;
if (try_to_uint64(&result, value, Min, Max)) if (try_to_uint64(&result, value, Min, Max, m_name))
{ {
m_value = static_cast<int_type>(result); m_value = static_cast<int_type>(result);
return true; return true;
@@ -646,7 +649,7 @@ namespace cfg
bool from_string(std::string_view value, bool /*dynamic*/ = false) override bool from_string(std::string_view value, bool /*dynamic*/ = false) override
{ {
u128 result; u128 result;
if (try_to_uint128(&result, value)) if (try_to_uint128(&result, value, m_name))
{ {
m_value = result; m_value = result;
return true; return true;
+5 -5
View File
@@ -23,19 +23,19 @@ inline void strcpy_trunc(D&& dst, const T& src)
} }
// Convert string to signed integer // Convert string to signed integer
bool try_to_int64(s64* out, std::string_view value, s64 min, s64 max); bool try_to_int64(s64* out, std::string_view value, s64 min, s64 max, std::string_view name = {});
// Convert string to unsigned integer // Convert string to unsigned integer
bool try_to_uint64(u64* out, std::string_view value, u64 min, u64 max); bool try_to_uint64(u64* out, std::string_view value, u64 min, u64 max, std::string_view name = {});
// Convert string to unsigned int128_t // Convert string to unsigned int128_t
bool try_to_uint128(u128* out, std::string_view value); bool try_to_uint128(u128* out, std::string_view value, std::string_view name = {});
// Convert string to float // Convert string to float
bool try_to_float(f64* out, std::string_view value, f64 min, f64 max); bool try_to_float(f64* out, std::string_view value, f64 min, f64 max, std::string_view name = {});
// Convert float to string locale independent // Convert float to string locale independent
bool try_to_string(std::string* out, const f64& value); bool try_to_string(std::string* out, f64 value, std::string_view name = {});
// Get the file extension of a file path ("png", "jpg", etc.) // Get the file extension of a file path ("png", "jpg", etc.)
std::string get_file_extension(const std::string& file_path); std::string get_file_extension(const std::string& file_path);
+2 -2
View File
@@ -1385,8 +1385,8 @@ error_code cellVdecGetPictureExt(ppu_thread& ppu, u32 handle, vm::cptr<CellVdecP
vdec->sws = sws_getCachedContext(vdec->sws, w, h, in_f, w, h, out_f, SWS_POINT, nullptr, nullptr, nullptr); vdec->sws = sws_getCachedContext(vdec->sws, w, h, in_f, w, h, out_f, SWS_POINT, nullptr, nullptr, nullptr);
u8* in_data[4] = { frame->data[0], frame->data[1], frame->data[2], alpha_plane.get() }; const u8* in_data[4] = { frame->data[0], frame->data[1], frame->data[2], alpha_plane.get() };
int in_line[4] = { frame->linesize[0], frame->linesize[1], frame->linesize[2], w * 1 }; const int in_line[4] = { frame->linesize[0], frame->linesize[1], frame->linesize[2], w * 1 };
u8* out_data[4] = { outBuff.get_ptr() }; u8* out_data[4] = { outBuff.get_ptr() };
int out_line[4] = { w * 4 }; // RGBA32 or ARGB32 int out_line[4] = { w * 4 }; // RGBA32 or ARGB32
+2 -2
View File
@@ -473,7 +473,7 @@ void lv2_exitspawn(ppu_thread& ppu, std::vector<std::string>& argv, std::vector<
}; };
Emu.after_kill_callback = [func = std::move(func), argv = std::move(argv), envp = std::move(envp), data = std::move(data), Emu.after_kill_callback = [func = std::move(func), argv = std::move(argv), envp = std::move(envp), data = std::move(data),
disc = std::move(disc), path = std::move(path), hdd1 = std::move(hdd1), old_config = Emu.GetUsedConfig(), klic]() mutable disc = std::move(disc), path = std::move(path), hdd1 = std::move(hdd1), old_config = Emu.GetUsedConfig(), old_db_config = Emu.GetUsedDatabaseConfig(), klic]() mutable
{ {
Emu.argv = std::move(argv); Emu.argv = std::move(argv);
Emu.envp = std::move(envp); Emu.envp = std::move(envp);
@@ -489,7 +489,7 @@ void lv2_exitspawn(ppu_thread& ppu, std::vector<std::string>& argv, std::vector<
Emu.SetForceBoot(true); Emu.SetForceBoot(true);
auto res = Emu.BootGame(path, "", true, cfg_mode::continuous, old_config); auto res = Emu.BootGame(path, "", true, cfg_mode::continuous, old_config, old_db_config);
if (res != game_boot_result::no_errors) if (res != game_boot_result::no_errors)
{ {
+51 -36
View File
@@ -159,6 +159,7 @@ void fmt_class_string<game_boot_result>::format(std::string& out, u64 arg)
case game_boot_result::still_running: return "Game is still running"; case game_boot_result::still_running: return "Game is still running";
case game_boot_result::already_added: return "Game was already added"; case game_boot_result::already_added: return "Game was already added";
case game_boot_result::currently_restricted: return "Booting is restricted at the time being"; case game_boot_result::currently_restricted: return "Booting is restricted at the time being";
case game_boot_result::database_config_missing: return "Could not find config in database";
} }
return unknown; return unknown;
}); });
@@ -176,6 +177,7 @@ void fmt_class_string<cfg_mode>::format(std::string& out, u64 arg)
case cfg_mode::global: return "global config"; case cfg_mode::global: return "global config";
case cfg_mode::config_override: return "config override"; case cfg_mode::config_override: return "config override";
case cfg_mode::continuous: return "continuous config"; case cfg_mode::continuous: return "continuous config";
case cfg_mode::database_config: return "database config";
case cfg_mode::default_config: return "default config"; case cfg_mode::default_config: return "default config";
} }
return unknown; return unknown;
@@ -932,14 +934,14 @@ game_boot_result Emulator::GetElfPathFromDir(std::string& elf_path, const std::s
return game_boot_result::invalid_file_or_folder; return game_boot_result::invalid_file_or_folder;
} }
game_boot_result Emulator::BootGame(const std::string& path, const std::string& title_id, bool direct, cfg_mode config_mode, const std::string& config_path) game_boot_result Emulator::BootGame(const std::string& path, const std::string& title_id, bool direct, cfg_mode config_mode, const std::string& config_path, const std::string& db_config)
{ {
if (m_restrict_emu_state_change) if (m_restrict_emu_state_change)
{ {
return game_boot_result::currently_restricted; return game_boot_result::currently_restricted;
} }
auto save_args = std::make_tuple(m_path, m_path_original, argv, envp, data, disc, klic, hdd1, m_config_mode, m_config_path); auto save_args = std::make_tuple(m_path, m_path_original, argv, envp, data, disc, klic, hdd1, m_config_mode, m_config_path, m_db_config);
auto restore_on_no_boot = [&](game_boot_result result) auto restore_on_no_boot = [&](game_boot_result result)
{ {
@@ -949,7 +951,7 @@ game_boot_result Emulator::BootGame(const std::string& path, const std::string&
if (m_state == system_state::stopped) if (m_state == system_state::stopped)
{ {
std::tie(m_path, m_path_original, argv, envp, data, disc, klic, hdd1, m_config_mode, m_config_path) = std::move(save_args); std::tie(m_path, m_path_original, argv, envp, data, disc, klic, hdd1, m_config_mode, m_config_path, m_db_config) = std::move(save_args);
if (result != game_boot_result::no_errors) if (result != game_boot_result::no_errors)
{ {
@@ -964,7 +966,7 @@ game_boot_result Emulator::BootGame(const std::string& path, const std::string&
// Execute after Kill() is done // Execute after Kill() is done
Emu.after_kill_callback = [this, result, save_args = std::move(save_args)]() mutable Emu.after_kill_callback = [this, result, save_args = std::move(save_args)]() mutable
{ {
std::tie(m_path, m_path_original, argv, envp, data, disc, klic, hdd1, m_config_mode, m_config_path) = std::move(save_args); std::tie(m_path, m_path_original, argv, envp, data, disc, klic, hdd1, m_config_mode, m_config_path, m_db_config) = std::move(save_args);
if (result != game_boot_result::no_errors) if (result != game_boot_result::no_errors)
{ {
@@ -981,6 +983,7 @@ game_boot_result Emulator::BootGame(const std::string& path, const std::string&
m_config_mode = config_mode; m_config_mode = config_mode;
m_config_path = config_path; m_config_path = config_path;
m_db_config = db_config;
// Handle files and special paths inside Load unmodified // Handle files and special paths inside Load unmodified
if (direct || !fs::is_dir(path)) if (direct || !fs::is_dir(path))
@@ -1563,6 +1566,9 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
sys_log.notice("Version: APP_VER=%s VERSION=%s", version_app, version_disc); sys_log.notice("Version: APP_VER=%s VERSION=%s", version_app, version_disc);
{ {
// We add the database configuration if it is set, unless we are using a mode that specifically selects a different configuration.
bool add_database_config = !m_db_config.empty() && (m_config_mode == cfg_mode::database_config || m_config_mode == cfg_mode::custom || m_config_mode == cfg_mode::continuous);
if (m_config_mode == cfg_mode::custom_selection || (m_config_mode == cfg_mode::continuous && !m_config_path.empty())) if (m_config_mode == cfg_mode::custom_selection || (m_config_mode == cfg_mode::continuous && !m_config_path.empty()))
{ {
if (fs::file cfg_file{ m_config_path }) if (fs::file cfg_file{ m_config_path })
@@ -1605,6 +1611,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
{ {
g_cfg.name = config_path; g_cfg.name = config_path;
m_config_path = config_path; m_config_path = config_path;
add_database_config = false; // A custom config exists. Do not add the database config.
break; break;
} }
@@ -1613,6 +1620,21 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
} }
} }
if (add_database_config)
{
// Add database config
sys_log.notice("Applying database config");
if (g_cfg.from_string(m_db_config))
{
g_cfg.name = "database_config";
}
else
{
sys_log.error("Failed to apply database config");
}
}
// Disable incompatible settings // Disable incompatible settings
fixup_settings(&_psf); fixup_settings(&_psf);
@@ -3342,6 +3364,25 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
return; return;
} }
const auto reset_emu_state = [this]()
{
m_ar.reset();
argv.clear();
envp.clear();
data.clear();
disc.clear();
klic.clear();
hdd1.clear();
init_mem_containers = nullptr;
m_db_config.clear();
m_config_path.clear();
m_config_mode = cfg_mode::custom;
read_used_savestate_versions();
m_savestate_extension_flags1 = {};
m_emu_state_close_pending = false;
m_precompilation_option = {};
};
if (system_state old_state = m_state.fetch_op([](system_state& state) if (system_state old_state = m_state.fetch_op([](system_state& state)
{ {
if (state == system_state::stopping || state == system_state::stopped) if (state == system_state::stopping || state == system_state::stopped)
@@ -3360,21 +3401,8 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
} }
// Ensure clean state // Ensure clean state
m_ar.reset(); reset_emu_state();
argv.clear();
envp.clear();
data.clear();
disc.clear();
klic.clear();
hdd1.clear();
init_mem_containers = nullptr;
after_kill_callback = nullptr; after_kill_callback = nullptr;
m_config_path.clear();
m_config_mode = cfg_mode::custom;
read_used_savestate_versions();
m_savestate_extension_flags1 = {};
m_emu_state_close_pending = false;
m_precompilation_option = {};
// Enable logging // Enable logging
rpcs3::utils::configure_logs(true); rpcs3::utils::configure_logs(true);
@@ -3422,7 +3450,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
// There is no race condition because it is only accessed by the same thread // There is no race condition because it is only accessed by the same thread
std::shared_ptr<std::shared_ptr<void>> join_thread = std::make_shared<std::shared_ptr<void>>(); std::shared_ptr<std::shared_ptr<void>> join_thread = std::make_shared<std::shared_ptr<void>>();
*join_thread = make_ptr(new named_thread("Emulation Join Thread"sv, [join_thread, savestate, allow_autoexit, save_stage = save_stage ? *save_stage : savestate_stage{}, this]() mutable *join_thread = make_ptr(new named_thread("Emulation Join Thread"sv, [join_thread, reset_emu_state, savestate, allow_autoexit, save_stage = save_stage ? *save_stage : savestate_stage{}, this]() mutable
{ {
fs::pending_file file; fs::pending_file file;
@@ -3921,7 +3949,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
set_progress_message("Resetting Objects"); set_progress_message("Resetting Objects");
// Final termination from main thread (move the last ownership of join thread in order to destroy it) // Final termination from main thread (move the last ownership of join thread in order to destroy it)
CallFromMainThread([join_thread = std::move(join_thread), verbose_message, stop_watchdog, init_mtx, allow_autoexit, this]() CallFromMainThread([join_thread = std::move(join_thread), reset_emu_state, verbose_message, stop_watchdog, init_mtx, allow_autoexit, this]()
{ {
cpu_thread::cleanup(); cpu_thread::cleanup();
@@ -3967,20 +3995,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
m_stop_ctr.notify_all(); m_stop_ctr.notify_all();
// Boot arg cleanup (preserved in the case restarting) // Boot arg cleanup (preserved in the case restarting)
argv.clear(); reset_emu_state();
envp.clear();
data.clear();
disc.clear();
klic.clear();
hdd1.clear();
init_mem_containers = nullptr;
m_config_path.clear();
m_config_mode = cfg_mode::custom;
m_ar.reset();
read_used_savestate_versions();
m_savestate_extension_flags1 = {};
m_emu_state_close_pending = false;
m_precompilation_option = {};
if (!m_continuous_mode) if (!m_continuous_mode)
{ {
@@ -4055,14 +4070,14 @@ game_boot_result Emulator::Restart(bool graceful, bool reset_path)
if (!IsStopped()) if (!IsStopped())
{ {
auto save_args = std::make_tuple(argv, envp, data, disc, klic, hdd1, m_config_mode, m_config_path); auto save_args = std::make_tuple(argv, envp, data, disc, klic, hdd1, m_config_mode, m_config_path, m_db_config);
if (graceful) if (graceful)
GracefulShutdown(false, false); GracefulShutdown(false, false);
else else
Kill(false); Kill(false);
std::tie(argv, envp, data, disc, klic, hdd1, m_config_mode, m_config_path) = std::move(save_args); std::tie(argv, envp, data, disc, klic, hdd1, m_config_mode, m_config_path, m_db_config) = std::move(save_args);
} }
else else
{ {
+8 -1
View File
@@ -58,6 +58,7 @@ enum class game_boot_result : u32
still_running, still_running,
already_added, already_added,
currently_restricted, currently_restricted,
database_config_missing,
}; };
constexpr bool is_error(game_boot_result res) constexpr bool is_error(game_boot_result res)
@@ -145,6 +146,7 @@ class Emulator final
cfg_mode m_config_mode = cfg_mode::custom; cfg_mode m_config_mode = cfg_mode::custom;
std::string m_config_path; std::string m_config_path;
std::string m_db_config;
std::string m_path; std::string m_path;
std::string m_path_old; std::string m_path_old;
std::string m_path_original; std::string m_path_original;
@@ -366,6 +368,11 @@ public:
return m_config_path; return m_config_path;
} }
const std::string& GetUsedDatabaseConfig() const
{
return m_db_config;
}
bool IsChildProcess() const bool IsChildProcess() const
{ {
return m_config_mode == cfg_mode::continuous; return m_config_mode == cfg_mode::continuous;
@@ -415,7 +422,7 @@ public:
return emulation_state_guard_t{this}; return emulation_state_guard_t{this};
} }
game_boot_result BootGame(const std::string& path, const std::string& title_id = "", bool direct = false, cfg_mode config_mode = cfg_mode::custom, const std::string& config_path = ""); game_boot_result BootGame(const std::string& path, const std::string& title_id = "", bool direct = false, cfg_mode config_mode = cfg_mode::custom, const std::string& config_path = "", const std::string& db_config = "");
bool BootRsxCapture(const std::string& path); bool BootRsxCapture(const std::string& path);
void SetForceBoot(bool force_boot); void SetForceBoot(bool force_boot);
+1
View File
@@ -7,5 +7,6 @@ enum class cfg_mode
global, // Use global config. global, // Use global config.
config_override, // Use config override. This does not use the global VFS settings! Fall back to global config. config_override, // Use config override. This does not use the global VFS settings! Fall back to global config.
continuous, // Use same config as on last boot. Fall back to global config. continuous, // Use same config as on last boot. Fall back to global config.
database_config, // Use database config. Fall back to global config.
default_config // Use the default values of the config entries. default_config // Use the default values of the config entries.
}; };
+17
View File
@@ -296,6 +296,9 @@
<ClCompile Include="QTGeneratedFiles\Debug\moc_game_list_context_menu.cpp"> <ClCompile Include="QTGeneratedFiles\Debug\moc_game_list_context_menu.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile> </ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_config_database.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_game_compatibility.cpp"> <ClCompile Include="QTGeneratedFiles\Debug\moc_game_compatibility.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile> </ClCompile>
@@ -602,6 +605,9 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_game_list_context_menu.cpp"> <ClCompile Include="QTGeneratedFiles\Release\moc_game_list_context_menu.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile> </ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_config_database.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_game_compatibility.cpp"> <ClCompile Include="QTGeneratedFiles\Release\moc_game_compatibility.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild> <ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile> </ClCompile>
@@ -912,6 +918,7 @@
<ClCompile Include="rpcs3qt\video_label.cpp" /> <ClCompile Include="rpcs3qt\video_label.cpp" />
<ClCompile Include="rpcs3qt\_discord_utils.cpp" /> <ClCompile Include="rpcs3qt\_discord_utils.cpp" />
<ClCompile Include="rpcs3qt\find_dialog.cpp" /> <ClCompile Include="rpcs3qt\find_dialog.cpp" />
<ClCompile Include="rpcs3qt\config_database.cpp" />
<ClCompile Include="rpcs3qt\game_compatibility.cpp" /> <ClCompile Include="rpcs3qt\game_compatibility.cpp" />
<ClCompile Include="rpcs3qt\game_list_grid.cpp" /> <ClCompile Include="rpcs3qt\game_list_grid.cpp" />
<ClCompile Include="rpcs3qt\progress_dialog.cpp" /> <ClCompile Include="rpcs3qt\progress_dialog.cpp" />
@@ -1855,6 +1862,16 @@
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent"</Command> <Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent"</Command>
</CustomBuild> </CustomBuild>
<ClInclude Include="rpcs3qt\custom_table_widget_item.h" /> <ClInclude Include="rpcs3qt\custom_table_widget_item.h" />
<CustomBuild Include="rpcs3qt\config_database.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing config_database.h...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing config_database.h...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent"</Command>
</CustomBuild>
<CustomBuild Include="rpcs3qt\game_compatibility.h"> <CustomBuild Include="rpcs3qt\game_compatibility.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs> <AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing game_compatibility.h...</Message> <Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing game_compatibility.h...</Message>
+12
View File
@@ -519,6 +519,15 @@
<ClCompile Include="rpcs3qt\progress_dialog.cpp"> <ClCompile Include="rpcs3qt\progress_dialog.cpp">
<Filter>Gui\misc dialogs</Filter> <Filter>Gui\misc dialogs</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_config_database.cpp">
<Filter>Generated Files\Debug</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_config_database.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\config_database.cpp">
<Filter>Gui\game list</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_game_compatibility.cpp"> <ClCompile Include="QTGeneratedFiles\Debug\moc_game_compatibility.cpp">
<Filter>Generated Files\Debug</Filter> <Filter>Generated Files\Debug</Filter>
</ClCompile> </ClCompile>
@@ -1651,6 +1660,9 @@
<CustomBuild Include="rpcs3qt\save_manager_dialog.h"> <CustomBuild Include="rpcs3qt\save_manager_dialog.h">
<Filter>Gui\save</Filter> <Filter>Gui\save</Filter>
</CustomBuild> </CustomBuild>
<CustomBuild Include="rpcs3qt\config_database.h">
<Filter>Gui\game list</Filter>
</CustomBuild>
<CustomBuild Include="rpcs3qt\game_compatibility.h"> <CustomBuild Include="rpcs3qt\game_compatibility.h">
<Filter>Gui\game list</Filter> <Filter>Gui\game list</Filter>
</CustomBuild> </CustomBuild>
+1
View File
@@ -11,6 +11,7 @@ add_library(rpcs3_ui STATIC
clans_settings_dialog.cpp clans_settings_dialog.cpp
config_adapter.cpp config_adapter.cpp
config_checker.cpp config_checker.cpp
config_database.cpp
curl_handle.cpp curl_handle.cpp
custom_dialog.cpp custom_dialog.cpp
custom_table_widget_item.cpp custom_table_widget_item.cpp
+228
View File
@@ -0,0 +1,228 @@
#include "stdafx.h"
#include "config_database.h"
#include "gui_settings.h"
#include "downloader.h"
#include "Emu/system_config.h"
LOG_CHANNEL(gui_log, "GUI");
config_database::config_database(std::shared_ptr<gui_settings> settings, QWidget* parent)
: QObject(parent)
, m_gui_settings(std::move(settings))
{
m_filepath = m_gui_settings->GetSettingsDir() + "/config_database.dat";
m_downloader = new downloader(parent);
request_config_database();
connect(m_downloader, &downloader::signal_download_error, this, &config_database::handle_download_error);
connect(m_downloader, &downloader::signal_download_finished, this, &config_database::handle_download_finished);
connect(m_downloader, &downloader::signal_download_canceled, this, &config_database::handle_download_canceled);
}
config_database::~config_database()
{
}
bool config_database::has_config(const std::string& title_id) const
{
return m_config_database.contains(title_id);
}
std::optional<std::string> config_database::get_config(const std::string& title_id)
{
if (!m_config_database.contains(title_id))
{
gui_log.error("Config database does not contain '%s'", title_id);
return std::nullopt;
}
QFile file(m_filepath);
if (!file.exists())
{
gui_log.error("Config database file not found: %s", m_filepath);
return std::nullopt;
}
if (!file.open(QIODevice::ReadOnly))
{
gui_log.error("Config database error - Could not read database from file: %s", m_filepath);
return std::nullopt;
}
const QByteArray content = file.readAll();
file.close();
return read_json(content, false, title_id);
}
void config_database::request_config_database(bool online)
{
if (!online)
{
// Retrieve database from file
QFile file(m_filepath);
if (!file.exists())
{
gui_log.notice("Config database file not found: %s", m_filepath);
return;
}
if (!file.open(QIODevice::ReadOnly))
{
gui_log.error("Config database error - Could not read database from file: %s", m_filepath);
return;
}
const QByteArray content = file.readAll();
file.close();
gui_log.notice("Finished reading config database from file: %s", m_filepath);
// Create new set from database
read_json(content, online);
return;
}
const std::string url = "https://api.rpcs3.net/config/?api=v1";
gui_log.notice("Beginning config database download from: %s", url);
m_downloader->start(url, true, true, true, tr("Downloading Config Database"));
Q_EMIT download_started();
}
void config_database::handle_download_error(const QString& error)
{
Q_EMIT download_error(error);
}
void config_database::handle_download_finished(const QByteArray& content)
{
gui_log.notice("Config database download finished");
// Create new map from database and write database to file if database was valid
if (read_json(content, true))
{
// Write database to file
QFile file(m_filepath);
if (file.exists())
{
gui_log.notice("Config database file found: %s", m_filepath);
}
if (!file.open(QIODevice::WriteOnly))
{
gui_log.error("Config database error - Could not write database to file: %s", m_filepath);
return;
}
file.write(content);
file.close();
gui_log.success("Wrote config database to file: %s", m_filepath);
}
Q_EMIT download_finished();
}
void config_database::handle_download_canceled()
{
Q_EMIT download_canceled();
}
std::optional<std::string> config_database::read_json(const QByteArray& data, bool after_download, const std::string& serial)
{
QJsonParseError error {};
const QJsonDocument json_document = QJsonDocument::fromJson(data, &error);
if (!json_document.isObject())
{
gui_log.error("Config database error - Invalid JSON: '%s'", error.errorString());
return std::nullopt;
}
const QJsonObject json_data = json_document.object();
const int return_code = json_data["return_code"].toInt(-255);
if (return_code < 0)
{
if (after_download)
{
std::string error_message;
switch (return_code)
{
case -1: error_message = "Server Error - Internal Error"; break;
case -2: error_message = "Server Error - Maintenance Mode"; break;
case -255: error_message = "Server Error - Return code not found"; break;
default: error_message = "Server Error - Unknown Error"; break;
}
gui_log.error("%s: return code %d", error_message, return_code);
Q_EMIT download_error(QString::fromStdString(error_message) + " " + QString::number(return_code));
}
else
{
gui_log.error("Config database error - Invalid: return code %d", return_code);
}
return std::nullopt;
}
if (!json_data["games"].isObject())
{
gui_log.error("Config database error - No games found");
return std::nullopt;
}
std::unique_ptr<cfg_root> config = std::make_unique<cfg_root>();
const QJsonObject json_games = json_data["games"].toObject();
const auto validate = [&json_games, &config](const QString& serial) -> std::optional<std::string>
{
if (!json_games[serial].isObject())
{
gui_log.error("Config database error - Unusable object %s", serial);
return std::nullopt;
}
const QJsonObject game = json_games[serial].toObject();
if (!game["config"].isString())
{
gui_log.error("Config database error - Unusable game string %s (config missing)", serial);
return std::nullopt;
}
const std::string content = game["config"].toString().toStdString();
// Verify config
if (!config->validate(content))
{
gui_log.error("Config database error - Invalid config for %s", serial);
return std::nullopt;
}
return content;
};
if (serial.empty())
{
m_config_database.clear();
// Retrieve status data for every valid entry
for (const QString& serial : json_games.keys())
{
if (validate(serial))
{
// Add title to set
m_config_database.insert(serial.toStdString());
}
}
return std::string();
}
return validate(QString::fromStdString(serial));
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <QJsonObject>
#include <optional>
class downloader;
class gui_settings;
class config_database : public QObject
{
Q_OBJECT
public:
config_database(std::shared_ptr<gui_settings> settings, QWidget* parent);
virtual ~config_database();
bool has_config(const std::string& title_id) const;
std::optional<std::string> get_config(const std::string& title_id);
/** Reads database. If online set to true: Downloads and writes the database to file */
void request_config_database(bool online = false);
Q_SIGNALS:
void download_started();
void download_finished();
void download_canceled();
void download_error(const QString& error);
private Q_SLOTS:
void handle_download_error(const QString& error);
void handle_download_finished(const QByteArray& content);
void handle_download_canceled();
private:
/** Creates new set from the database. Returns config for the optional serial. */
std::optional<std::string> read_json(const QByteArray& data, bool after_download, const std::string& serial = "");
std::shared_ptr<gui_settings> m_gui_settings;
QString m_filepath;
downloader* m_downloader = nullptr;
std::set<std::string> m_config_database;
};
+17 -1
View File
@@ -117,7 +117,7 @@ bool emu_settings::Init()
return true; return true;
} }
void emu_settings::LoadSettings(const std::string& title_id, bool create_config_from_global) void emu_settings::LoadSettings(const std::string& title_id, bool create_config_from_global, const std::string& db_config)
{ {
m_title_id = title_id; m_title_id = title_id;
@@ -159,6 +159,22 @@ void emu_settings::LoadSettings(const std::string& title_id, bool create_config_
.arg(QString::fromStdString(global_config_path)).arg(QString::fromStdString(global_error)), QMessageBox::Ok); .arg(QString::fromStdString(global_config_path)).arg(QString::fromStdString(global_error)), QMessageBox::Ok);
} }
} }
else if (!db_config.empty())
{
// Add database config
auto [config, error] = yaml_load(db_config);
if (config && error.empty())
{
m_current_settings += config;
}
else
{
cfg_log.fatal("Failed to load database config for '%s':\n%s", title_id, error);
QMessageBox::critical(nullptr, tr("Config Error"), tr("Failed to load database config:\nError: %1")
.arg(QString::fromStdString(error)), QMessageBox::Ok);
}
}
// Add game config // Add game config
if (!title_id.empty()) if (!title_id.empty())
+1 -1
View File
@@ -103,7 +103,7 @@ public:
midi_creator m_midi_creator; midi_creator m_midi_creator;
/** Loads the settings from path.*/ /** Loads the settings from path.*/
void LoadSettings(const std::string& title_id = "", bool create_config_from_global = true); void LoadSettings(const std::string& title_id = "", bool create_config_from_global = true, const std::string& db_config = "");
/** Fixes all registered invalid settings after asking the user for permission.*/ /** Fixes all registered invalid settings after asking the user for permission.*/
void OpenCorrectionDialog(QWidget* parent = Q_NULLPTR); void OpenCorrectionDialog(QWidget* parent = Q_NULLPTR);
+16 -7
View File
@@ -13,9 +13,9 @@
LOG_CHANNEL(compat_log, "Compat"); LOG_CHANNEL(compat_log, "Compat");
game_compatibility::game_compatibility(std::shared_ptr<gui_settings> gui_settings, QWidget* parent) game_compatibility::game_compatibility(std::shared_ptr<gui_settings> settings, QWidget* parent)
: QObject(parent) : QObject(parent)
, m_gui_settings(std::move(gui_settings)) , m_gui_settings(std::move(settings))
{ {
m_filepath = m_gui_settings->GetSettingsDir() + "/compat_database.dat"; m_filepath = m_gui_settings->GetSettingsDir() + "/compat_database.dat";
m_downloader = new downloader(parent); m_downloader = new downloader(parent);
@@ -58,7 +58,7 @@ void game_compatibility::handle_download_finished(const QByteArray& content)
compat_log.success("Wrote database to file: %s", m_filepath); compat_log.success("Wrote database to file: %s", m_filepath);
} }
// We have a new database in map, therefore refresh gamelist to new state // We have a new database in map, therefore refresh game list to new state
Q_EMIT DownloadFinished(); Q_EMIT DownloadFinished();
} }
@@ -69,7 +69,16 @@ void game_compatibility::handle_download_canceled()
bool game_compatibility::handle_json(const QByteArray& data, bool after_download) bool game_compatibility::handle_json(const QByteArray& data, bool after_download)
{ {
const QJsonObject json_data = QJsonDocument::fromJson(data).object(); QJsonParseError error {};
const QJsonDocument json_document = QJsonDocument::fromJson(data, &error);
if (!json_document.isObject())
{
compat_log.error("Database Error - Invalid JSON: '%s'", error.errorString());
return false;
}
const QJsonObject json_data = json_document.object();
const int return_code = json_data["return_code"].toInt(-255); const int return_code = json_data["return_code"].toInt(-255);
if (return_code < 0) if (return_code < 0)
@@ -220,11 +229,11 @@ void game_compatibility::RequestCompatibility(bool online)
m_downloader->start(url, true, true, true, tr("Downloading Database")); m_downloader->start(url, true, true, true, tr("Downloading Database"));
// We want to retrieve a new database, therefore refresh gamelist and indicate that // We want to retrieve a new database, therefore refresh game list and indicate that
Q_EMIT DownloadStarted(); Q_EMIT DownloadStarted();
} }
compat::status game_compatibility::GetCompatibility(const std::string& title_id) compat::status game_compatibility::GetCompatibility(const std::string& title_id) const
{ {
if (m_compat_database.empty()) if (m_compat_database.empty())
{ {
@@ -244,7 +253,7 @@ compat::status game_compatibility::GetStatusData(const QString& status) const
return ::at32(Status_Data, status); return ::at32(Status_Data, status);
} }
compat::package_info game_compatibility::GetPkgInfo(const QString& pkg_path, game_compatibility* compat) compat::package_info game_compatibility::GetPkgInfo(const QString& pkg_path, const game_compatibility* compat)
{ {
compat::package_info info; compat::package_info info;
+2 -2
View File
@@ -148,13 +148,13 @@ public:
void RequestCompatibility(bool online = false); void RequestCompatibility(bool online = false);
/** Returns the compatibility status for the requested title */ /** Returns the compatibility status for the requested title */
compat::status GetCompatibility(const std::string& title_id); compat::status GetCompatibility(const std::string& title_id) const;
/** Returns the data for the requested status */ /** Returns the data for the requested status */
compat::status GetStatusData(const QString& status) const; compat::status GetStatusData(const QString& status) const;
/** Returns package information like title, version, changelog etc. */ /** Returns package information like title, version, changelog etc. */
static compat::package_info GetPkgInfo(const QString& pkg_path, game_compatibility* compat); static compat::package_info GetPkgInfo(const QString& pkg_path, const game_compatibility* compat);
Q_SIGNALS: Q_SIGNALS:
void DownloadStarted(); void DownloadStarted();
+41 -10
View File
@@ -11,6 +11,7 @@
#include "pad_settings_dialog.h" #include "pad_settings_dialog.h"
#include "patch_manager_dialog.h" #include "patch_manager_dialog.h"
#include "persistent_settings.h" #include "persistent_settings.h"
#include "config_database.h"
#include "Utilities/File.h" #include "Utilities/File.h"
#include "Emu/system_utils.hpp" #include "Emu/system_utils.hpp"
@@ -65,7 +66,7 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
const bool is_current_running_game = game_list_actions::IsGameRunning(serial); const bool is_current_running_game = game_list_actions::IsGameRunning(serial);
// Make Actions // Make Actions
QAction* boot = new QAction(gameinfo->has_custom_config QAction* boot = new QAction((gameinfo->has_custom_config || gameinfo->has_database_config)
? (is_current_running_game ? (is_current_running_game
? tr("&Reboot with Global Configuration") ? tr("&Reboot with Global Configuration")
: tr("&Boot with Global Configuration")) : tr("&Boot with Global Configuration"))
@@ -88,7 +89,21 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
Q_EMIT m_game_list_frame->RequestBoot(gameinfo); Q_EMIT m_game_list_frame->RequestBoot(gameinfo);
}); });
} }
else
if (gameinfo->has_database_config)
{
QAction* boot_db = addAction(is_current_running_game
? tr("&Reboot with Database Configuration")
: tr("&Boot with Database Configuration"));
boot_db->setFont(font);
connect(boot_db, &QAction::triggered, m_game_list_frame, [this, gameinfo]
{
sys_log.notice("Booting from gamelist per context menu...");
Q_EMIT m_game_list_frame->RequestBoot(gameinfo, cfg_mode::database_config);
});
}
if (!gameinfo->has_custom_config && !gameinfo->has_database_config)
{ {
boot->setFont(font); boot->setFont(font);
} }
@@ -161,6 +176,8 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
: tr("&Create Custom Configuration From Global Settings")); : tr("&Create Custom Configuration From Global Settings"));
QAction* create_game_default_config = gameinfo->has_custom_config ? nullptr QAction* create_game_default_config = gameinfo->has_custom_config ? nullptr
: addAction(tr("&Create Custom Configuration From Default Settings")); : addAction(tr("&Create Custom Configuration From Default Settings"));
QAction* create_game_database_config = (gameinfo->has_custom_config || !gameinfo->has_database_config) ? nullptr
: addAction(tr("&Create Custom Configuration From Database Settings"));
QAction* pad_configure = addAction(gameinfo->has_custom_pad_config QAction* pad_configure = addAction(gameinfo->has_custom_pad_config
? tr("&Change Custom Gamepad Configuration") ? tr("&Change Custom Gamepad Configuration")
: tr("&Create Custom Gamepad Configuration")); : tr("&Create Custom Gamepad Configuration"));
@@ -578,6 +595,7 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
QAction* check_compat = addAction(tr("&Check Game Compatibility")); QAction* check_compat = addAction(tr("&Check Game Compatibility"));
QAction* download_compat = addAction(tr("&Download Compatibility Database")); QAction* download_compat = addAction(tr("&Download Compatibility Database"));
QAction* download_config_db = addAction(tr("&Download Config Database"));
addSeparator(); addSeparator();
@@ -601,9 +619,10 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
Q_EMIT m_game_list_frame->RequestBoot(gameinfo, cfg_mode::global); Q_EMIT m_game_list_frame->RequestBoot(gameinfo, cfg_mode::global);
}); });
auto configure_l = [this, current_game, gameinfo](bool create_cfg_from_global_cfg) const auto configure_game = [this, current_game, gameinfo](bool create_cfg_from_global_cfg, bool create_cfg_from_database)
{ {
settings_dialog dlg(m_gui_settings, m_emu_settings, 0, m_game_list_frame, &current_game, create_cfg_from_global_cfg); const std::optional<std::string> db_config = create_cfg_from_database ? m_game_list_frame->GetConfigDatabase()->get_config(gameinfo->info.serial) : "";
settings_dialog dlg(m_gui_settings, m_emu_settings, 0, m_game_list_frame, &current_game, create_cfg_from_global_cfg, db_config ? *db_config : "");
connect(&dlg, &settings_dialog::EmuSettingsApplied, [this, gameinfo]() connect(&dlg, &settings_dialog::EmuSettingsApplied, [this, gameinfo]()
{ {
@@ -618,14 +637,16 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
dlg.exec(); dlg.exec();
}; };
connect(configure, &QAction::triggered, this, [configure_game]() { configure_game(true, false); });
if (create_game_default_config) if (create_game_default_config)
{ {
connect(configure, &QAction::triggered, m_game_list_frame, [configure_l]() { configure_l(true); }); connect(create_game_default_config, &QAction::triggered, m_game_list_frame, [configure_game]() { configure_game(false, false); });
connect(create_game_default_config, &QAction::triggered, m_game_list_frame, [configure_l = std::move(configure_l)]() { configure_l(false); });
} }
else
if (create_game_database_config)
{ {
connect(configure, &QAction::triggered, m_game_list_frame, [configure_l = std::move(configure_l)]() { configure_l(true); }); connect(create_game_database_config, &QAction::triggered, m_game_list_frame, [configure_game]() { configure_game(false, true); });
} }
connect(pad_configure, &QAction::triggered, m_game_list_frame, [this, current_game, gameinfo]() connect(pad_configure, &QAction::triggered, m_game_list_frame, [this, current_game, gameinfo]()
@@ -671,7 +692,11 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
}); });
connect(download_compat, &QAction::triggered, m_game_list_frame, [this] connect(download_compat, &QAction::triggered, m_game_list_frame, [this]
{ {
ensure(m_game_list_frame->GetGameCompatibility())->RequestCompatibility(true); m_game_list_frame->GetGameCompatibility()->RequestCompatibility(true);
});
connect(download_config_db, &QAction::triggered, m_game_list_frame, [this]
{
m_game_list_frame->GetConfigDatabase()->request_config_database(true);
}); });
connect(rename_title, &QAction::triggered, m_game_list_frame, [this, name, serial = QString::fromStdString(serial), global_pos] connect(rename_title, &QAction::triggered, m_game_list_frame, [this, name, serial = QString::fromStdString(serial), global_pos]
{ {
@@ -935,7 +960,13 @@ void game_list_context_menu::show_multi_selection_context_menu(const std::vector
QAction* download_compat = addAction(tr("&Download Compatibility Database")); QAction* download_compat = addAction(tr("&Download Compatibility Database"));
connect(download_compat, &QAction::triggered, m_game_list_frame, [this] connect(download_compat, &QAction::triggered, m_game_list_frame, [this]
{ {
ensure(m_game_list_frame->GetGameCompatibility())->RequestCompatibility(true); m_game_list_frame->GetGameCompatibility()->RequestCompatibility(true);
});
QAction* download_config_db = addAction(tr("&Download Config Database"));
connect(download_config_db, &QAction::triggered, m_game_list_frame, [this]
{
m_game_list_frame->GetConfigDatabase()->request_config_database(true);
}); });
addSeparator(); addSeparator();
+28
View File
@@ -10,6 +10,7 @@
#include "game_list_table.h" #include "game_list_table.h"
#include "game_list_grid.h" #include "game_list_grid.h"
#include "game_list_grid_item.h" #include "game_list_grid_item.h"
#include "config_database.h"
#include "Emu/System.h" #include "Emu/System.h"
#include "Emu/vfs_config.h" #include "Emu/vfs_config.h"
@@ -74,6 +75,7 @@ game_list_frame::game_list_frame(std::shared_ptr<gui_settings> gui_settings, std
m_game_list->verticalScrollBar()->installEventFilter(this); m_game_list->verticalScrollBar()->installEventFilter(this);
m_game_compat = new game_compatibility(m_gui_settings, this); m_game_compat = new game_compatibility(m_gui_settings, this);
m_config_db = new config_database(m_gui_settings, this);
m_central_widget = new QStackedWidget(this); m_central_widget = new QStackedWidget(this);
m_central_widget->addWidget(m_game_list); m_central_widget->addWidget(m_game_list);
@@ -200,6 +202,22 @@ game_list_frame::game_list_frame(std::shared_ptr<gui_settings> gui_settings, std
QMessageBox::warning(this, tr("Warning!"), tr("Failed to retrieve the online compatibility database!\nFalling back to local database.\n\n%0").arg(error)); QMessageBox::warning(this, tr("Warning!"), tr("Failed to retrieve the online compatibility database!\nFalling back to local database.\n\n%0").arg(error));
}); });
connect(m_config_db, &config_database::download_started, this, [this]()
{
for (const auto& game : m_game_data)
{
game->has_database_config = false;
}
Refresh();
});
connect(m_config_db, &config_database::download_finished, this, &game_list_frame::OnConfigDatabaseFinished);
connect(m_config_db, &config_database::download_canceled, this, &game_list_frame::OnConfigDatabaseFinished);
connect(m_config_db, &config_database::download_error, this, [this](const QString& error)
{
OnConfigDatabaseFinished();
QMessageBox::warning(this, tr("Warning!"), tr("Failed to retrieve the online config database!\nFalling back to local database.\n\n%0").arg(error));
});
connect(m_game_list, &game_list::FocusToSearchBar, this, &game_list_frame::FocusToSearchBar); connect(m_game_list, &game_list::FocusToSearchBar, this, &game_list_frame::FocusToSearchBar);
connect(m_game_grid, &game_list_grid::FocusToSearchBar, this, &game_list_frame::FocusToSearchBar); connect(m_game_grid, &game_list_grid::FocusToSearchBar, this, &game_list_frame::FocusToSearchBar);
@@ -801,6 +819,7 @@ void game_list_frame::OnParsingFinished()
game.localized_category = std::move(qt_cat); game.localized_category = std::move(qt_cat);
game.compat = m_game_compat->GetCompatibility(game.info.serial); game.compat = m_game_compat->GetCompatibility(game.info.serial);
game.has_database_config = m_config_db->has_config(game.info.serial);
game.has_custom_config = fs::is_file(rpcs3::utils::get_custom_config_path(game.info.serial)); game.has_custom_config = fs::is_file(rpcs3::utils::get_custom_config_path(game.info.serial));
game.has_custom_pad_config = fs::is_file(rpcs3::utils::get_custom_input_config_path(game.info.serial)); game.has_custom_pad_config = fs::is_file(rpcs3::utils::get_custom_input_config_path(game.info.serial));
@@ -1024,6 +1043,15 @@ void game_list_frame::OnCompatFinished()
Refresh(); Refresh();
} }
void game_list_frame::OnConfigDatabaseFinished()
{
for (const auto& game : m_game_data)
{
game->has_database_config = m_config_db->has_config(game->info.serial);
}
Refresh();
}
void game_list_frame::ToggleCategoryFilter(const QStringList& categories, bool show) void game_list_frame::ToggleCategoryFilter(const QStringList& categories, bool show)
{ {
QStringList& filters = m_is_list_layout ? m_category_filters : m_grid_category_filters; QStringList& filters = m_is_list_layout ? m_category_filters : m_grid_category_filters;
+5 -1
View File
@@ -18,6 +18,7 @@
#include <optional> #include <optional>
#include <set> #include <set>
class config_database;
class game_list_table; class game_list_table;
class game_list_grid; class game_list_grid;
class gui_settings; class gui_settings;
@@ -53,7 +54,8 @@ public:
void SetShowHidden(bool show); void SetShowHidden(bool show);
game_compatibility* GetGameCompatibility() const { return m_game_compat; } game_compatibility* GetGameCompatibility() const { return ensure(m_game_compat); }
config_database* GetConfigDatabase() const { return ensure(m_config_db); }
const std::vector<game_info>& GetGameInfo() const { return m_game_data; } const std::vector<game_info>& GetGameInfo() const { return m_game_data; }
std::shared_ptr<game_list_actions> actions() const { return m_game_list_actions; } std::shared_ptr<game_list_actions> actions() const { return m_game_list_actions; }
std::shared_ptr<gui_settings> get_gui_settings() const { return m_gui_settings; } std::shared_ptr<gui_settings> get_gui_settings() const { return m_gui_settings; }
@@ -95,6 +97,7 @@ private Q_SLOTS:
void OnParsingFinished(); void OnParsingFinished();
void OnRefreshFinished(); void OnRefreshFinished();
void OnCompatFinished(); void OnCompatFinished();
void OnConfigDatabaseFinished();
void OnColClicked(int col); void OnColClicked(int col);
void ShowContextMenu(const QPoint& pos); void ShowContextMenu(const QPoint& pos);
void doubleClickedSlot(QTableWidgetItem* item); void doubleClickedSlot(QTableWidgetItem* item);
@@ -151,6 +154,7 @@ private:
// Game List // Game List
game_list_table* m_game_list = nullptr; game_list_table* m_game_list = nullptr;
game_compatibility* m_game_compat = nullptr; game_compatibility* m_game_compat = nullptr;
config_database* m_config_db = nullptr;
progress_dialog* m_progress_dialog = nullptr; progress_dialog* m_progress_dialog = nullptr;
std::map<int, QAction*> m_column_acts; std::map<int, QAction*> m_column_acts;
Qt::SortOrder m_col_sort_order{}; Qt::SortOrder m_col_sort_order{};
+2 -1
View File
@@ -13,9 +13,10 @@ struct gui_game_info
{ {
GameInfo info{}; GameInfo info{};
QString localized_category; QString localized_category;
compat::status compat; compat::status compat{};
QPixmap icon; QPixmap icon;
QPixmap pxmap; QPixmap pxmap;
bool has_database_config = false;
bool has_custom_config = false; bool has_custom_config = false;
bool has_custom_pad_config = false; bool has_custom_pad_config = false;
bool has_custom_icon = false; bool has_custom_icon = false;
+24 -2
View File
@@ -47,6 +47,7 @@
#include "music_player_dialog.h" #include "music_player_dialog.h"
#include "sound_effect_manager_dialog.h" #include "sound_effect_manager_dialog.h"
#include "recording_settings_dialog.h" #include "recording_settings_dialog.h"
#include "config_database.h"
#include <thread> #include <thread>
#include <unordered_set> #include <unordered_set>
@@ -493,6 +494,9 @@ void main_window::show_boot_error(game_boot_result status)
case game_boot_result::firmware_version: case game_boot_result::firmware_version:
message = tr("The game or PS3 application needs a more recent firmware version."); message = tr("The game or PS3 application needs a more recent firmware version.");
break; break;
case game_boot_result::database_config_missing:
message = tr("Could not find any configuration for this game in the database.");
break;
case game_boot_result::firmware_missing: // Handled elsewhere case game_boot_result::firmware_missing: // Handled elsewhere
case game_boot_result::already_added: // Handled elsewhere case game_boot_result::already_added: // Handled elsewhere
case game_boot_result::currently_restricted: case game_boot_result::currently_restricted:
@@ -530,7 +534,25 @@ void main_window::Boot(const std::string& path, const std::string& title_id, boo
m_app_icon = gui::utils::get_app_icon_from_path(path, title_id); m_app_icon = gui::utils::get_app_icon_from_path(path, title_id);
if (const auto error = Emu.BootGame(path, title_id, direct, config_mode, config_path); error != game_boot_result::no_errors) std::string db_config;
// Get database config if possible or if we are in database_config mode (to ensure we see an error on invalid use)
if (config_database* db = m_game_list_frame->GetConfigDatabase();
db->has_config(title_id) || config_mode == cfg_mode::database_config)
{
const std::optional<std::string> config = db->get_config(title_id);
if (!config)
{
gui_log.error("Boot failed: reason: no database config found for '%s'", title_id);
show_boot_error(game_boot_result::database_config_missing);
return;
}
db_config = *config;
}
if (const auto error = Emu.BootGame(path, title_id, direct, config_mode, config_path, db_config); error != game_boot_result::no_errors)
{ {
gui_log.error("Boot failed: reason: %s, path: %s", error, path); gui_log.error("Boot failed: reason: %s, path: %s", error, path);
show_boot_error(error); show_boot_error(error);
@@ -940,7 +962,7 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo
bool precompile_caches = false; bool precompile_caches = false;
bool canceled = false; bool canceled = false;
game_compatibility* compat = m_game_list_frame ? m_game_list_frame->GetGameCompatibility() : nullptr; const game_compatibility* compat = m_game_list_frame ? m_game_list_frame->GetGameCompatibility() : nullptr;
// Let the user choose the packages to install and select the order in which they shall be installed. // Let the user choose the packages to install and select the order in which they shall be installed.
pkg_install_dialog dlg(file_paths, compat, this); pkg_install_dialog dlg(file_paths, compat, this);
+10 -1
View File
@@ -1168,7 +1168,16 @@ void patch_manager_dialog::download_update(bool automatic, bool auto_accept)
bool patch_manager_dialog::handle_json(const QByteArray& data) bool patch_manager_dialog::handle_json(const QByteArray& data)
{ {
const QJsonObject json_data = QJsonDocument::fromJson(data).object(); QJsonParseError error {};
const QJsonDocument json_document = QJsonDocument::fromJson(data, &error);
if (!json_document.isObject())
{
patch_log.error("Patch download error - Invalid JSON: '%s'", error.errorString());
return false;
}
const QJsonObject json_data = json_document.object();
const int return_code = json_data["return_code"].toInt(-255); const int return_code = json_data["return_code"].toInt(-255);
if (return_code < 0) if (return_code < 0)
+1 -1
View File
@@ -24,7 +24,7 @@ enum Roles
DataSizeRole = Qt::UserRole + 5, DataSizeRole = Qt::UserRole + 5,
}; };
pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibility* compat, QWidget* parent) pkg_install_dialog::pkg_install_dialog(const QStringList& paths, const game_compatibility* compat, QWidget* parent)
: QDialog(parent) : QDialog(parent)
{ {
ensure(!paths.empty()); ensure(!paths.empty());
+1 -1
View File
@@ -17,7 +17,7 @@ class pkg_install_dialog : public QDialog
Q_OBJECT Q_OBJECT
public: public:
explicit pkg_install_dialog(const QStringList& paths, game_compatibility* compat, QWidget* parent = nullptr); explicit pkg_install_dialog(const QStringList& paths, const game_compatibility* compat, QWidget* parent = nullptr);
std::vector<compat::package_info> get_paths_to_install() const; std::vector<compat::package_info> get_paths_to_install() const;
bool precompile_caches() const { return m_precompile_caches; } bool precompile_caches() const { return m_precompile_caches; }
bool create_desktop_shortcuts() const { return m_create_desktop_shortcuts; } bool create_desktop_shortcuts() const { return m_create_desktop_shortcuts; }
+2 -2
View File
@@ -87,7 +87,7 @@ void remove_item(QComboBox* box, int data_value, int def_value)
extern const std::map<std::string_view, int> g_prx_list; extern const std::map<std::string_view, int> g_prx_list;
settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std::shared_ptr<emu_settings> emu_settings, int tab_index, QWidget* parent, const GameInfo* game, bool create_cfg_from_global_cfg) settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std::shared_ptr<emu_settings> emu_settings, int tab_index, QWidget* parent, const GameInfo* game, bool create_cfg_from_global_cfg, const std::string& db_config)
: QDialog(parent) : QDialog(parent)
, m_tab_index(tab_index) , m_tab_index(tab_index)
, ui(new Ui::settings_dialog) , ui(new Ui::settings_dialog)
@@ -132,7 +132,7 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
if (game) if (game)
{ {
m_emu_settings->LoadSettings(game->serial, create_cfg_from_global_cfg); m_emu_settings->LoadSettings(game->serial, create_cfg_from_global_cfg, db_config);
setWindowTitle(tr("Settings: [%0] %1", "Settings dialog").arg(QString::fromStdString(game->serial)).arg(QString::fromStdString(game->name))); setWindowTitle(tr("Settings: [%0] %1", "Settings dialog").arg(QString::fromStdString(game->serial)).arg(QString::fromStdString(game->name)));
} }
else else
+1 -1
View File
@@ -21,7 +21,7 @@ class settings_dialog : public QDialog
Q_OBJECT Q_OBJECT
public: public:
explicit settings_dialog(std::shared_ptr<gui_settings> gui_settings, std::shared_ptr<emu_settings> emu_settings, int tab_index = 0, QWidget* parent = nullptr, const GameInfo* game = nullptr, bool create_cfg_from_global_cfg = true); explicit settings_dialog(std::shared_ptr<gui_settings> gui_settings, std::shared_ptr<emu_settings> emu_settings, int tab_index = 0, QWidget* parent = nullptr, const GameInfo* game = nullptr, bool create_cfg_from_global_cfg = true, const std::string& db_config = "");
~settings_dialog(); ~settings_dialog();
void open() override; void open() override;
Q_SIGNALS: Q_SIGNALS:
+10 -1
View File
@@ -116,7 +116,16 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce
{ {
update_log.notice("Download of update info finished. automatic=%d, check_only=%d, auto_accept=%d", automatic, check_only, auto_accept); update_log.notice("Download of update info finished. automatic=%d, check_only=%d, auto_accept=%d", automatic, check_only, auto_accept);
const QJsonObject json_data = QJsonDocument::fromJson(data).object(); QJsonParseError error {};
const QJsonDocument json_document = QJsonDocument::fromJson(data, &error);
if (!json_document.isObject())
{
update_log.error("Update error - Invalid JSON: '%s'", error.errorString());
return false;
}
const QJsonObject json_data = json_document.object();
const int return_code = json_data["return_code"].toInt(-255); const int return_code = json_data["return_code"].toInt(-255);
m_update_info.hash_found = true; m_update_info.hash_found = true;