mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-05-07 13:37:46 +00:00
Merge branch 'master' into feature/iso-multigame-support
This commit is contained in:
commit
3528a7dcd7
59 changed files with 1290 additions and 323 deletions
|
|
@ -4,11 +4,15 @@
|
|||
cd build || exit 1
|
||||
|
||||
cd bin
|
||||
git clone --revision=32dceb35e2c95b46cec501033cbc3a1ddf32d6e8 https://github.com/KhronosGroup/MoltenVK.git
|
||||
cd MoltenVK
|
||||
./fetchDependencies --macos
|
||||
make macos MVK_USE_METAL_PRIVATE_API=1
|
||||
cd ../
|
||||
|
||||
mkdir -p "rpcs3.app/Contents/Resources/vulkan/icd.d" || true
|
||||
wget https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-macos-privateapi.tar
|
||||
tar -xvf MoltenVK-macos-privateapi.tar
|
||||
cp "MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib"
|
||||
cp "MoltenVK/MoltenVK/dynamic/dylib/macOS/MoltenVK_icd.json" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json"
|
||||
cp "MoltenVK/Package/Latest/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib"
|
||||
cp "MoltenVK/Package/Latest/MoltenVK/dynamic/dylib/macOS/MoltenVK_icd.json" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json"
|
||||
sed -i '' "s/.\//..\/..\/..\/Frameworks\//g" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json"
|
||||
|
||||
cp "$(realpath $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib"
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ mkdir ./bin/config
|
|||
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://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
|
||||
mkdir -p ./bin/share/qt6/translations
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ mkdir ./bin/config
|
|||
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://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
|
||||
mkdir -p ./bin/qt6/translations
|
||||
|
|
|
|||
30
3rdparty/curl/CMakeLists.txt
vendored
30
3rdparty/curl/CMakeLists.txt
vendored
|
|
@ -7,24 +7,24 @@ if(USE_SYSTEM_CURL)
|
|||
target_link_libraries(3rdparty_libcurl INTERFACE CURL::libcurl)
|
||||
else()
|
||||
message(STATUS "RPCS3: building libcurl + wolfssl submodules")
|
||||
set(BUILD_CURL_EXE OFF CACHE BOOL "Set to ON to build curl executable.")
|
||||
set(BUILD_STATIC_CURL OFF CACHE BOOL "Set to ON to build curl executable with static libcurl.")
|
||||
set(BUILD_STATIC_LIBS ON CACHE BOOL "Set to ON to build static libcurl.")
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Set to ON to build shared libcurl.")
|
||||
set(BUILD_CURL_EXE OFF CACHE INTERNAL "")
|
||||
set(BUILD_STATIC_CURL OFF CACHE INTERNAL "")
|
||||
set(BUILD_STATIC_LIBS ON CACHE INTERNAL "")
|
||||
set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "")
|
||||
find_package(WolfSSL REQUIRED)
|
||||
set(CURL_USE_WOLFSSL ON CACHE BOOL "enable wolfSSL for SSL/TLS")
|
||||
set(CURL_USE_OPENSSL OFF CACHE BOOL "Use OpenSSL code. Experimental")
|
||||
set(HTTP_ONLY ON CACHE BOOL "disables all protocols except HTTP (This overrides all CURL_DISABLE_* options)")
|
||||
set(USE_LIBIDN2 OFF CACHE BOOL "Use libidn2 for IDN support") # Disabled because MacOS CI doesn't work otherwise
|
||||
set(CURL_CA_PATH "none" CACHE STRING "Location of default CA path. Set 'none' to disable or 'auto' for auto-detection. Defaults to 'auto'.")
|
||||
option(CURL_DISABLE_INSTALL "Disable installation targets" ON)
|
||||
set(CURL_USE_WOLFSSL ON CACHE INTERNAL "")
|
||||
set(CURL_USE_OPENSSL OFF CACHE INTERNAL "")
|
||||
set(HTTP_ONLY ON CACHE INTERNAL "")
|
||||
set(USE_LIBIDN2 OFF CACHE INTERNAL "") # Disabled because MacOS CI doesn't work otherwise
|
||||
set(CURL_CA_PATH "none" CACHE INTERNAL "")
|
||||
set(CURL_DISABLE_INSTALL ON CACHE INTERNAL "")
|
||||
if(WIN32)
|
||||
set(ENABLE_UNICODE ON CACHE BOOL "enable Unicode")
|
||||
set(ENABLE_UNICODE ON CACHE INTERNAL "")
|
||||
endif()
|
||||
set(CURL_USE_LIBSSH2 OFF CACHE BOOL "Use libSSH2")
|
||||
set(CURL_USE_LIBPSL OFF CACHE BOOL "Use libPSL")
|
||||
option(BUILD_TESTING "Build tests" OFF)
|
||||
option(BUILD_EXAMPLES "Build libcurl examples" OFF)
|
||||
set(CURL_USE_LIBSSH2 OFF CACHE INTERNAL "")
|
||||
set(CURL_USE_LIBPSL OFF CACHE INTERNAL "")
|
||||
set(BUILD_TESTING OFF CACHE INTERNAL "")
|
||||
set(BUILD_EXAMPLES OFF CACHE INTERNAL "")
|
||||
|
||||
add_subdirectory(curl EXCLUDE_FROM_ALL)
|
||||
|
||||
|
|
|
|||
2
3rdparty/libpng/libpng
vendored
2
3rdparty/libpng/libpng
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit d5515b5b8be3901aac04e5bd8bd5c89f287bcd33
|
||||
Subproject commit 3061454d980de7d53608f594194cfac722721d2a
|
||||
9
3rdparty/version_check.sh
vendored
9
3rdparty/version_check.sh
vendored
|
|
@ -119,7 +119,8 @@ echo -e "\n\nResult:\n"
|
|||
|
||||
# Find the max length of the paths (before '->')
|
||||
max_len=0
|
||||
while IFS='->' read -r left _; do
|
||||
while read -r line; do
|
||||
left="${line%%->*}"
|
||||
len=$(echo -n "$left" | wc -c)
|
||||
if (( len > max_len )); then
|
||||
max_len=$len
|
||||
|
|
@ -127,8 +128,10 @@ while IFS='->' read -r left _; do
|
|||
done < "$resultfile"
|
||||
|
||||
# Print with padding so '->' lines up
|
||||
while IFS='->' read -r left right; do
|
||||
right=$(echo "$right" | sed 's/^[[:space:]]*>*[[:space:]]*//')
|
||||
while read -r line; do
|
||||
left="${line%%->*}"
|
||||
right="${line#*->}"
|
||||
right=$(echo "$right" | sed 's/^[[:space:]]*//')
|
||||
printf "%-${max_len}s -> %s\n" "$left" "$right"
|
||||
done < "$resultfile"
|
||||
|
||||
|
|
|
|||
31
3rdparty/wolfssl/CMakeLists.txt
vendored
31
3rdparty/wolfssl/CMakeLists.txt
vendored
|
|
@ -4,23 +4,22 @@ if(USE_SYSTEM_WOLFSSL)
|
|||
add_library(wolfssl INTERFACE)
|
||||
target_link_libraries(wolfssl INTERFACE PkgConfig::WolfSSL)
|
||||
else()
|
||||
option(WOLFSSL_TLS13 "Enable wolfSSL TLS v1.3 (default: enabled)" OFF)
|
||||
set(WOLFSSL_SHA3 ON CACHE STRING "Enable wolfSSL SHA-3 support (default: enabled on x86_64/aarch64)")
|
||||
set(WOLFSSL_SHAKE256 ON CACHE STRING "Enable wolfSSL SHAKE256 support (default: enabled on x86_64/aarch64)")
|
||||
option(WOLFSSL_BASE64_ENCODE "Enable Base64 encoding (default: enabled on x86_64)" OFF)
|
||||
option(WOLFSSL_DES3 "Enable DES3 (default: disabled)" ON)
|
||||
option(WOLFSSL_PWDBASED "Enable PWDBASED (default: disabled)" ON)
|
||||
option(WOLFSSL_FAST_MATH "Enable fast math ops (default: disabled)" ON)
|
||||
option(WOLFSSL_EXAMPLES "Enable examples (default: enabled)" OFF)
|
||||
option(WOLFSSL_CRYPT_TESTS "Enable Crypt Bench/Test (default: enabled)" OFF)
|
||||
option(WOLFSSL_ASYNC_THREADS "Enable Asynchronous Threading (default: enabled)" OFF)
|
||||
option(WOLFSSL_BUILD_OUT_OF_TREE "Don't generate files in the source tree (default: yes)" ON)
|
||||
option(WOLFSSL_SNI "Enable SNI (default: disabled)" ON)
|
||||
option(WOLFSSL_OPENSSLEXTRA "Enable extra OpenSSL API, size+ (default: disabled)" ON)
|
||||
option(WOLFSSL_HARDEN "Enable Hardened build, Enables Timing Resistance and Blinding (default: enabled)" OFF)
|
||||
option(WOLFSSL_ALT_CERT_CHAINS "Enable support for Alternate certification chains (default: disabled)" ON)
|
||||
set(WOLFSSL_TLS13 OFF CACHE INTERNAL "")
|
||||
set(WOLFSSL_SHA3 ON CACHE INTERNAL "")
|
||||
set(WOLFSSL_SHAKE256 ON CACHE INTERNAL "")
|
||||
set(WOLFSSL_BASE64_ENCODE OFF CACHE INTERNAL "")
|
||||
set(WOLFSSL_DES3 ON CACHE INTERNAL "")
|
||||
set(WOLFSSL_PWDBASED ON CACHE INTERNAL "")
|
||||
set(WOLFSSL_FAST_MATH ON CACHE INTERNAL "")
|
||||
set(WOLFSSL_EXAMPLES OFF CACHE INTERNAL "")
|
||||
set(WOLFSSL_CRYPT_TESTS OFF CACHE INTERNAL "")
|
||||
set(WOLFSSL_ASYNC_THREADS OFF CACHE INTERNAL "")
|
||||
set(WOLFSSL_BUILD_OUT_OF_TREE ON CACHE INTERNAL "")
|
||||
set(WOLFSSL_SNI ON CACHE INTERNAL "")
|
||||
set(WOLFSSL_OPENSSLEXTRA ON CACHE INTERNAL "")
|
||||
set(WOLFSSL_ALT_CERT_CHAINS ON CACHE INTERNAL "")
|
||||
|
||||
add_subdirectory(wolfssl EXCLUDE_FROM_ALL)
|
||||
|
||||
target_compile_definitions(wolfssl PUBLIC WOLFSSL_DES_ECB HAVE_WRITE_DUP FP_MAX_BITS=8192 WOLFSSL_NO_OPTIONS_H)
|
||||
target_compile_definitions(wolfssl PUBLIC WOLFSSL_DES_ECB HAVE_WRITE_DUP FP_MAX_BITS=8192 WOLFSSL_USE_OPTIONS_H)
|
||||
endif()
|
||||
|
|
|
|||
2
3rdparty/wolfssl/wolfssl
vendored
2
3rdparty/wolfssl/wolfssl
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 922d04b3568c6428a9fb905ddee3ef5a68db3108
|
||||
Subproject commit 1d363f3adceba9d1478230ede476a37b0dcdef24
|
||||
|
|
@ -40,7 +40,7 @@ namespace cfg
|
|||
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");
|
||||
return false;
|
||||
|
|
@ -68,7 +68,7 @@ namespace cfg
|
|||
|
||||
// Incrementally load config entries from YAML::Node.
|
||||
// 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)
|
||||
|
|
@ -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)};
|
||||
}
|
||||
|
||||
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 (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;
|
||||
}
|
||||
|
||||
|
|
@ -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 (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;
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ bool try_to_int64(s64* out, std::string_view value, s64 min, s64 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;
|
||||
}
|
||||
|
||||
|
|
@ -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)};
|
||||
}
|
||||
|
||||
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 (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;
|
||||
}
|
||||
|
||||
|
|
@ -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 (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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -166,11 +166,11 @@ bool try_to_uint64(u64* out, std::string_view value, u64 min, u64 max)
|
|||
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 (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;
|
||||
}
|
||||
|
||||
|
|
@ -193,7 +193,7 @@ bool try_to_uint128(u128* out, std::string_view value)
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -207,7 +207,7 @@ bool try_to_uint128(u128* out, std::string_view value)
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -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)};
|
||||
}
|
||||
|
||||
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 (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;
|
||||
}
|
||||
|
||||
|
|
@ -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 (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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -251,7 +251,7 @@ bool try_to_float(f64* out, std::string_view value, f64 min, f64 max)
|
|||
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__
|
||||
if (out) *out = std::to_string(value);
|
||||
|
|
@ -266,13 +266,13 @@ bool try_to_string(std::string* out, const f64& value)
|
|||
}
|
||||
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;
|
||||
}
|
||||
#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;
|
||||
|
||||
|
|
@ -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 (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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -468,37 +468,50 @@ 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())
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (rhs.get_type())
|
||||
{
|
||||
case type::node:
|
||||
{
|
||||
if (data.IsScalar() || data.IsSequence())
|
||||
if (!data.IsMap())
|
||||
{
|
||||
return; // ???
|
||||
cfg_log.error("node node is not a map");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
|
||||
for (const auto& pair : data)
|
||||
{
|
||||
if (!pair.first.IsScalar()) continue;
|
||||
|
||||
// Find the key among existing nodes
|
||||
for (const auto& node : static_cast<node&>(rhs).get_nodes())
|
||||
const auto& nodes = static_cast<node&>(rhs).get_nodes();
|
||||
const auto it = std::find_if(nodes.cbegin(), nodes.cend(), [&pair](const auto& node) { return ensure(node)->get_name() == pair.first.Scalar(); });
|
||||
|
||||
if (it == nodes.cend())
|
||||
{
|
||||
if (node->get_name() == pair.first.Scalar())
|
||||
if (strict)
|
||||
{
|
||||
decode(pair.second, *node, dynamic);
|
||||
cfg_log.error("Unknown key found: '%s'", pair.first.Scalar());
|
||||
success = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!decode(pair.second, *ensure(*it), dynamic, strict) && strict)
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
return success;
|
||||
}
|
||||
case type::set:
|
||||
{
|
||||
|
|
@ -506,7 +519,10 @@ void cfg::decode(const YAML::Node& data, cfg::_base& rhs, bool dynamic)
|
|||
|
||||
if (YAML::convert<decltype(values)>::decode(data, values))
|
||||
{
|
||||
rhs.from_list(std::move(values));
|
||||
if (!rhs.from_list(std::move(values)) && strict)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -516,7 +532,8 @@ void cfg::decode(const YAML::Node& data, cfg::_base& rhs, bool dynamic)
|
|||
{
|
||||
if (!data.IsMap())
|
||||
{
|
||||
return;
|
||||
cfg_log.error("map node is not a map");
|
||||
return false;
|
||||
}
|
||||
|
||||
map_of_type<std::string> values;
|
||||
|
|
@ -533,22 +550,36 @@ void cfg::decode(const YAML::Node& data, cfg::_base& rhs, bool dynamic)
|
|||
}
|
||||
case type::log:
|
||||
{
|
||||
if (data.IsScalar() || data.IsSequence())
|
||||
if (!data.IsMap())
|
||||
{
|
||||
return; // ???
|
||||
cfg_log.error("log node is not a map");
|
||||
return false;
|
||||
}
|
||||
|
||||
map_of_type<logs::level> values;
|
||||
|
||||
for (const auto& pair : data)
|
||||
{
|
||||
if (!pair.first.IsScalar() || !pair.second.IsScalar()) continue;
|
||||
if (!pair.first.IsScalar() || !pair.second.IsScalar())
|
||||
{
|
||||
if (strict)
|
||||
{
|
||||
if (!pair.first.IsScalar())
|
||||
cfg_log.error("Key in map is not a scalar");
|
||||
else
|
||||
cfg_log.error("Value in map is not a scalar. key='%s'", pair.first.Scalar());
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
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));
|
||||
|
|
@ -558,20 +589,43 @@ void cfg::decode(const YAML::Node& data, cfg::_base& rhs, bool dynamic)
|
|||
{
|
||||
if (!data.IsMap())
|
||||
{
|
||||
return; // ???
|
||||
cfg_log.error("device node is not a map");
|
||||
return false;
|
||||
}
|
||||
|
||||
map_of_type<device_info> values;
|
||||
|
||||
for (const auto& pair : data)
|
||||
{
|
||||
if (!pair.first.IsScalar() || !pair.second.IsMap()) continue;
|
||||
if (!pair.first.IsScalar() || !pair.second.IsMap())
|
||||
{
|
||||
if (strict)
|
||||
{
|
||||
if (!pair.first.IsScalar())
|
||||
cfg_log.error("Key in device map is not a scalar");
|
||||
else
|
||||
cfg_log.error("Value in device map is not a map. key='%s'", pair.first.Scalar());
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
device_info info{};
|
||||
|
||||
for (const auto& key_value : pair.second)
|
||||
{
|
||||
if (!key_value.first.IsScalar() || !key_value.second.IsScalar()) continue;
|
||||
if (!key_value.first.IsScalar() || !key_value.second.IsScalar())
|
||||
{
|
||||
if (strict)
|
||||
{
|
||||
if (!key_value.first.IsScalar())
|
||||
cfg_log.error("Key in device info map is not a scalar");
|
||||
else
|
||||
cfg_log.error("Value in device map is not a scalar. key='%s'", key_value.first.Scalar());
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key_value.first.Scalar() == "Path")
|
||||
info.path = key_value.second.Scalar();
|
||||
|
|
@ -598,12 +652,17 @@ void cfg::decode(const YAML::Node& data, cfg::_base& rhs, bool dynamic)
|
|||
|
||||
if (YAML::convert<std::string>::decode(data, value))
|
||||
{
|
||||
rhs.from_string(value, dynamic);
|
||||
if (!rhs.from_string(value, dynamic) && strict)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
break; // ???
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string cfg::node::to_string() const
|
||||
|
|
@ -620,8 +679,7 @@ bool cfg::node::from_string(std::string_view value, bool dynamic)
|
|||
|
||||
if (error.empty())
|
||||
{
|
||||
cfg::decode(result, *this, dynamic);
|
||||
return true;
|
||||
return cfg::decode(result, *this, dynamic, false);
|
||||
}
|
||||
|
||||
cfg_log.error("Failed to load node: %s", error);
|
||||
|
|
@ -644,6 +702,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)
|
||||
{
|
||||
if (auto it = m_map.find(key); it != m_map.end())
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ namespace cfg
|
|||
std::vector<std::string> make_float_range(f64 min, f64 max);
|
||||
|
||||
// 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
|
||||
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)
|
||||
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)
|
||||
virtual std::vector<std::string> to_list() const
|
||||
|
|
@ -161,6 +161,9 @@ namespace cfg
|
|||
|
||||
// Restore default members
|
||||
void restore_defaults() override;
|
||||
|
||||
// Try to convert from string and validate
|
||||
bool validate(std::string_view value);
|
||||
};
|
||||
|
||||
class _bool final : public _base
|
||||
|
|
@ -301,7 +304,7 @@ namespace cfg
|
|||
{
|
||||
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
|
||||
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
|
||||
{
|
||||
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);
|
||||
return true;
|
||||
|
|
@ -451,7 +454,7 @@ namespace cfg
|
|||
std::string to_string() const override
|
||||
{
|
||||
std::string result;
|
||||
if (try_to_string(&result, m_value))
|
||||
if (try_to_string(&result, m_value, m_name))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
|
@ -462,7 +465,7 @@ namespace cfg
|
|||
std::string def_to_string() const override
|
||||
{
|
||||
std::string result;
|
||||
if (try_to_string(&result, def))
|
||||
if (try_to_string(&result, def, m_name))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
|
@ -473,7 +476,7 @@ namespace cfg
|
|||
bool from_string(std::string_view value, bool /*dynamic*/ = false) override
|
||||
{
|
||||
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);
|
||||
return true;
|
||||
|
|
@ -560,7 +563,7 @@ namespace cfg
|
|||
bool from_string(std::string_view value, bool /*dynamic*/ = false) override
|
||||
{
|
||||
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);
|
||||
return true;
|
||||
|
|
@ -646,7 +649,7 @@ namespace cfg
|
|||
bool from_string(std::string_view value, bool /*dynamic*/ = false) override
|
||||
{
|
||||
u128 result;
|
||||
if (try_to_uint128(&result, value))
|
||||
if (try_to_uint128(&result, value, m_name))
|
||||
{
|
||||
m_value = result;
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -23,19 +23,19 @@ inline void strcpy_trunc(D&& dst, const T& src)
|
|||
}
|
||||
|
||||
// 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
|
||||
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
|
||||
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
|
||||
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
|
||||
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.)
|
||||
std::string get_file_extension(const std::string& file_path);
|
||||
|
|
|
|||
|
|
@ -2883,6 +2883,13 @@ void thread_base::exec()
|
|||
}
|
||||
}
|
||||
|
||||
void thread_ctrl::set_name(std::string name)
|
||||
{
|
||||
ensure(g_tls_this_thread);
|
||||
g_tls_this_thread->m_tname.store(make_single<std::string>(name));
|
||||
g_tls_this_thread->set_name(std::move(name));
|
||||
}
|
||||
|
||||
[[noreturn]] void thread_ctrl::emergency_exit(std::string_view reason)
|
||||
{
|
||||
// Print stacktrace
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ public:
|
|||
const native_entry entry_point;
|
||||
|
||||
// Set name for debugger
|
||||
static void set_name(std::string);
|
||||
static void set_name(std::string name);
|
||||
|
||||
private:
|
||||
// Thread handle (platform-specific)
|
||||
|
|
@ -232,11 +232,7 @@ public:
|
|||
}
|
||||
|
||||
// Set current thread name (not recommended)
|
||||
static void set_name(std::string name)
|
||||
{
|
||||
g_tls_this_thread->m_tname.store(make_single<std::string>(name));
|
||||
g_tls_this_thread->set_name(std::move(name));
|
||||
}
|
||||
static void set_name(std::string name);
|
||||
|
||||
// Set thread name (not recommended)
|
||||
template <typename T>
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@ if(BUILD_RPCS3_TESTS)
|
|||
tests/test_tuple.cpp
|
||||
tests/test_simple_array.cpp
|
||||
tests/test_address_range.cpp
|
||||
tests/test_sys_fs.cpp
|
||||
tests/test_rsx_cfg.cpp
|
||||
tests/test_rsx_fp_asm.cpp
|
||||
tests/test_dmux_pamf.cpp
|
||||
|
|
|
|||
|
|
@ -520,10 +520,11 @@ error_code cellHddGameCheck(ppu_thread& ppu, u32 version, vm::cptr<char> dirName
|
|||
return CELL_GAMEDATA_ERROR_PARAM;
|
||||
}
|
||||
|
||||
if (!fs::create_path(vfs::get(usrdir)))
|
||||
{
|
||||
return {CELL_GAME_ERROR_ACCESS_ERROR, usrdir};
|
||||
}
|
||||
// Nuked until correctly reversed engineered
|
||||
//if (!fs::create_path(vfs::get(usrdir)))
|
||||
//{
|
||||
// return {CELL_GAME_ERROR_ACCESS_ERROR, usrdir};
|
||||
//}
|
||||
}
|
||||
|
||||
// Nuked until correctly reversed engineered
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
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 u8* in_data[4] = { frame->data[0], frame->data[1], frame->data[2], alpha_plane.get() };
|
||||
const int in_line[4] = { frame->linesize[0], frame->linesize[1], frame->linesize[2], w * 1 };
|
||||
u8* out_data[4] = { outBuff.get_ptr() };
|
||||
int out_line[4] = { w * 4 }; // RGBA32 or ARGB32
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
#include "Emu/system_utils.hpp"
|
||||
#include "Emu/Cell/lv2/sys_process.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <span>
|
||||
#include <shared_mutex>
|
||||
|
||||
|
|
@ -93,15 +92,22 @@ void fmt_class_string<lv2_dir>::format(std::string& out, u64 arg)
|
|||
bool has_fs_write_rights(std::string_view vpath)
|
||||
{
|
||||
// VSH has access to everything
|
||||
if (g_ps3_process_info.has_root_perm())
|
||||
return true;
|
||||
const bool has_root_perm = g_ps3_process_info.has_root_perm();
|
||||
|
||||
const auto norm_vpath = lv2_fs_object::get_normalized_path(vpath);
|
||||
const auto parent_dir = fs::get_parent_dir_view(norm_vpath);
|
||||
const auto parent_dir = fs::get_parent_dir_view(vpath);
|
||||
const auto [dev_root, trail] = lv2_fs_object::get_path_root_and_trail(parent_dir);
|
||||
|
||||
// This is not exhaustive, PS3 has a unix filesystem with rights for each directory and files
|
||||
// This is mostly meant to protect against games doing insane things(ie NPUB30003 => NPUB30008)
|
||||
if (parent_dir == "/dev_hdd0" || parent_dir == "/dev_hdd0/game")
|
||||
// This is mostly meant to protect against games doing insane things (ie NPUB30003 => NPUB30008)
|
||||
if (dev_root == "dev_hdd0"sv && (trail.empty() || trail == "game"sv))
|
||||
return has_root_perm;
|
||||
|
||||
// This is read-only for games
|
||||
if (dev_root.starts_with("dev_flash"sv))
|
||||
return has_root_perm;
|
||||
|
||||
// Technically should not reach here, but handle it anyways
|
||||
if (dev_root == "dev_bdvd"sv || dev_root == "dev_ps2disc"sv || dev_root.empty())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
|
@ -205,27 +211,29 @@ bool lv2_fs_mount_info_map::remove(std::string_view path)
|
|||
|
||||
const lv2_fs_mount_info& lv2_fs_mount_info_map::lookup(std::string_view path, bool no_cell_fs_path, std::string* mount_path) const
|
||||
{
|
||||
if (path.starts_with("/"sv))
|
||||
const auto [dev_root, trail] = lv2_fs_object::get_path_root_and_trail(path);
|
||||
|
||||
if (dev_root.empty())
|
||||
{
|
||||
if (trail.empty())
|
||||
{
|
||||
return map.find("/")->second;
|
||||
}
|
||||
|
||||
return g_mi_sys_not_found;
|
||||
}
|
||||
|
||||
if (const auto iterator = map.find("/" + std::string{dev_root}); iterator != map.end())
|
||||
{
|
||||
constexpr std::string_view cell_fs_path = "CELL_FS_PATH:"sv;
|
||||
const std::string normalized_path = lv2_fs_object::get_normalized_path(path);
|
||||
std::string_view parent_dir;
|
||||
u32 parent_level = 0;
|
||||
|
||||
do
|
||||
{
|
||||
parent_dir = fs::get_parent_dir_view(normalized_path, parent_level++);
|
||||
if (const auto iterator = map.find(parent_dir); iterator != map.end())
|
||||
{
|
||||
if (iterator->second == &g_mp_sys_dev_root && parent_level > 1)
|
||||
break;
|
||||
if (no_cell_fs_path && iterator->second.device.starts_with(cell_fs_path))
|
||||
return lookup(iterator->second.device.substr(cell_fs_path.size()), no_cell_fs_path, mount_path); // Recursively look up the parent mount info
|
||||
if (mount_path)
|
||||
*mount_path = iterator->first;
|
||||
return iterator->second;
|
||||
}
|
||||
} while (parent_dir.length() > 1); // Exit the loop when parent_dir == "/" or empty
|
||||
if (no_cell_fs_path && iterator->second.device.starts_with(cell_fs_path))
|
||||
return lookup(iterator->second.device.substr(cell_fs_path.size()), no_cell_fs_path, mount_path); // Recursively look up the parent mount info
|
||||
|
||||
if (mount_path)
|
||||
*mount_path = iterator->first;
|
||||
|
||||
return iterator->second;
|
||||
}
|
||||
|
||||
return g_mi_sys_not_found;
|
||||
|
|
@ -287,36 +295,89 @@ bool lv2_fs_mount_info_map::vfs_unmount(std::string_view vpath, bool remove_from
|
|||
return result;
|
||||
}
|
||||
|
||||
std::string lv2_fs_object::get_normalized_path(std::string_view path)
|
||||
std::pair<std::string_view, std::string> lv2_fs_object::get_path_root_and_trail(std::string_view filename)
|
||||
{
|
||||
std::string normalized_path = std::filesystem::path(path).lexically_normal().string();
|
||||
|
||||
#ifdef _WIN32
|
||||
std::replace(normalized_path.begin(), normalized_path.end(), '\\', '/');
|
||||
#endif
|
||||
|
||||
if (normalized_path.ends_with('/'))
|
||||
normalized_path.pop_back();
|
||||
|
||||
return normalized_path.empty() ? "/" : normalized_path;
|
||||
}
|
||||
|
||||
std::string lv2_fs_object::get_device_root(std::string_view filename)
|
||||
{
|
||||
std::string path = get_normalized_path(filename); // Prevent getting fooled by ".." trick such as "/dev_usb000/../dev_flash"
|
||||
|
||||
if (const auto first = path.find_first_not_of("/"sv); first != umax)
|
||||
if (filename.empty())
|
||||
{
|
||||
if (const auto pos = path.substr(first).find_first_of("/"sv); pos != umax)
|
||||
path = path.substr(0, first + pos);
|
||||
path = path.substr(std::max<std::make_signed_t<usz>>(0, first - 1)); // Remove duplicate leading '/' while keeping only one
|
||||
}
|
||||
else
|
||||
{
|
||||
path = path.substr(0, 1);
|
||||
// Should CELL_ENOENT later - root cannot have a trail
|
||||
return {""sv, "ENOENT"};
|
||||
}
|
||||
|
||||
return path;
|
||||
std::string_view root;
|
||||
std::string trail;
|
||||
|
||||
usz level = 0;
|
||||
usz pos = 0;
|
||||
|
||||
while (pos != umax)
|
||||
{
|
||||
const usz ndl_pos = filename.find_first_not_of("/", pos);
|
||||
|
||||
if (ndl_pos == pos)
|
||||
{
|
||||
// Should CELL_ENOENT later - root cannot have a trail
|
||||
return {""sv, "ENOENT"};
|
||||
}
|
||||
|
||||
if (ndl_pos == umax)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const usz dl_pos = ndl_pos == umax ? usz{umax} : filename.find_first_of("/", ndl_pos);
|
||||
std::string_view component = filename.substr(ndl_pos, dl_pos - ndl_pos);
|
||||
|
||||
if (component == "."sv)
|
||||
{
|
||||
// No change
|
||||
// level += 0;
|
||||
pos = dl_pos;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (component == ".."sv)
|
||||
{
|
||||
if (level > 1)
|
||||
{
|
||||
ensure(!trail.empty());
|
||||
trail.resize(trail.find_last_of("/") + 1);
|
||||
trail.resize(trail.find_last_not_of("/") + 1);
|
||||
}
|
||||
else if (level == 1)
|
||||
{
|
||||
// Reset root
|
||||
root = {};
|
||||
}
|
||||
else//if (level == 0)
|
||||
{
|
||||
// Should CELL_ENOENT later - root cannot have a trail
|
||||
return {""sv, "ENOENT"};
|
||||
}
|
||||
|
||||
ensure(level)--;
|
||||
pos = dl_pos;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (level == 0)
|
||||
{
|
||||
root = component;
|
||||
}
|
||||
else if (trail.empty())
|
||||
{
|
||||
trail = std::string{component};
|
||||
}
|
||||
else
|
||||
{
|
||||
trail += "/";
|
||||
trail.append(component);
|
||||
}
|
||||
|
||||
level++;
|
||||
pos = dl_pos;
|
||||
}
|
||||
|
||||
return { root, std::move(trail) };
|
||||
}
|
||||
|
||||
lv2_fs_mount_point* lv2_fs_object::get_mp(std::string_view filename, std::string* vfs_path)
|
||||
|
|
@ -328,7 +389,7 @@ lv2_fs_mount_point* lv2_fs_object::get_mp(std::string_view filename, std::string
|
|||
filename.remove_prefix(cell_fs_path.size());
|
||||
|
||||
const bool is_path = filename.starts_with("/"sv);
|
||||
std::string mp_name = is_path ? get_device_root(filename) : std::string(filename);
|
||||
std::string mp_name = is_path ? std::string{get_path_root_and_trail(filename).first} : std::string(filename);
|
||||
|
||||
const auto check_mp = [&]()
|
||||
{
|
||||
|
|
@ -1405,6 +1466,10 @@ error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr<char> path, vm::ptr<u32> fd)
|
|||
|
||||
break;
|
||||
}
|
||||
case fs::error::notdir:
|
||||
{
|
||||
return { CELL_ENOTDIR, path };
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (has_non_directory_components(local_path))
|
||||
|
|
@ -3398,7 +3463,7 @@ error_code sys_fs_mount(ppu_thread& ppu, vm::cptr<char> dev_name, vm::cptr<char>
|
|||
return {path_error, path_sv};
|
||||
}
|
||||
|
||||
const std::string vpath = lv2_fs_object::get_normalized_path(path_sv);
|
||||
const auto [root_name, trail] = lv2_fs_object::get_path_root_and_trail(path_sv);
|
||||
|
||||
std::string vfs_path;
|
||||
const auto mp = lv2_fs_object::get_mp(device_name, &vfs_path);
|
||||
|
|
@ -3416,8 +3481,8 @@ error_code sys_fs_mount(ppu_thread& ppu, vm::cptr<char> dev_name, vm::cptr<char>
|
|||
if (vfs_path.empty())
|
||||
return {CELL_ENOTSUP, device_name};
|
||||
|
||||
if (vpath.find_first_not_of('/') == umax || !vfs::get(vpath).empty())
|
||||
return {CELL_EEXIST, vpath};
|
||||
if (root_name.empty() || !vfs::get(path_sv).empty())
|
||||
return {CELL_EEXIST, path_sv};
|
||||
|
||||
if (mp == &g_mp_sys_dev_hdd1)
|
||||
{
|
||||
|
|
@ -3452,7 +3517,7 @@ error_code sys_fs_mount(ppu_thread& ppu, vm::cptr<char> dev_name, vm::cptr<char>
|
|||
}
|
||||
}
|
||||
|
||||
if (!vfs::mount(vpath, vfs_path, !is_simplefs))
|
||||
if (!vfs::mount("/" + std::string{root_name}, vfs_path, !is_simplefs))
|
||||
{
|
||||
if (is_simplefs)
|
||||
{
|
||||
|
|
@ -3469,7 +3534,7 @@ error_code sys_fs_mount(ppu_thread& ppu, vm::cptr<char> dev_name, vm::cptr<char>
|
|||
return CELL_EIO;
|
||||
}
|
||||
|
||||
g_fxo->get<lv2_fs_mount_info_map>().add(vpath, mp, device_name, filesystem, prot);
|
||||
g_fxo->get<lv2_fs_mount_info_map>().add("/" + std::string{root_name}, mp, device_name, filesystem, prot);
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "Emu/Cell/ErrorCodes.h"
|
||||
#include "Utilities/File.h"
|
||||
#include "Utilities/StrUtil.h"
|
||||
#include "Utilities/mutex.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
|
@ -245,11 +246,15 @@ public:
|
|||
|
||||
lv2_fs_object& operator=(const lv2_fs_object&) = delete;
|
||||
|
||||
// Normalize a virtual path
|
||||
static std::string get_normalized_path(std::string_view path);
|
||||
// Get the device's root path (e.g. "/dev_hdd0") from a given path
|
||||
// Cut the trail and return it in seccond argument
|
||||
static std::pair<std::string_view, std::string> get_path_root_and_trail(std::string_view path);
|
||||
|
||||
// Get the device's root path (e.g. "/dev_hdd0") from a given path
|
||||
static std::string get_device_root(std::string_view filename);
|
||||
static std::string get_device_root(std::string_view filename)
|
||||
{
|
||||
return std::string{get_path_root_and_trail(filename).first};
|
||||
}
|
||||
|
||||
// Filename can be either a path starting with '/' or a CELL_FS device name
|
||||
// This should be used only when handling devices that are not mounted
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
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.envp = std::move(envp);
|
||||
|
|
@ -489,7 +489,7 @@ void lv2_exitspawn(ppu_thread& ppu, std::vector<std::string>& argv, std::vector<
|
|||
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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::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::database_config_missing: return "Could not find config in database";
|
||||
}
|
||||
return unknown;
|
||||
});
|
||||
|
|
@ -173,7 +174,7 @@ void fmt_class_string<cfg_mode>::format(std::string& out, u64 arg)
|
|||
{
|
||||
case cfg_mode::custom: return "custom config";
|
||||
case cfg_mode::custom_selection: return "custom config selection";
|
||||
case cfg_mode::global: return "global config";
|
||||
case cfg_mode::database_config: return "database config";
|
||||
case cfg_mode::config_override: return "config override";
|
||||
case cfg_mode::continuous: return "continuous config";
|
||||
case cfg_mode::default_config: return "default config";
|
||||
|
|
@ -932,14 +933,14 @@ game_boot_result Emulator::GetElfPathFromDir(std::string& elf_path, const std::s
|
|||
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::optional<std::string>& db_config)
|
||||
{
|
||||
if (m_restrict_emu_state_change)
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
|
@ -949,7 +950,7 @@ game_boot_result Emulator::BootGame(const std::string& path, const std::string&
|
|||
|
||||
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)
|
||||
{
|
||||
|
|
@ -964,7 +965,7 @@ game_boot_result Emulator::BootGame(const std::string& path, const std::string&
|
|||
// Execute after Kill() is done
|
||||
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)
|
||||
{
|
||||
|
|
@ -981,6 +982,7 @@ game_boot_result Emulator::BootGame(const std::string& path, const std::string&
|
|||
|
||||
m_config_mode = config_mode;
|
||||
m_config_path = config_path;
|
||||
m_db_config = db_config;
|
||||
|
||||
// Handle files and special paths inside Load unmodified
|
||||
if (direct || !fs::is_dir(path))
|
||||
|
|
@ -1563,6 +1565,24 @@ 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);
|
||||
|
||||
{
|
||||
if (m_config_mode == cfg_mode::database_config || m_config_mode == cfg_mode::custom)
|
||||
{
|
||||
if (!m_db_config)
|
||||
{
|
||||
// Get database config if possible. This only happens if the database config hasn't been set by the UI (e.g. if booted with no-gui).
|
||||
// We only know the title_id for sure at this point, so it doesn't make sense to retrieve it earlier.
|
||||
m_db_config = Emu.GetCallbacks().get_database_config(m_title_id);
|
||||
}
|
||||
|
||||
// We add the database configuration if it is set, unless we are using a mode that specifically selects a different configuration.
|
||||
m_add_database_config = m_db_config && !m_db_config->empty();
|
||||
}
|
||||
else if (m_config_mode != cfg_mode::continuous)
|
||||
{
|
||||
// Reset flag unless in continuous mode
|
||||
m_add_database_config = false;
|
||||
}
|
||||
|
||||
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 })
|
||||
|
|
@ -1605,6 +1625,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||
{
|
||||
g_cfg.name = config_path;
|
||||
m_config_path = config_path;
|
||||
m_add_database_config = false; // A custom config exists. Do not add the database config.
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1613,6 +1634,21 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||
}
|
||||
}
|
||||
|
||||
if (m_add_database_config && m_db_config && !m_db_config->empty())
|
||||
{
|
||||
// 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
|
||||
fixup_settings(&_psf);
|
||||
|
||||
|
|
@ -3342,6 +3378,25 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
|
|||
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 = std::nullopt;
|
||||
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 (state == system_state::stopping || state == system_state::stopped)
|
||||
|
|
@ -3360,21 +3415,8 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
|
|||
}
|
||||
|
||||
// Ensure clean state
|
||||
m_ar.reset();
|
||||
argv.clear();
|
||||
envp.clear();
|
||||
data.clear();
|
||||
disc.clear();
|
||||
klic.clear();
|
||||
hdd1.clear();
|
||||
init_mem_containers = nullptr;
|
||||
reset_emu_state();
|
||||
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
|
||||
rpcs3::utils::configure_logs(true);
|
||||
|
|
@ -3422,7 +3464,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
|
||||
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;
|
||||
|
||||
|
|
@ -3921,7 +3963,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
|
|||
set_progress_message("Resetting Objects");
|
||||
|
||||
// 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();
|
||||
|
||||
|
|
@ -3967,20 +4009,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
|
|||
m_stop_ctr.notify_all();
|
||||
|
||||
// Boot arg cleanup (preserved in the case restarting)
|
||||
argv.clear();
|
||||
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 = {};
|
||||
reset_emu_state();
|
||||
|
||||
if (!m_continuous_mode)
|
||||
{
|
||||
|
|
@ -4004,7 +4033,9 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
|
|||
|
||||
if (allow_autoexit)
|
||||
{
|
||||
Quit(g_cfg.misc.autoexit.get());
|
||||
const bool autoexit = g_cfg.misc.autoexit.get();
|
||||
sys_log.notice("Quit with main_window::closeEvent. (autoexit=%d)", autoexit);
|
||||
Quit(autoexit);
|
||||
}
|
||||
|
||||
if (after_kill_callback)
|
||||
|
|
@ -4055,14 +4086,14 @@ game_boot_result Emulator::Restart(bool graceful, bool reset_path)
|
|||
|
||||
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)
|
||||
GracefulShutdown(false, false);
|
||||
else
|
||||
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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ enum class game_boot_result : u32
|
|||
still_running,
|
||||
already_added,
|
||||
currently_restricted,
|
||||
database_config_missing,
|
||||
};
|
||||
|
||||
constexpr bool is_error(game_boot_result res)
|
||||
|
|
@ -114,6 +115,7 @@ struct EmuCallbacks
|
|||
std::function<void()> check_microphone_permissions;
|
||||
std::function<std::unique_ptr<class video_source>()> make_video_source;
|
||||
std::function<void(bool)> enable_gamemode;
|
||||
std::function<std::string(const std::string&)> get_database_config;
|
||||
};
|
||||
|
||||
namespace utils
|
||||
|
|
@ -145,6 +147,7 @@ class Emulator final
|
|||
|
||||
cfg_mode m_config_mode = cfg_mode::custom;
|
||||
std::string m_config_path;
|
||||
std::optional<std::string> m_db_config; // std::nullopt means it has not been retrieved yet
|
||||
std::string m_path;
|
||||
std::string m_path_old;
|
||||
std::string m_path_original;
|
||||
|
|
@ -169,6 +172,7 @@ class Emulator final
|
|||
|
||||
bool m_continuous_mode = false;
|
||||
bool m_has_gui = true;
|
||||
bool m_add_database_config = false;
|
||||
|
||||
bool m_state_inspection_savestate = false;
|
||||
|
||||
|
|
@ -368,6 +372,12 @@ public:
|
|||
return m_config_path;
|
||||
}
|
||||
|
||||
const std::string& GetUsedDatabaseConfig() const
|
||||
{
|
||||
static std::string empty_db_config;
|
||||
return m_db_config ? *m_db_config : empty_db_config;
|
||||
}
|
||||
|
||||
bool IsChildProcess() const
|
||||
{
|
||||
return m_config_mode == cfg_mode::continuous;
|
||||
|
|
@ -417,7 +427,7 @@ public:
|
|||
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::optional<std::string>& db_config = std::nullopt);
|
||||
bool BootRsxCapture(const std::string& path);
|
||||
|
||||
void SetForceBoot(bool force_boot);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ enum class cfg_mode
|
|||
{
|
||||
custom, // Prefer regular custom config. Fall back to global config.
|
||||
custom_selection, // Use user-selected custom config. Fall back to global config.
|
||||
global, // Use global config.
|
||||
database_config, // Use database config. 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.
|
||||
default_config // Use the default values of the config entries.
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ struct cfg_root : cfg::node
|
|||
cfg::_bool paint_move_spheres{this, "Paint move spheres", false, true};
|
||||
cfg::_bool allow_move_hue_set_by_game{this, "Allow move hue set by game", false, true};
|
||||
cfg::_bool lock_overlay_input_to_player_one{this, "Lock overlay input to player one", false, true};
|
||||
cfg::string midi_devices{this, "Emulated Midi devices", "ßßß@@@ßßß@@@ßßß@@@"};
|
||||
cfg::string midi_devices{this, "Emulated Midi devices", "Keyboardßßß@@@Keyboardßßß@@@Keyboardßßß@@@"};
|
||||
cfg::_bool load_sdl_mappings{ this, "Load SDL GameController Mappings", true };
|
||||
cfg::_bool pad_debug_overlay{ this, "IO Debug overlay", false, true };
|
||||
cfg::_bool mouse_debug_overlay{ this, "Mouse Debug overlay", false, true };
|
||||
|
|
|
|||
|
|
@ -788,7 +788,7 @@ std::vector<pad_list_entry> keyboard_pad_handler::list_devices()
|
|||
|
||||
std::string keyboard_pad_handler::GetMouseName(const QMouseEvent* event)
|
||||
{
|
||||
return GetMouseName(event->button());
|
||||
return GetMouseName(static_cast<u32>(mouse::button) + static_cast<u32>(event->button()));
|
||||
}
|
||||
|
||||
std::string keyboard_pad_handler::GetMouseName(u32 button)
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ bool iso_file_decryption::init(const std::string& path)
|
|||
const u32 region_count = char_arr_BE_to_uint(sec0_sec1.data());
|
||||
|
||||
// Ensure the region count is a proper value
|
||||
if (region_count < 1 || region_count > 31) // It's non-PS3ISO
|
||||
if (region_count < 1 || region_count > 127) // It's non-PS3ISO
|
||||
{
|
||||
iso_log.error("init: Failed to read region information: '%s' (region_count=%d)", path, region_count);
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ void headless_application::InitializeCallbacks()
|
|||
on_exit();
|
||||
}
|
||||
|
||||
sys_log.notice("Quitting headless application");
|
||||
sys_log.notice("Quitting headless application (force_quit=%d)", force_quit);
|
||||
quit();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
#include "main_application.h"
|
||||
#include "display_sleep_control.h"
|
||||
#include "gamemode_control.h"
|
||||
#include "rpcs3qt/gui_settings.h"
|
||||
#include "rpcs3qt/config_database.h"
|
||||
|
||||
#include "util/types.hpp"
|
||||
#include "util/logs.hpp"
|
||||
|
|
@ -409,5 +411,28 @@ EmuCallbacks main_application::CreateCallbacks()
|
|||
return path + suffix;
|
||||
};
|
||||
|
||||
callbacks.get_database_config = [](const std::string& title_id)
|
||||
{
|
||||
if (title_id.empty())
|
||||
return std::string();
|
||||
|
||||
sys_log.notice("Trying to retrieve database config for: '%s'", title_id);
|
||||
|
||||
const auto settings = std::make_shared<gui_settings>();
|
||||
config_database config_db(settings, nullptr);
|
||||
config_db.request_config_database(false);
|
||||
|
||||
if (!config_db.has_config(title_id))
|
||||
return std::string();
|
||||
|
||||
if (const auto config = config_db.get_config(title_id))
|
||||
{
|
||||
sys_log.notice("Found database config for: '%s'", title_id);
|
||||
return config.value();
|
||||
}
|
||||
|
||||
return std::string();
|
||||
};
|
||||
|
||||
return callbacks;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@
|
|||
<string>Licensed under GPLv2</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.games</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>14.4</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
|
|
|
|||
|
|
@ -296,6 +296,9 @@
|
|||
<ClCompile Include="QTGeneratedFiles\Debug\moc_game_list_context_menu.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</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">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
|
|
@ -602,6 +605,9 @@
|
|||
<ClCompile Include="QTGeneratedFiles\Release\moc_game_list_context_menu.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</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">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
|
|
@ -912,6 +918,7 @@
|
|||
<ClCompile Include="rpcs3qt\video_label.cpp" />
|
||||
<ClCompile Include="rpcs3qt\_discord_utils.cpp" />
|
||||
<ClCompile Include="rpcs3qt\find_dialog.cpp" />
|
||||
<ClCompile Include="rpcs3qt\config_database.cpp" />
|
||||
<ClCompile Include="rpcs3qt\game_compatibility.cpp" />
|
||||
<ClCompile Include="rpcs3qt\game_list_grid.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>
|
||||
</CustomBuild>
|
||||
<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">
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing game_compatibility.h...</Message>
|
||||
|
|
|
|||
|
|
@ -519,6 +519,15 @@
|
|||
<ClCompile Include="rpcs3qt\progress_dialog.cpp">
|
||||
<Filter>Gui\misc dialogs</Filter>
|
||||
</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">
|
||||
<Filter>Generated Files\Debug</Filter>
|
||||
</ClCompile>
|
||||
|
|
@ -1651,6 +1660,9 @@
|
|||
<CustomBuild Include="rpcs3qt\save_manager_dialog.h">
|
||||
<Filter>Gui\save</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\config_database.h">
|
||||
<Filter>Gui\game list</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\game_compatibility.h">
|
||||
<Filter>Gui\game list</Filter>
|
||||
</CustomBuild>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ add_library(rpcs3_ui STATIC
|
|||
clans_settings_dialog.cpp
|
||||
config_adapter.cpp
|
||||
config_checker.cpp
|
||||
config_database.cpp
|
||||
curl_handle.cpp
|
||||
custom_dialog.cpp
|
||||
custom_table_widget_item.cpp
|
||||
|
|
|
|||
|
|
@ -1,76 +1,127 @@
|
|||
#include "stdafx.h"
|
||||
#include "config_checker.h"
|
||||
#include "midi_creator.h"
|
||||
#include "microphone_creator.h"
|
||||
#include "Emu/system_config.h"
|
||||
#include "Emu/system_utils.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QMessageBox>
|
||||
#include <QTextEdit>
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
|
||||
LOG_CHANNEL(gui_log, "GUI");
|
||||
|
||||
config_checker::config_checker(QWidget* parent, const QString& content, bool is_log) : QDialog(parent)
|
||||
config_checker::config_checker(QWidget* parent, const QString& content_or_serial, checker_mode mode, const std::string& db_config)
|
||||
: QDialog(parent)
|
||||
, m_checker_mode(mode)
|
||||
, m_content_or_serial(content_or_serial)
|
||||
, m_db_config(db_config)
|
||||
{
|
||||
setObjectName("config_checker");
|
||||
setWindowTitle(tr("Config Checker"));
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
QLabel* label = new QLabel(this);
|
||||
layout->addWidget(label);
|
||||
QComboBox* combo = nullptr;
|
||||
|
||||
QString result;
|
||||
|
||||
if (check_config(content, result, is_log))
|
||||
if (mode == checker_mode::gamelist)
|
||||
{
|
||||
setWindowTitle(tr("Interesting!"));
|
||||
m_serial = content_or_serial.toStdString();
|
||||
|
||||
if (result.isEmpty())
|
||||
combo = new QComboBox(this);
|
||||
|
||||
std::string custom_config_path;
|
||||
if (std::string config_path = rpcs3::utils::get_custom_config_path(m_serial); fs::is_file(config_path))
|
||||
{
|
||||
label->setText(tr("Found config.\nIt seems to match the default config."));
|
||||
custom_config_path = std::move(config_path);
|
||||
combo->addItem(tr("Custom Configuration"), static_cast<int>(cfg_mode::custom));
|
||||
}
|
||||
else
|
||||
|
||||
combo->addItem(tr("Database + Global Configuration"), static_cast<int>(cfg_mode::database_config));
|
||||
combo->setCurrentIndex(combo->findData(static_cast<int>(custom_config_path.empty() ? cfg_mode::database_config : cfg_mode::custom)));
|
||||
|
||||
connect(combo, &QComboBox::currentIndexChanged, this, [this, combo]()
|
||||
{
|
||||
label->setText(tr("Found config.\nSome settings seem to deviate from the default config:"));
|
||||
check_config(static_cast<cfg_mode>(combo->currentData().toInt()));
|
||||
});
|
||||
|
||||
QTextEdit* text_box = new QTextEdit();
|
||||
text_box->setReadOnly(true);
|
||||
text_box->setHtml(result);
|
||||
layout->addWidget(text_box);
|
||||
layout->addWidget(combo);
|
||||
}
|
||||
|
||||
resize(400, 600);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setWindowTitle(tr("Ooops!"));
|
||||
label->setText(result);
|
||||
}
|
||||
m_label = new QLabel(this);
|
||||
layout->addWidget(m_label);
|
||||
|
||||
m_text_box = new QTextEdit();
|
||||
m_text_box->setReadOnly(true);
|
||||
layout->addWidget(m_text_box);
|
||||
|
||||
QDialogButtonBox* box = new QDialogButtonBox(QDialogButtonBox::Close);
|
||||
connect(box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
layout->addWidget(box);
|
||||
|
||||
setLayout(layout);
|
||||
resize(400, 600);
|
||||
|
||||
check_config(combo ? static_cast<cfg_mode>(combo->currentData().toInt()) : cfg_mode::database_config);
|
||||
}
|
||||
|
||||
bool config_checker::check_config(QString content, QString& result, bool is_log)
|
||||
void config_checker::check_config(cfg_mode mode)
|
||||
{
|
||||
cfg_root config{};
|
||||
QString result;
|
||||
|
||||
if (is_log)
|
||||
if (check_config(mode, m_content_or_serial, result))
|
||||
{
|
||||
if (m_checker_mode == checker_mode::gamelist)
|
||||
{
|
||||
if (result.isEmpty())
|
||||
{
|
||||
m_label->setText(tr("The configuration seems to match the default config."));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_label->setText(tr("Config database settings are marked with an * in front of the name.\nSome settings seem to deviate from the default config:"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (result.isEmpty())
|
||||
{
|
||||
m_label->setText(tr("Found config.\nIt seems to match the default config."));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_label->setText(tr("Found config.\nSome settings seem to deviate from the default config:"));
|
||||
}
|
||||
}
|
||||
|
||||
m_text_box->setVisible(!result.isEmpty());
|
||||
m_text_box->setHtml(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_label->setText(result);
|
||||
}
|
||||
}
|
||||
|
||||
bool config_checker::check_config(cfg_mode mode, QString content_or_serial, QString& result)
|
||||
{
|
||||
std::unique_ptr<cfg_root> config = std::make_unique<cfg_root>();
|
||||
std::unique_ptr<cfg_root> config_db_only;
|
||||
|
||||
if (m_checker_mode == checker_mode::log)
|
||||
{
|
||||
const QString start_token = "SYS: Used configuration:\n";
|
||||
const QString end_token = "\n·";
|
||||
|
||||
qsizetype start = content.indexOf(start_token);
|
||||
qsizetype start = content_or_serial.indexOf(start_token);
|
||||
qsizetype end = -1;
|
||||
|
||||
if (start >= 0)
|
||||
{
|
||||
start += start_token.size();
|
||||
end = content.indexOf(end_token, start);
|
||||
end = content_or_serial.indexOf(end_token, start);
|
||||
}
|
||||
|
||||
if (end < 0)
|
||||
|
|
@ -79,24 +130,93 @@ bool config_checker::check_config(QString content, QString& result, bool is_log)
|
|||
return false;
|
||||
}
|
||||
|
||||
content = content.mid(start, end - start);
|
||||
content_or_serial = content_or_serial.mid(start, end - start);
|
||||
}
|
||||
|
||||
if (!config.from_string(content.toStdString()))
|
||||
if (m_checker_mode == checker_mode::gamelist)
|
||||
{
|
||||
gui_log.error("log_viewer: Failed to parse config:\n%s", content);
|
||||
config->from_default();
|
||||
|
||||
// Load global config
|
||||
const std::string cfg_path = fs::get_config_dir(true) + "config.yml";
|
||||
if (const fs::file cfg_file{cfg_path})
|
||||
{
|
||||
gui_log.notice("config_checker: Applying global config: %s", cfg_path);
|
||||
|
||||
if (!config->from_string(cfg_file.to_string()))
|
||||
{
|
||||
gui_log.error("config_checker: Failed to apply global config: %s", cfg_path);
|
||||
result = tr("Failed to apply global config!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Load custom config
|
||||
const std::string custom_config_path = rpcs3::utils::get_custom_config_path(m_serial);
|
||||
if (mode == cfg_mode::custom && !custom_config_path.empty())
|
||||
{
|
||||
if (const fs::file cfg_file{custom_config_path})
|
||||
{
|
||||
gui_log.notice("config_checker: Applying custom config: %s", custom_config_path);
|
||||
|
||||
if (!config->from_string(cfg_file.to_string()))
|
||||
{
|
||||
gui_log.error("config_checker: Failed to apply custom config: %s", custom_config_path);
|
||||
result = tr("Failed to apply custom config!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == cfg_mode::database_config && !m_db_config.empty())
|
||||
{
|
||||
gui_log.notice("config_checker: Applying database config: %s", custom_config_path);
|
||||
|
||||
if (!config->from_string(m_db_config))
|
||||
{
|
||||
gui_log.error("config_checker: Failed to apply database config:\n%s", m_db_config);
|
||||
result = tr("Failed to apply database config!");
|
||||
return false;
|
||||
}
|
||||
|
||||
config_db_only = std::make_unique<cfg_root>();
|
||||
config_db_only->from_default();
|
||||
if (!config_db_only->from_string(m_db_config))
|
||||
{
|
||||
gui_log.error("config_checker: Failed to apply database config:\n%s", m_db_config);
|
||||
result = tr("Failed to apply database config!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!config->from_string(content_or_serial.toStdString()))
|
||||
{
|
||||
gui_log.error("config_checker: Failed to parse config:\n%s", content_or_serial);
|
||||
result = tr("Cannot find any config!");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::function<void(const cfg::_base*, std::string&, int)> print_diff_recursive;
|
||||
print_diff_recursive = [&print_diff_recursive](const cfg::_base* base, std::string& diff, int indentation) -> void
|
||||
std::function<void(const cfg::_base*, const cfg::_base*, std::string&, int)> print_diff_recursive;
|
||||
print_diff_recursive = [this, &print_diff_recursive, &config](const cfg::_base* base, const cfg::_base* base_db_only, std::string& diff, int indentation) -> void
|
||||
{
|
||||
if (!base)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore some irrelevant settings in gamelist mode
|
||||
if (m_checker_mode == checker_mode::gamelist && base->get_type() != cfg::type::node)
|
||||
{
|
||||
const std::string key = base->get_name();
|
||||
|
||||
if (key == config->sys.console_psid.get_name() ||
|
||||
key == config->sys.system_name.get_name() ||
|
||||
key == config->video.vk.adapter.get_name())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto indent = [](std::string& str, int indentation)
|
||||
{
|
||||
for (int i = 0; i < indentation * 2; i++)
|
||||
|
|
@ -105,6 +225,16 @@ bool config_checker::check_config(QString content, QString& result, bool is_log)
|
|||
}
|
||||
};
|
||||
|
||||
const auto base_name_db = [base](bool is_db_config)
|
||||
{
|
||||
if (is_db_config)
|
||||
{
|
||||
return "*" + base->get_name();
|
||||
}
|
||||
|
||||
return base->get_name();
|
||||
};
|
||||
|
||||
switch (base->get_type())
|
||||
{
|
||||
case cfg::type::node:
|
||||
|
|
@ -115,7 +245,20 @@ bool config_checker::check_config(QString content, QString& result, bool is_log)
|
|||
|
||||
for (const auto& n : node->get_nodes())
|
||||
{
|
||||
print_diff_recursive(n, diff_tmp, indentation + 1);
|
||||
const cfg::_base* n_db_only = nullptr;
|
||||
if (const auto& node_db_only = static_cast<const cfg::node*>(base_db_only))
|
||||
{
|
||||
for (const auto& n_db : node_db_only->get_nodes())
|
||||
{
|
||||
if (n_db->get_name() == n->get_name())
|
||||
{
|
||||
n_db_only = n_db;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print_diff_recursive(n, n_db_only, diff_tmp, indentation + 1);
|
||||
}
|
||||
|
||||
if (!diff_tmp.empty())
|
||||
|
|
@ -142,19 +285,75 @@ bool config_checker::check_config(QString content, QString& result, bool is_log)
|
|||
const std::string val = base->to_string();
|
||||
const std::string def = base->def_to_string();
|
||||
|
||||
if (val != def)
|
||||
{
|
||||
indent(diff, indentation);
|
||||
if (val == def)
|
||||
break;
|
||||
|
||||
if (def.empty())
|
||||
indent(diff, indentation);
|
||||
|
||||
if (m_checker_mode == checker_mode::gamelist)
|
||||
{
|
||||
if (base->get_name() == config->io.midi_devices.get_name())
|
||||
{
|
||||
fmt::append(diff, "%s: <span style=\"color:red;\">%s</span><br>", base->get_name(), val);
|
||||
fmt::append(diff, "%s:<br>", base->get_name());
|
||||
|
||||
midi_creator mc {};
|
||||
|
||||
mc.parse_devices(def);
|
||||
const std::array<midi_device, max_midi_devices> def_devices = mc.get_selection_list();
|
||||
|
||||
mc.parse_devices(val);
|
||||
const std::array<midi_device, max_midi_devices> devices = mc.get_selection_list();
|
||||
|
||||
for (usz i = 0; i < devices.size(); i++)
|
||||
{
|
||||
const midi_device& def_device = def_devices[i];
|
||||
const midi_device& device = devices[i];
|
||||
|
||||
if (device.name == def_device.name)
|
||||
continue;
|
||||
|
||||
indent(diff, indentation + 1);
|
||||
fmt::append(diff, "Device %d: <span style=\"color:red;\">%s: %s</span><br>", i + 1, device.type, device.name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
else if (base->get_name() == config->audio.microphone_devices.get_name())
|
||||
{
|
||||
fmt::append(diff, "%s: <span style=\"color:red;\">%s</span> <span style=\"color:gray;\">default:</span> <span style=\"color:green;\">%s</span><br>", base->get_name(), val, def);
|
||||
fmt::append(diff, "%s:<br>", base->get_name());
|
||||
|
||||
microphone_creator mc {};
|
||||
|
||||
mc.parse_devices(def);
|
||||
const std::array<std::string, 4> def_devices = mc.get_selection_list();
|
||||
|
||||
mc.parse_devices(val);
|
||||
const std::array<std::string, 4> devices = mc.get_selection_list();
|
||||
|
||||
for (usz i = 0; i < devices.size(); i++)
|
||||
{
|
||||
const std::string& def_device = def_devices[i];
|
||||
const std::string& device = devices[i];
|
||||
|
||||
if (device == def_device)
|
||||
continue;
|
||||
|
||||
indent(diff, indentation + 1);
|
||||
fmt::append(diff, "Device %d: <span style=\"color:red;\">%s</span><br>", i + 1, device);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const bool is_db_config = base_db_only && base_db_only->to_string() != def;
|
||||
|
||||
if (def.empty())
|
||||
{
|
||||
fmt::append(diff, "%s: <span style=\"color:red;\">%s</span><br>", base_name_db(is_db_config), val);
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt::append(diff, "%s: <span style=\"color:red;\">%s</span> <span style=\"color:gray;\">default:</span> <span style=\"color:green;\">%s</span><br>", base_name_db(is_db_config), val, def);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case cfg::type::set:
|
||||
|
|
@ -208,7 +407,7 @@ bool config_checker::check_config(QString content, QString& result, bool is_log)
|
|||
};
|
||||
|
||||
std::string diff;
|
||||
print_diff_recursive(&config, diff, 0);
|
||||
print_diff_recursive(config.get(), config_db_only.get(), diff, 0);
|
||||
result = QString::fromStdString(diff);
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include "Emu/config_mode.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QTextEdit>
|
||||
|
||||
class config_checker : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
config_checker(QWidget* parent, const QString& path, bool is_log);
|
||||
enum class checker_mode
|
||||
{
|
||||
config,
|
||||
log,
|
||||
gamelist
|
||||
};
|
||||
|
||||
bool check_config(QString content, QString& result, bool is_log);
|
||||
config_checker(QWidget* parent, const QString& content_or_serial, checker_mode mode, const std::string& db_config = {});
|
||||
|
||||
private:
|
||||
void check_config(cfg_mode mode);
|
||||
bool check_config(cfg_mode mode, QString content_or_serial, QString& result);
|
||||
|
||||
QLabel* m_label = nullptr;
|
||||
QTextEdit* m_text_box = nullptr;
|
||||
|
||||
checker_mode m_checker_mode = checker_mode::config;
|
||||
QString m_content_or_serial;
|
||||
std::string m_db_config;
|
||||
std::string m_serial;
|
||||
};
|
||||
|
|
|
|||
228
rpcs3/rpcs3qt/config_database.cpp
Normal file
228
rpcs3/rpcs3qt/config_database.cpp
Normal 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
rpcs3/rpcs3qt/config_database.h
Normal file
43
rpcs3/rpcs3qt/config_database.h
Normal 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;
|
||||
};
|
||||
|
|
@ -103,7 +103,7 @@ void downloader::start(const std::string& url, bool follow_location, bool show_p
|
|||
{
|
||||
if (m_curl_abort)
|
||||
{
|
||||
network_log.notice("Download aborted");
|
||||
network_log.notice("Download aborted (url='%s')", url);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ void downloader::start(const std::string& url, bool follow_location, bool show_p
|
|||
|
||||
if (m_curl_success)
|
||||
{
|
||||
network_log.notice("Download finished");
|
||||
network_log.notice("Download finished (url='%s')", url);
|
||||
|
||||
if (check_return_code && m_download_attempts < 3)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ bool emu_settings::Init()
|
|||
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;
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
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
|
||||
if (!title_id.empty())
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ public:
|
|||
midi_creator m_midi_creator;
|
||||
|
||||
/** 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.*/
|
||||
void OpenCorrectionDialog(QWidget* parent = Q_NULLPTR);
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@
|
|||
|
||||
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)
|
||||
, m_gui_settings(std::move(gui_settings))
|
||||
, m_gui_settings(std::move(settings))
|
||||
{
|
||||
m_filepath = m_gui_settings->GetSettingsDir() + "/compat_database.dat";
|
||||
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);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +69,16 @@ void game_compatibility::handle_download_canceled()
|
|||
|
||||
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);
|
||||
|
||||
if (return_code < 0)
|
||||
|
|
@ -220,11 +229,11 @@ void game_compatibility::RequestCompatibility(bool online)
|
|||
|
||||
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();
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
|
|
@ -244,7 +253,7 @@ compat::status game_compatibility::GetStatusData(const QString& status) const
|
|||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -148,13 +148,13 @@ public:
|
|||
void RequestCompatibility(bool online = false);
|
||||
|
||||
/** 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 */
|
||||
compat::status GetStatusData(const QString& status) const;
|
||||
|
||||
/** 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:
|
||||
void DownloadStarted();
|
||||
|
|
|
|||
|
|
@ -374,7 +374,7 @@ void game_list_actions::ShowDiskUsageDialog()
|
|||
// so run it on a concurrent thread avoiding to block the entire GUI
|
||||
m_disk_usage_future = QtConcurrent::run([this]()
|
||||
{
|
||||
thread_ctrl::set_name("Disk Usage");
|
||||
thread_base::set_name("Disk Usage");
|
||||
|
||||
const std::vector<std::pair<std::string, u64>> vfs_disk_usage = rpcs3::utils::get_vfs_disk_usage();
|
||||
const u64 cache_disk_usage = rpcs3::utils::get_cache_disk_usage();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
#include "pad_settings_dialog.h"
|
||||
#include "patch_manager_dialog.h"
|
||||
#include "persistent_settings.h"
|
||||
#include "config_database.h"
|
||||
#include "config_checker.h"
|
||||
|
||||
#include "Utilities/File.h"
|
||||
#include "Emu/system_utils.hpp"
|
||||
|
|
@ -67,8 +69,8 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
|
|||
// Make Actions
|
||||
QAction* boot = new QAction(gameinfo->has_custom_config
|
||||
? (is_current_running_game
|
||||
? tr("&Reboot with Global Configuration")
|
||||
: tr("&Boot with Global Configuration"))
|
||||
? (gameinfo->has_database_config ? tr("&Reboot with Database + Global Configuration") : tr("&Reboot with Global Configuration"))
|
||||
: (gameinfo->has_database_config ? tr("&Boot with Database + Global Configuration") : tr("&Boot with Global Configuration")))
|
||||
: (is_current_running_game
|
||||
? tr("&Reboot")
|
||||
: tr("&Boot")));
|
||||
|
|
@ -156,6 +158,8 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
|
|||
|
||||
addSeparator();
|
||||
|
||||
QAction* create_game_database_config = (gameinfo->has_custom_config || !gameinfo->has_database_config) ? nullptr
|
||||
: addAction(tr("&Create Custom Configuration From Database Settings"));
|
||||
QAction* configure = addAction(gameinfo->has_custom_config
|
||||
? tr("&Change Custom Configuration")
|
||||
: tr("&Create Custom Configuration From Global Settings"));
|
||||
|
|
@ -164,6 +168,26 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
|
|||
QAction* pad_configure = addAction(gameinfo->has_custom_pad_config
|
||||
? tr("&Change Custom Gamepad Configuration")
|
||||
: tr("&Create Custom Gamepad Configuration"));
|
||||
|
||||
QAction* compare_config = addAction(tr("&Compare Configurations"));
|
||||
connect(compare_config, &QAction::triggered, this, [this, serial]()
|
||||
{
|
||||
std::string db_config;
|
||||
if (config_database* db = m_game_list_frame->GetConfigDatabase(); db->has_config(serial))
|
||||
{
|
||||
if (const std::optional<std::string> config = db->get_config(serial))
|
||||
{
|
||||
db_config = *config;
|
||||
}
|
||||
else
|
||||
{
|
||||
game_list_log.error("No database config found for '%s'", serial);
|
||||
}
|
||||
}
|
||||
config_checker* dlg = new config_checker(m_game_list_frame, QString::fromStdString(serial), config_checker::checker_mode::gamelist, db_config);
|
||||
dlg->open();
|
||||
});
|
||||
|
||||
QAction* configure_patches = addAction(tr("&Manage Game Patches"));
|
||||
|
||||
addSeparator();
|
||||
|
|
@ -578,6 +602,7 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
|
|||
|
||||
QAction* check_compat = addAction(tr("&Check Game Compatibility"));
|
||||
QAction* download_compat = addAction(tr("&Download Compatibility Database"));
|
||||
QAction* download_config_db = addAction(tr("&Download Config Database"));
|
||||
|
||||
addSeparator();
|
||||
|
||||
|
|
@ -598,12 +623,13 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
|
|||
connect(boot, &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::global);
|
||||
Q_EMIT m_game_list_frame->RequestBoot(gameinfo, cfg_mode::database_config);
|
||||
});
|
||||
|
||||
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, ¤t_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, ¤t_game, create_cfg_from_global_cfg, db_config ? *db_config : "");
|
||||
|
||||
connect(&dlg, &settings_dialog::EmuSettingsApplied, [this, gameinfo]()
|
||||
{
|
||||
|
|
@ -618,14 +644,16 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
|
|||
dlg.exec();
|
||||
};
|
||||
|
||||
connect(configure, &QAction::triggered, this, [configure_game]() { configure_game(true, false); });
|
||||
|
||||
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_l = std::move(configure_l)]() { configure_l(false); });
|
||||
connect(create_game_default_config, &QAction::triggered, m_game_list_frame, [configure_game]() { configure_game(false, 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]()
|
||||
|
|
@ -671,7 +699,11 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info&
|
|||
});
|
||||
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]
|
||||
{
|
||||
|
|
@ -935,7 +967,13 @@ void game_list_context_menu::show_multi_selection_context_menu(const std::vector
|
|||
QAction* download_compat = addAction(tr("&Download Compatibility Database"));
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include "game_list_table.h"
|
||||
#include "game_list_grid.h"
|
||||
#include "game_list_grid_item.h"
|
||||
#include "config_database.h"
|
||||
|
||||
#include "Emu/System.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_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->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));
|
||||
});
|
||||
|
||||
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_grid, &game_list_grid::FocusToSearchBar, this, &game_list_frame::FocusToSearchBar);
|
||||
|
||||
|
|
@ -804,6 +822,7 @@ void game_list_frame::OnParsingFinished()
|
|||
|
||||
game.localized_category = std::move(qt_cat);
|
||||
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_pad_config = fs::is_file(rpcs3::utils::get_custom_input_config_path(game.info.serial));
|
||||
|
||||
|
|
@ -1058,6 +1077,15 @@ void game_list_frame::OnCompatFinished()
|
|||
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)
|
||||
{
|
||||
QStringList& filters = m_is_list_layout ? m_category_filters : m_grid_category_filters;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#include <optional>
|
||||
#include <set>
|
||||
|
||||
class config_database;
|
||||
class game_list_table;
|
||||
class game_list_grid;
|
||||
class gui_settings;
|
||||
|
|
@ -53,7 +54,8 @@ public:
|
|||
|
||||
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; }
|
||||
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; }
|
||||
|
|
@ -95,6 +97,7 @@ private Q_SLOTS:
|
|||
void OnParsingFinished();
|
||||
void OnRefreshFinished();
|
||||
void OnCompatFinished();
|
||||
void OnConfigDatabaseFinished();
|
||||
void OnColClicked(int col);
|
||||
void ShowContextMenu(const QPoint& pos);
|
||||
void doubleClickedSlot(QTableWidgetItem* item);
|
||||
|
|
@ -151,6 +154,7 @@ private:
|
|||
// Game List
|
||||
game_list_table* m_game_list = nullptr;
|
||||
game_compatibility* m_game_compat = nullptr;
|
||||
config_database* m_config_db = nullptr;
|
||||
progress_dialog* m_progress_dialog = nullptr;
|
||||
std::map<int, QAction*> m_column_acts;
|
||||
Qt::SortOrder m_col_sort_order{};
|
||||
|
|
|
|||
|
|
@ -640,13 +640,15 @@ void gui_application::InitializeCallbacks()
|
|||
on_exit();
|
||||
}
|
||||
|
||||
const bool no_gui = !m_main_window;
|
||||
|
||||
if (m_main_window)
|
||||
{
|
||||
// Close main window in order to save its window state
|
||||
m_main_window->close();
|
||||
}
|
||||
|
||||
gui_log.notice("Quitting gui application");
|
||||
gui_log.notice("Quitting gui application (force_quit=%d, no-gui=%d)", force_quit, no_gui);
|
||||
quit();
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,9 +13,10 @@ struct gui_game_info
|
|||
{
|
||||
GameInfo info{};
|
||||
QString localized_category;
|
||||
compat::status compat;
|
||||
compat::status compat{};
|
||||
QPixmap icon;
|
||||
QPixmap pxmap;
|
||||
bool has_database_config = false;
|
||||
bool has_custom_config = false;
|
||||
bool has_custom_pad_config = false;
|
||||
bool has_custom_icon = false;
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ void log_viewer::show_context_menu(const QPoint& pos)
|
|||
|
||||
connect(config, &QAction::triggered, this, [this]()
|
||||
{
|
||||
config_checker* dlg = new config_checker(this, m_full_log, true);
|
||||
config_checker* dlg = new config_checker(this, m_full_log, config_checker::checker_mode::log);
|
||||
dlg->open();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@
|
|||
#include "music_player_dialog.h"
|
||||
#include "sound_effect_manager_dialog.h"
|
||||
#include "recording_settings_dialog.h"
|
||||
#include "config_database.h"
|
||||
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
|
|
@ -168,7 +169,7 @@ extern void qt_events_aware_op(int repeat_duration_ms, std::function<bool()> wra
|
|||
}
|
||||
}
|
||||
|
||||
main_window::main_window(std::shared_ptr<gui_settings> gui_settings, std::shared_ptr<emu_settings> emu_settings, std::shared_ptr<persistent_settings> persistent_settings, QWidget *parent)
|
||||
main_window::main_window(std::shared_ptr<gui_settings> gui_settings, std::shared_ptr<emu_settings> emu_settings, std::shared_ptr<persistent_settings> persistent_settings, QWidget* parent)
|
||||
: QMainWindow(parent)
|
||||
, ui(new Ui::main_window)
|
||||
, m_gui_settings(gui_settings)
|
||||
|
|
@ -233,7 +234,7 @@ bool main_window::Init([[maybe_unused]] bool with_cli_boot)
|
|||
|
||||
connect(ui->actionDownload_Update, &QAction::triggered, this, [this]
|
||||
{
|
||||
m_updater.update(false);
|
||||
m_updater.update(false, true);
|
||||
});
|
||||
|
||||
#ifdef _WIN32
|
||||
|
|
@ -259,6 +260,25 @@ bool main_window::Init([[maybe_unused]] bool with_cli_boot)
|
|||
#endif
|
||||
|
||||
#ifdef RPCS3_UPDATE_SUPPORTED
|
||||
#ifndef _WIN32
|
||||
connect(&m_updater, &update_manager::signal_download_additional_files, this, [this](bool auto_accept)
|
||||
{
|
||||
if (!m_game_list_frame) return;
|
||||
|
||||
connect(m_game_list_frame->GetGameCompatibility(), &game_compatibility::DownloadFinished, this, [this, auto_accept]()
|
||||
{
|
||||
connect(m_game_list_frame->GetConfigDatabase(), &config_database::download_finished, this, [this, auto_accept]()
|
||||
{
|
||||
m_updater.update(auto_accept, false);
|
||||
}, Qt::ConnectionType::SingleShotConnection);
|
||||
|
||||
m_game_list_frame->GetConfigDatabase()->request_config_database(true);
|
||||
}, Qt::ConnectionType::SingleShotConnection);
|
||||
|
||||
m_game_list_frame->GetGameCompatibility()->RequestCompatibility(true);
|
||||
});
|
||||
#endif
|
||||
|
||||
if (const auto update_value = m_gui_settings->GetValue(gui::m_check_upd_start).toString(); update_value != gui::update_off)
|
||||
{
|
||||
const bool in_background = with_cli_boot || update_value == gui::update_bkg;
|
||||
|
|
@ -493,6 +513,9 @@ void main_window::show_boot_error(game_boot_result status)
|
|||
case game_boot_result::firmware_version:
|
||||
message = tr("The game or PS3 application needs a more recent firmware version.");
|
||||
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::already_added: // Handled elsewhere
|
||||
case game_boot_result::currently_restricted:
|
||||
|
|
@ -530,7 +553,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);
|
||||
|
||||
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))
|
||||
{
|
||||
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);
|
||||
show_boot_error(error);
|
||||
|
|
@ -940,10 +981,10 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo
|
|||
bool precompile_caches = 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.
|
||||
pkg_install_dialog dlg(file_paths, compat, this);
|
||||
pkg_install_dialog dlg(file_paths, from_boot, compat, this);
|
||||
connect(&dlg, &QDialog::finished, this, [&](int result)
|
||||
{
|
||||
if (result != QDialog::Accepted)
|
||||
|
|
@ -968,7 +1009,8 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo
|
|||
|
||||
if (canceled)
|
||||
{
|
||||
return false;
|
||||
// return "true" if installation of optional packages (requested by some games at first boot) is skipped
|
||||
return from_boot;
|
||||
}
|
||||
|
||||
if (!from_boot)
|
||||
|
|
@ -3208,7 +3250,7 @@ void main_window::CreateConnects()
|
|||
|
||||
m_gui_settings->SetValue(gui::fd_cfg_check, file_info.path());
|
||||
|
||||
config_checker* dlg = new config_checker(this, content, file_path.endsWith(".log") || file_path.endsWith(".log.gz"));
|
||||
config_checker* dlg = new config_checker(this, content, (file_path.endsWith(".log") || file_path.endsWith(".log.gz")) ? config_checker::checker_mode::log : config_checker::checker_mode::config);
|
||||
dlg->open();
|
||||
});
|
||||
|
||||
|
|
@ -3906,6 +3948,7 @@ void main_window::closeEvent(QCloseEvent* closeEvent)
|
|||
|
||||
Q_EMIT NotifyWindowCloseEvent(true);
|
||||
|
||||
gui_log.notice("Quit with main_window::closeEvent");
|
||||
Emu.Quit(true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class main_window : public QMainWindow
|
|||
};
|
||||
|
||||
public:
|
||||
explicit main_window(std::shared_ptr<gui_settings> gui_settings, std::shared_ptr<emu_settings> emu_settings, std::shared_ptr<persistent_settings> persistent_settings, QWidget *parent = nullptr);
|
||||
explicit main_window(std::shared_ptr<gui_settings> gui_settings, std::shared_ptr<emu_settings> emu_settings, std::shared_ptr<persistent_settings> persistent_settings, QWidget* parent = nullptr);
|
||||
~main_window();
|
||||
bool Init(bool with_cli_boot);
|
||||
QIcon GetAppIcon() const;
|
||||
|
|
|
|||
|
|
@ -1168,7 +1168,16 @@ void patch_manager_dialog::download_update(bool automatic, bool auto_accept)
|
|||
|
||||
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);
|
||||
|
||||
if (return_code < 0)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ enum Roles
|
|||
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, bool from_boot, const game_compatibility* compat, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
ensure(!paths.empty());
|
||||
|
|
@ -148,6 +148,11 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil
|
|||
buttons->button(QDialogButtonBox::Ok)->setText(tr("Install"));
|
||||
buttons->button(QDialogButtonBox::Ok)->setDefault(true);
|
||||
|
||||
if (from_boot)
|
||||
{
|
||||
buttons->button(QDialogButtonBox::Cancel)->setText(tr("Skip"));
|
||||
}
|
||||
|
||||
m_dir_list->sortItems();
|
||||
m_dir_list->setCurrentRow(0);
|
||||
m_dir_list->setMinimumWidth((m_dir_list->sizeHintForColumn(0) * 125) / 100);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class pkg_install_dialog : public QDialog
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit pkg_install_dialog(const QStringList& paths, game_compatibility* compat, QWidget* parent = nullptr);
|
||||
explicit pkg_install_dialog(const QStringList& paths, bool from_boot, const game_compatibility* compat, QWidget* parent = nullptr);
|
||||
std::vector<compat::package_info> get_paths_to_install() const;
|
||||
bool precompile_caches() const { return m_precompile_caches; }
|
||||
bool create_desktop_shortcuts() const { return m_create_desktop_shortcuts; }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
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)
|
||||
, m_tab_index(tab_index)
|
||||
, ui(new Ui::settings_dialog)
|
||||
|
|
@ -132,7 +132,7 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
|
|||
|
||||
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)));
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class settings_dialog : public QDialog
|
|||
Q_OBJECT
|
||||
|
||||
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();
|
||||
void open() override;
|
||||
Q_SIGNALS:
|
||||
|
|
|
|||
|
|
@ -4519,11 +4519,11 @@
|
|||
</property>
|
||||
<layout class="QVBoxLayout" name="gb_debug_cpu_layout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="accurateDFMA">
|
||||
<widget class="QCheckBox" name="accurateDFMA">
|
||||
<property name="text">
|
||||
<string>Accurate PPU/SPU Double-Precision FMA</string>
|
||||
<string>Accurate PPU/SPU Double-Precision FMA</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="accurateClineStores">
|
||||
|
|
@ -4575,7 +4575,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacerDebugCore">
|
||||
<spacer name="verticalSpacerDebugCpu">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@
|
|||
|
||||
LOG_CHANNEL(update_log, "UPDATER");
|
||||
|
||||
constexpr bool allow_local_auto_update = false; // Set true for debugging the auto updater locally
|
||||
|
||||
update_manager::update_manager(QObject* parent, std::shared_ptr<gui_settings> gui_settings)
|
||||
: QObject(parent), m_gui_settings(std::move(gui_settings))
|
||||
{
|
||||
|
|
@ -60,7 +62,7 @@ void update_manager::check_for_updates(bool automatic, bool check_only, bool aut
|
|||
if (automatic)
|
||||
{
|
||||
// Don't check for updates on local builds
|
||||
if (rpcs3::is_local_build())
|
||||
if (!allow_local_auto_update && rpcs3::is_local_build())
|
||||
{
|
||||
update_log.notice("Skipped automatic update check: this is a local build");
|
||||
return;
|
||||
|
|
@ -116,7 +118,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);
|
||||
|
||||
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);
|
||||
|
||||
m_update_info.hash_found = true;
|
||||
|
|
@ -126,7 +137,7 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce
|
|||
std::string error_message;
|
||||
switch (return_code)
|
||||
{
|
||||
case -1: error_message = "Hash not found(Custom/PR build)"; break;
|
||||
case -1: error_message = "Hash not found (Custom/PR build)"; break;
|
||||
case -2: error_message = "Server Error - Maintenance Mode"; break;
|
||||
case -3: error_message = "Server Error - Illegal Search"; break;
|
||||
case -255: error_message = "Server Error - Return code not found"; break;
|
||||
|
|
@ -139,14 +150,12 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce
|
|||
update_log.warning("Update error: %s, return code: %d", error_message, return_code);
|
||||
|
||||
// If a user clicks "Check for Updates" with a custom build ask him if he's sure he wants to update to latest version
|
||||
if (!automatic && return_code == -1)
|
||||
{
|
||||
m_update_info.hash_found = false;
|
||||
}
|
||||
else
|
||||
if (!allow_local_auto_update && (automatic || return_code != -1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_update_info.hash_found = false;
|
||||
}
|
||||
|
||||
const auto& current = json_data["current_build"];
|
||||
|
|
@ -302,17 +311,17 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce
|
|||
return true;
|
||||
}
|
||||
|
||||
update(auto_accept);
|
||||
update(auto_accept, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void update_manager::update(bool auto_accept)
|
||||
void update_manager::update(bool auto_accept, bool is_first_call)
|
||||
{
|
||||
update_log.notice("Updating with auto_accept=%d", auto_accept);
|
||||
|
||||
ensure(m_downloader);
|
||||
|
||||
if (!auto_accept)
|
||||
if (!auto_accept && is_first_call)
|
||||
{
|
||||
if (!m_update_info.update_found)
|
||||
{
|
||||
|
|
@ -416,6 +425,14 @@ void update_manager::update(bool auto_accept)
|
|||
return;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
if (is_first_call)
|
||||
{
|
||||
Q_EMIT signal_download_additional_files(auto_accept);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
m_downloader->disconnect();
|
||||
|
||||
connect(m_downloader, &downloader::signal_download_error, this, [this](const QString& /*error*/)
|
||||
|
|
|
|||
|
|
@ -16,10 +16,11 @@ class update_manager final : public QObject
|
|||
public:
|
||||
update_manager(QObject* parent, std::shared_ptr<gui_settings> gui_settings);
|
||||
void check_for_updates(bool automatic, bool check_only, bool auto_accept, QWidget* parent = nullptr);
|
||||
void update(bool auto_accept);
|
||||
void update(bool auto_accept, bool is_first_call);
|
||||
|
||||
Q_SIGNALS:
|
||||
void signal_update_available(bool update_available);
|
||||
void signal_download_additional_files(bool auto_accept);
|
||||
|
||||
private:
|
||||
downloader* m_downloader = nullptr;
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@
|
|||
<ClCompile Include="test_rsx_fp_asm.cpp" />
|
||||
<ClCompile Include="test_simple_array.cpp" />
|
||||
<ClCompile Include="test_address_range.cpp" />
|
||||
<ClCompile Include="test_sys_fs.cpp" />
|
||||
<ClCompile Include="test_tuple.cpp" />
|
||||
<ClCompile Include="test_pair.cpp" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
51
rpcs3/tests/test_sys_fs.cpp
Normal file
51
rpcs3/tests/test_sys_fs.cpp
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
#define private public
|
||||
#include "Emu/Cell/lv2/sys_fs.h"
|
||||
#undef private
|
||||
|
||||
using namespace utils;
|
||||
|
||||
namespace utils
|
||||
{
|
||||
TEST(cellFs, PathRoot)
|
||||
{
|
||||
std::string path = "/.";
|
||||
auto [root, trail] = lv2_fs_object::get_path_root_and_trail(path);
|
||||
EXPECT_TRUE(root.empty());
|
||||
EXPECT_TRUE(trail.empty());
|
||||
|
||||
path = "/./././dev_bdvd/./";
|
||||
std::tie(root, trail) = lv2_fs_object::get_path_root_and_trail(path);
|
||||
EXPECT_EQ(root, "dev_bdvd"sv);
|
||||
EXPECT_TRUE(trail.empty());
|
||||
|
||||
path = "/../";
|
||||
std::tie(root, trail) = lv2_fs_object::get_path_root_and_trail(path);
|
||||
EXPECT_TRUE(root.empty());
|
||||
EXPECT_EQ(trail, "ENOENT"sv);
|
||||
}
|
||||
|
||||
TEST(cellFs, PathSimplify)
|
||||
{
|
||||
std::string path = "/dev_hdd0/";
|
||||
auto [root, trail] = lv2_fs_object::get_path_root_and_trail(path);
|
||||
EXPECT_EQ(root, "dev_hdd0"sv);
|
||||
EXPECT_TRUE(trail.empty());
|
||||
|
||||
path = "/dev_hdd0/game";
|
||||
std::tie(root, trail) = lv2_fs_object::get_path_root_and_trail(path);
|
||||
EXPECT_EQ(root, "dev_hdd0"sv);
|
||||
EXPECT_EQ(trail, "game"sv);
|
||||
|
||||
path = "/dev_hdd0/game/NP1234567";
|
||||
std::tie(root, trail) = lv2_fs_object::get_path_root_and_trail(path);
|
||||
EXPECT_EQ(root, "dev_hdd0"sv);
|
||||
EXPECT_EQ(trail, "game/NP1234567"sv);
|
||||
|
||||
path = "/dev_hdd0/game/NP1234567/../../NP1234568/.";
|
||||
std::tie(root, trail) = lv2_fs_object::get_path_root_and_trail(path);
|
||||
EXPECT_EQ(root, "dev_hdd0"sv);
|
||||
EXPECT_EQ(trail, "NP1234568"sv);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue