mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-05-07 13:37:46 +00:00
Merge branch 'master' into master
This commit is contained in:
commit
45c56fdfce
81 changed files with 2496 additions and 802 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
|
||||
|
|
|
|||
8
.github/workflows/rpcs3.yml
vendored
8
.github/workflows/rpcs3.yml
vendored
|
|
@ -30,23 +30,23 @@ jobs:
|
|||
matrix:
|
||||
include:
|
||||
- os: ubuntu-24.04
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy:1.10"
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy:1.11"
|
||||
build_sh: "/rpcs3/.ci/build-linux.sh"
|
||||
compiler: clang
|
||||
UPLOAD_COMMIT_HASH: d812f1254a1157c80fd402f94446310560f54e5f
|
||||
UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux"
|
||||
- os: ubuntu-24.04
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy:1.10"
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy:1.11"
|
||||
build_sh: "/rpcs3/.ci/build-linux.sh"
|
||||
compiler: gcc
|
||||
- os: ubuntu-24.04-arm
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.10"
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.11"
|
||||
build_sh: "/rpcs3/.ci/build-linux-aarch64.sh"
|
||||
compiler: clang
|
||||
UPLOAD_COMMIT_HASH: a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1
|
||||
UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux-arm64"
|
||||
- os: ubuntu-24.04-arm
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.10"
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.11"
|
||||
build_sh: "/rpcs3/.ci/build-linux-aarch64.sh"
|
||||
compiler: gcc
|
||||
name: RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }}
|
||||
|
|
|
|||
2
3rdparty/libpng/libpng
vendored
2
3rdparty/libpng/libpng
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit d5515b5b8be3901aac04e5bd8bd5c89f287bcd33
|
||||
Subproject commit 95ab3fdca83ea294efd3b092e9a53c5a39886444
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include <process.h>
|
||||
#include <sysinfoapi.h>
|
||||
|
||||
#include "stack_trace.h"
|
||||
#include "util/dyn_lib.hpp"
|
||||
|
||||
DYNAMIC_IMPORT_RENAME("Kernel32.dll", SetThreadDescriptionImport, "SetThreadDescription", HRESULT(HANDLE hThread, PCWSTR lpThreadDescription));
|
||||
|
|
@ -1981,9 +1982,39 @@ static LONG exception_filter(PEXCEPTION_POINTERS pExp) noexcept
|
|||
}
|
||||
|
||||
fmt::append(msg, "RPCS3 image base: %p.\n", GetModuleHandle(NULL));
|
||||
|
||||
#if defined(ARCH_X64)
|
||||
fmt::append(msg, "RAX: %016llX RBX: %016llX\n", pExp->ContextRecord->Rax, pExp->ContextRecord->Rbx);
|
||||
fmt::append(msg, "RCX: %016llX RDX: %016llX\n", pExp->ContextRecord->Rcx, pExp->ContextRecord->Rdx);
|
||||
fmt::append(msg, "RSI: %016llX RDI: %016llX\n", pExp->ContextRecord->Rsi, pExp->ContextRecord->Rdi);
|
||||
fmt::append(msg, "RBP: %016llX RSP: %016llX\n", pExp->ContextRecord->Rbp, pExp->ContextRecord->Rsp);
|
||||
fmt::append(msg, "R8: %016llX R9: %016llX\n", pExp->ContextRecord->R8, pExp->ContextRecord->R9);
|
||||
fmt::append(msg, "R10: %016llX R11: %016llX\n", pExp->ContextRecord->R10, pExp->ContextRecord->R11);
|
||||
fmt::append(msg, "R12: %016llX R13: %016llX\n", pExp->ContextRecord->R12, pExp->ContextRecord->R13);
|
||||
fmt::append(msg, "R14: %016llX R15: %016llX\n", pExp->ContextRecord->R14, pExp->ContextRecord->R15);
|
||||
fmt::append(msg, "RFLAGS: %08X\n", pExp->ContextRecord->EFlags);
|
||||
#elif defined(ARCH_ARM64)
|
||||
for (int i = 0; i < 29; i += 2)
|
||||
{
|
||||
if (i + 1 < 29)
|
||||
fmt::append(msg, "X%-2d: %016llX X%-2d: %016llX\n", i, pExp->ContextRecord->X[i], i + 1, pExp->ContextRecord->X[i + 1]);
|
||||
else
|
||||
fmt::append(msg, "X%-2d: %016llX\n", i, pExp->ContextRecord->X[i]);
|
||||
}
|
||||
fmt::append(msg, "SP: %016llX FP: %016llX LR: %016llX\n", pExp->ContextRecord->Sp, pExp->ContextRecord->Fp, pExp->ContextRecord->Lr);
|
||||
fmt::append(msg, "CPSR: %08X\n", pExp->ContextRecord->Cpsr);
|
||||
#endif
|
||||
|
||||
// TODO: print registers and the callstack
|
||||
const auto stack_trace = utils::get_backtrace(64, pExp->ContextRecord);
|
||||
const auto stack_symbols = utils::get_backtrace_symbols(stack_trace);
|
||||
|
||||
msg += "Stack Trace:\n";
|
||||
|
||||
for (const auto& symbol : stack_symbols)
|
||||
{
|
||||
fmt::append(msg, "%s\n", symbol);
|
||||
}
|
||||
|
||||
sys_log.fatal("\n%s", msg);
|
||||
logs::listener::sync_all();
|
||||
|
||||
|
|
@ -2852,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>
|
||||
|
|
|
|||
|
|
@ -30,42 +30,61 @@ namespace utils
|
|||
return out.data();
|
||||
}
|
||||
|
||||
std::vector<void*> get_backtrace(int max_depth)
|
||||
std::vector<void*> get_backtrace(int max_depth, PCONTEXT ctx)
|
||||
{
|
||||
static struct sym_initer_t
|
||||
{
|
||||
sym_initer_t() noexcept
|
||||
{
|
||||
SymInitialize(GetCurrentProcess(), NULL, TRUE);
|
||||
}
|
||||
~sym_initer_t() noexcept
|
||||
{
|
||||
SymCleanup(GetCurrentProcess());
|
||||
}
|
||||
} s_initer{};
|
||||
|
||||
std::vector<void*> result = {};
|
||||
|
||||
const auto hProcess = ::GetCurrentProcess();
|
||||
const auto hThread = ::GetCurrentThread();
|
||||
|
||||
CONTEXT context{};
|
||||
RtlCaptureContext(&context);
|
||||
if (ctx)
|
||||
context = *ctx;
|
||||
else
|
||||
RtlCaptureContext(&context);
|
||||
|
||||
STACKFRAME64 stack = {};
|
||||
stack.AddrPC.Mode = AddrModeFlat;
|
||||
stack.AddrStack.Mode = AddrModeFlat;
|
||||
stack.AddrFrame.Mode = AddrModeFlat;
|
||||
#if defined(ARCH_X64)
|
||||
const DWORD machineType = IMAGE_FILE_MACHINE_AMD64;
|
||||
stack.AddrPC.Offset = context.Rip;
|
||||
stack.AddrStack.Offset = context.Rsp;
|
||||
stack.AddrFrame.Offset = context.Rbp;
|
||||
#elif defined(ARCH_ARM64)
|
||||
const DWORD machineType = IMAGE_FILE_MACHINE_ARM64;
|
||||
stack.AddrPC.Offset = context.Pc;
|
||||
stack.AddrStack.Offset = context.Sp;
|
||||
stack.AddrFrame.Offset = context.Fp;
|
||||
#else
|
||||
#error "Unsupported architecture"
|
||||
#endif
|
||||
|
||||
while (max_depth--)
|
||||
{
|
||||
if (!StackWalk64(
|
||||
IMAGE_FILE_MACHINE_AMD64,
|
||||
hProcess,
|
||||
hThread,
|
||||
&stack,
|
||||
&context,
|
||||
NULL,
|
||||
SymFunctionTableAccess64,
|
||||
SymGetModuleBase64,
|
||||
NULL))
|
||||
machineType,
|
||||
hProcess,
|
||||
hThread,
|
||||
&stack,
|
||||
&context,
|
||||
NULL,
|
||||
SymFunctionTableAccess64,
|
||||
SymGetModuleBase64,
|
||||
NULL))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
#include <util/types.hpp>
|
||||
#include <util/logs.hpp>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
namespace utils
|
||||
{
|
||||
namespace stack_trace
|
||||
|
|
@ -30,7 +35,12 @@ namespace utils
|
|||
};
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
std::vector<void*> get_backtrace(int max_depth = 255, PCONTEXT ctx = nullptr);
|
||||
#else
|
||||
std::vector<void*> get_backtrace(int max_depth = 255);
|
||||
#endif
|
||||
|
||||
std::vector<std::string> get_backtrace_symbols(const std::vector<void*>& stack);
|
||||
|
||||
FORCE_INLINE void print_trace(stack_trace::Logger auto& logger, int max_depth = 255)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ target_sources(rpcs3_emu PRIVATE
|
|||
../Loader/PUP.cpp
|
||||
../Loader/TAR.cpp
|
||||
../Loader/ISO.cpp
|
||||
../Loader/iso_cache.cpp
|
||||
../Loader/TROPUSR.cpp
|
||||
../Loader/TRP.cpp
|
||||
)
|
||||
|
|
@ -160,7 +161,7 @@ if(WIN32)
|
|||
Audio/XAudio2/xaudio2_enumerator.cpp
|
||||
)
|
||||
target_compile_definitions(rpcs3_emu PRIVATE UNICODE _UNICODE _WIN32_WINNT=0x0A00)
|
||||
target_link_libraries(rpcs3_emu PRIVATE pdh bcrypt)
|
||||
target_link_libraries(rpcs3_emu PRIVATE pdh bcrypt dbghelp)
|
||||
endif()
|
||||
|
||||
# Cell
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ void fmt_class_string<cpu_flag>::format(std::string& out, u64 arg)
|
|||
case cpu_flag::notify: return "ntf";
|
||||
case cpu_flag::yield: return "y";
|
||||
case cpu_flag::preempt: return "PREEMPT";
|
||||
case cpu_flag::req_exit: return "REQ-EXIT";
|
||||
case cpu_flag::dbg_global_pause: return "G-PAUSE";
|
||||
case cpu_flag::dbg_pause: return "PAUSE";
|
||||
case cpu_flag::dbg_step: return "STEP";
|
||||
|
|
@ -729,8 +730,14 @@ void cpu_thread::operator()()
|
|||
{
|
||||
if (_this)
|
||||
{
|
||||
sys_log.warning("CPU Thread '%s' terminated abnormally!", name);
|
||||
cleanup();
|
||||
|
||||
auto log_thread = named_thread("CPU Thread Cleanup Logger", [name = name]()
|
||||
{
|
||||
sys_log.warning("CPU Thread '%s' terminated abnormally!", name);
|
||||
});
|
||||
|
||||
log_thread();
|
||||
}
|
||||
}
|
||||
} cleanup;
|
||||
|
|
|
|||
|
|
@ -244,36 +244,48 @@ llvm::Value* cpu_translator::bitcast(llvm::Value* val, llvm::Type* type, std::so
|
|||
}
|
||||
}
|
||||
|
||||
for (auto it = source_val->use_begin(); it != source_val->use_end(); ++it)
|
||||
// Skip use iteration for values that don't have use lists
|
||||
#if LLVM_VERSION_MAJOR >= 21
|
||||
if (source_val->hasUseList())
|
||||
#endif
|
||||
{
|
||||
llvm::Value* it_val = *it;
|
||||
|
||||
if (!it_val)
|
||||
for (llvm::Value* it_val : source_val->uses())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
llvm::CastInst* bci = llvm::dyn_cast_or_null<llvm::CastInst>(it_val);
|
||||
|
||||
// Walk through bitcasts
|
||||
while (bci && bci->getOpcode() == llvm::Instruction::BitCast)
|
||||
{
|
||||
if (bci->getParent() != m_ir->GetInsertBlock())
|
||||
if (!it_val)
|
||||
{
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bci->getType() == type)
|
||||
{
|
||||
return bci;
|
||||
}
|
||||
llvm::CastInst* bci = llvm::dyn_cast_or_null<llvm::CastInst>(it_val);
|
||||
|
||||
if (bci->use_begin() == bci->use_end())
|
||||
// Walk through bitcasts
|
||||
while (bci && bci->getOpcode() == llvm::Instruction::BitCast)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (bci->getParent() != m_ir->GetInsertBlock())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bci = llvm::dyn_cast_or_null<llvm::CastInst>(*bci->use_begin());
|
||||
if (bci->getType() == type)
|
||||
{
|
||||
return bci;
|
||||
}
|
||||
|
||||
// Check if bci has use list before accessing use_begin()
|
||||
#if LLVM_VERSION_MAJOR >= 21
|
||||
if (!bci->hasUseList())
|
||||
{
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (bci->use_begin() == bci->use_end())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bci = llvm::dyn_cast_or_null<llvm::CastInst>(*bci->use_begin());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -553,14 +565,25 @@ void cpu_translator::erase_stores(llvm::ArrayRef<llvm::Value*> args)
|
|||
{
|
||||
for (auto v : args)
|
||||
{
|
||||
for (auto it = v->use_begin(); it != v->use_end(); ++it)
|
||||
// Skip use iteration for values that don't have use lists
|
||||
#if LLVM_VERSION_MAJOR >= 21
|
||||
if (!v->hasUseList())
|
||||
continue;
|
||||
#endif
|
||||
|
||||
for (llvm::Value* i : v->uses())
|
||||
{
|
||||
llvm::Value* i = *it;
|
||||
llvm::CastInst* bci = nullptr;
|
||||
|
||||
// Walk through bitcasts
|
||||
while (i && (bci = llvm::dyn_cast<llvm::CastInst>(i)) && bci->getOpcode() == llvm::Instruction::BitCast)
|
||||
{
|
||||
// Check if bci has use list before accessing use_begin()
|
||||
#if LLVM_VERSION_MAJOR >= 21
|
||||
if (!bci->hasUseList())
|
||||
break;
|
||||
#endif
|
||||
|
||||
i = *bci->use_begin();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -524,19 +524,11 @@ error_code cellVideoOutGetScreenSize(u32 videoOut, vm::ptr<f32> screenSize)
|
|||
{
|
||||
// Return Playstation 3D display value
|
||||
// Some games call this function when 3D is enabled
|
||||
*screenSize = 24.f;
|
||||
*screenSize = static_cast<f32>(g_cfg.video.screen_size.get());
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
// TODO: Use virtual screen size
|
||||
#ifdef _WIN32
|
||||
// HDC screen = GetDC(NULL);
|
||||
// float diagonal = roundf(sqrtf((powf(float(GetDeviceCaps(screen, HORZSIZE)), 2) + powf(float(GetDeviceCaps(screen, VERTSIZE)), 2))) * 0.0393f);
|
||||
#else
|
||||
// TODO: Linux implementation, without using wx
|
||||
// float diagonal = roundf(sqrtf((powf(wxGetDisplaySizeMM().GetWidth(), 2) + powf(wxGetDisplaySizeMM().GetHeight(), 2))) * 0.0393f);
|
||||
#endif
|
||||
|
||||
// Let's just return not set for now
|
||||
return CELL_VIDEO_OUT_ERROR_VALUE_IS_NOT_SET;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -5921,17 +5921,6 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
|||
}
|
||||
}
|
||||
|
||||
const auto prev_wi = wi - 1;
|
||||
if (prev_wi != umax && ::at32(reg_state_it, prev_wi).reduced_loop.active)
|
||||
{
|
||||
const auto reduced_loop = &::at32(reg_state_it, prev_wi).reduced_loop;
|
||||
|
||||
for (const auto& [reg_num, reg] : reduced_loop->regs)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (wi < reg_state_it.size())
|
||||
{
|
||||
wa = ::at32(reg_state_it, wi).pc;
|
||||
|
|
@ -6144,8 +6133,6 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
|||
}
|
||||
}
|
||||
|
||||
u32 reg_pos = SPU_LS_SIZE;
|
||||
|
||||
auto org = reduced_loop->get_reg(op_rt);
|
||||
|
||||
u32 reg_first = s_reg_max;
|
||||
|
|
@ -6491,7 +6478,6 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
|||
}
|
||||
|
||||
bool should_have_argument_dictator = false;
|
||||
bool should_have_argument_increment = false;
|
||||
bool cond_val_incr_before_cond = false;
|
||||
bool ends_with_comparison = false;
|
||||
|
||||
|
|
@ -6500,10 +6486,6 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
|||
switch (reg->mod1_type)
|
||||
{
|
||||
case spu_itype::A:
|
||||
{
|
||||
should_have_argument_increment = true;
|
||||
[[fallthrough]];
|
||||
}
|
||||
case spu_itype::AI:
|
||||
case spu_itype::AHI:
|
||||
{
|
||||
|
|
@ -6563,10 +6545,6 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
|||
switch (reg->mod2_type)
|
||||
{
|
||||
case spu_itype::A:
|
||||
{
|
||||
should_have_argument_increment = true;
|
||||
[[fallthrough]];
|
||||
}
|
||||
case spu_itype::AI:
|
||||
case spu_itype::AHI:
|
||||
{
|
||||
|
|
@ -6773,8 +6751,6 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
|||
break;
|
||||
}
|
||||
|
||||
u32 cond_val_incr = static_cast<s32>(reg_org->IMM);
|
||||
|
||||
if (reg_org->mod1_type == spu_itype::AI || reg_org->mod1_type == spu_itype::AHI)
|
||||
{
|
||||
reduced_loop->cond_val_incr_is_immediate = true;
|
||||
|
|
@ -6986,7 +6962,6 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
|||
|
||||
// The loop dictator is the register that is not the argument
|
||||
const u32 loop_arg_reg = reg_index == op_ra ? op_rb : op_ra;
|
||||
const u32 loop_dict_reg = reg_index == op_ra ? op_ra : op_rb;
|
||||
reduced_loop->cond_val_is_immediate = false;
|
||||
|
||||
if (found_loop_argument_for_dictator)
|
||||
|
|
@ -8639,8 +8614,6 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
|||
|
||||
if (inst_attr attr = m_inst_attrs[(loop_pc - entry_point) / 4]; attr == inst_attr::none)
|
||||
{
|
||||
const u64 hash = loop_pc / 4 + read_from_ptr<be_t<u64>>(func_hash.data());
|
||||
|
||||
add_pattern(inst_attr::reduced_loop, loop_pc - result.entry_point, 0, std::make_shared<reduced_loop_t>(pattern));
|
||||
|
||||
std::string regs = "{";
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ const extern spu_decoder<spu_iflag> g_spu_iflag;
|
|||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#pragma optimize("", off)
|
||||
#ifdef ARCH_ARM64
|
||||
#include "Emu/CPU/Backends/AArch64/AArch64JIT.h"
|
||||
#endif
|
||||
|
|
@ -2618,8 +2617,6 @@ public:
|
|||
{
|
||||
if (auto init_val = reduced_loop_init_regs[i])
|
||||
{
|
||||
llvm::Type* type = g_cfg.core.spu_xfloat_accuracy == xfloat_accuracy::accurate && bb.reg_maybe_xf[i] ? get_type<f64[4]>() : get_reg_type(i);
|
||||
|
||||
const auto _phi = m_ir->CreatePHI(init_val->getType(), 2, fmt::format("reduced_0x%05x_r%u", baddr, i));
|
||||
_phi->addIncoming(init_val, prev_insert_block);
|
||||
|
||||
|
|
|
|||
|
|
@ -540,13 +540,11 @@ public:
|
|||
{
|
||||
case spu_itype::XSBH:
|
||||
{
|
||||
const auto prev_type = modified == 1 ? mod1_type : mod2_type;
|
||||
is_ok &= mod1_type == spu_itype::CEQB || mod1_type == spu_itype::CEQBI || mod1_type == spu_itype::CGTB || mod1_type == spu_itype::CGTBI || mod1_type == spu_itype::CLGTB || mod1_type == spu_itype::CLGTBI;
|
||||
break;
|
||||
}
|
||||
case spu_itype::ANDI:
|
||||
{
|
||||
const auto prev_type = modified == 1 ? mod1_type : mod2_type;
|
||||
is_ok &= mod1_type == spu_itype::CEQB || mod1_type == spu_itype::CEQBI || mod1_type == spu_itype::CGTB || mod1_type == spu_itype::CGTBI || mod1_type == spu_itype::CLGTB || mod1_type == spu_itype::CLGTBI;
|
||||
is_ok &= (spu_opcode_t{imm}.si10 & 0xff) == 0xff;
|
||||
break;
|
||||
|
|
@ -579,6 +577,10 @@ public:
|
|||
IMM = spu_opcode_t{imm}.si10;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_ok)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,12 @@
|
|||
|
||||
#include "util/asm.hpp"
|
||||
|
||||
// Unaligned u128 alias
|
||||
union x128
|
||||
{
|
||||
u8 _u8[16];
|
||||
};
|
||||
|
||||
namespace utils
|
||||
{
|
||||
template <typename T, typename U>
|
||||
|
|
@ -520,14 +526,14 @@ struct copy_decoded_bc1_block
|
|||
|
||||
struct copy_decoded_bc2_block
|
||||
{
|
||||
static void copy_mipmap_level(std::span<u32> dst, std::span<const u128> src, u16 width_in_block, u32 row_count, u16 depth, u32 dst_pitch_in_block, u32 src_pitch_in_block)
|
||||
static void copy_mipmap_level(std::span<u32> dst, std::span<const x128> src, u16 width_in_block, u32 row_count, u16 depth, u32 dst_pitch_in_block, u32 src_pitch_in_block)
|
||||
{
|
||||
u32 src_offset = 0, dst_offset = 0, destinationPitch = dst_pitch_in_block * 4;
|
||||
for (u32 row = 0; row < row_count * depth; row++)
|
||||
{
|
||||
for (u32 col = 0; col < width_in_block; col++)
|
||||
{
|
||||
const u8* compressedBlock = reinterpret_cast<const u8*>(&src[src_offset + col]);
|
||||
const u8* compressedBlock = src[src_offset + col]._u8;
|
||||
u8* decompressedBlock = reinterpret_cast<u8*>(&dst[dst_offset + col * 4]);
|
||||
bcdec_bc2(compressedBlock, decompressedBlock, destinationPitch);
|
||||
}
|
||||
|
|
@ -540,14 +546,14 @@ struct copy_decoded_bc2_block
|
|||
|
||||
struct copy_decoded_bc3_block
|
||||
{
|
||||
static void copy_mipmap_level(std::span<u32> dst, std::span<const u128> src, u16 width_in_block, u32 row_count, u16 depth, u32 dst_pitch_in_block, u32 src_pitch_in_block)
|
||||
static void copy_mipmap_level(std::span<u32> dst, std::span<const x128> src, u16 width_in_block, u32 row_count, u16 depth, u32 dst_pitch_in_block, u32 src_pitch_in_block)
|
||||
{
|
||||
u32 src_offset = 0, dst_offset = 0, destinationPitch = dst_pitch_in_block * 4;
|
||||
for (u32 row = 0; row < row_count * depth; row++)
|
||||
{
|
||||
for (u32 col = 0; col < width_in_block; col++)
|
||||
{
|
||||
const u8* compressedBlock = reinterpret_cast<const u8*>(&src[src_offset + col]);
|
||||
const u8* compressedBlock = src[src_offset + col]._u8;
|
||||
u8* decompressedBlock = reinterpret_cast<u8*>(&dst[dst_offset + col * 4]);
|
||||
bcdec_bc3(compressedBlock, decompressedBlock, destinationPitch);
|
||||
}
|
||||
|
|
@ -1039,22 +1045,25 @@ namespace rsx
|
|||
// This is only supported using Nvidia OpenGL.
|
||||
// Remove the VTC tiling to support ATI and Vulkan.
|
||||
copy_unmodified_block_vtc::copy_mipmap_level(dst_buffer.as_span<u64>(), src_layout.data.as_span<const u64>(), w, h, depth, get_row_pitch_in_block<u64>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
break;
|
||||
}
|
||||
else if (is_3d && !is_po2 && caps.supports_vtc_decoding)
|
||||
|
||||
if (is_3d && !is_po2 && caps.supports_vtc_decoding)
|
||||
{
|
||||
// In this case, hardware expects us to feed it a VTC input, but on PS3 we only have a linear one.
|
||||
// We need to compress the 2D-planar DXT input into a VTC output
|
||||
copy_linear_block_to_vtc::copy_mipmap_level(dst_buffer.as_span<u64>(), src_layout.data.as_span<const u64>(), w, h, depth, get_row_pitch_in_block<u64>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
break;
|
||||
}
|
||||
else if (caps.supports_zero_copy)
|
||||
|
||||
if (caps.supports_zero_copy)
|
||||
{
|
||||
result.require_upload = true;
|
||||
result.deferred_cmds = build_transfer_cmds(src_layout.data.data(), 8, w, h, depth, 0, get_row_pitch_in_block<u64>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
copy_unmodified_block::copy_mipmap_level(dst_buffer.as_span<u64>(), src_layout.data.as_span<const u64>(), 1, w, h, depth, 0, get_row_pitch_in_block<u64>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
}
|
||||
|
||||
copy_unmodified_block::copy_mipmap_level(dst_buffer.as_span<u64>(), src_layout.data.as_span<const u64>(), 1, w, h, depth, 0, get_row_pitch_in_block<u64>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1062,7 +1071,7 @@ namespace rsx
|
|||
{
|
||||
if (!caps.supports_dxt)
|
||||
{
|
||||
copy_decoded_bc2_block::copy_mipmap_level(dst_buffer.as_span<u32>(), src_layout.data.as_span<const u128>(), w, h, depth, get_row_pitch_in_block<u32>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
copy_decoded_bc2_block::copy_mipmap_level(dst_buffer.as_span<u32>(), src_layout.data.as_span<const x128>(), w, h, depth, get_row_pitch_in_block<u32>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
|
|
@ -1071,7 +1080,7 @@ namespace rsx
|
|||
{
|
||||
if (!caps.supports_dxt)
|
||||
{
|
||||
copy_decoded_bc3_block::copy_mipmap_level(dst_buffer.as_span<u32>(), src_layout.data.as_span<const u128>(), w, h, depth, get_row_pitch_in_block<u32>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
copy_decoded_bc3_block::copy_mipmap_level(dst_buffer.as_span<u32>(), src_layout.data.as_span<const x128>(), w, h, depth, get_row_pitch_in_block<u32>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1083,23 +1092,44 @@ namespace rsx
|
|||
// PS3 uses the Nvidia VTC memory layout for compressed 3D textures.
|
||||
// This is only supported using Nvidia OpenGL.
|
||||
// Remove the VTC tiling to support ATI and Vulkan.
|
||||
copy_unmodified_block_vtc::copy_mipmap_level(dst_buffer.as_span<u128>(), src_layout.data.as_span<const u128>(), w, h, depth, get_row_pitch_in_block<u128>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
if (src_layout.data.is_naturally_aligned<u128>())
|
||||
{
|
||||
copy_unmodified_block_vtc::copy_mipmap_level(dst_buffer.as_span<u128>(), src_layout.data.as_span<const u128>(), w, h, depth, get_row_pitch_in_block<u128>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
break;
|
||||
}
|
||||
|
||||
copy_unmodified_block_vtc::copy_mipmap_level(dst_buffer.as_span<x128>(), src_layout.data.as_span<const x128>(), w, h, depth, get_row_pitch_in_block<u128>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
break;
|
||||
}
|
||||
else if (is_3d && !is_po2 && caps.supports_vtc_decoding)
|
||||
|
||||
if (is_3d && !is_po2 && caps.supports_vtc_decoding)
|
||||
{
|
||||
// In this case, hardware expects us to feed it a VTC input, but on PS3 we only have a linear one.
|
||||
// We need to compress the 2D-planar DXT input into a VTC output
|
||||
copy_linear_block_to_vtc::copy_mipmap_level(dst_buffer.as_span<u128>(), src_layout.data.as_span<const u128>(), w, h, depth, get_row_pitch_in_block<u128>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
if (src_layout.data.is_naturally_aligned<u128>())
|
||||
{
|
||||
copy_linear_block_to_vtc::copy_mipmap_level(dst_buffer.as_span<u128>(), src_layout.data.as_span<const u128>(), w, h, depth, get_row_pitch_in_block<u128>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
break;
|
||||
}
|
||||
|
||||
copy_linear_block_to_vtc::copy_mipmap_level(dst_buffer.as_span<x128>(), src_layout.data.as_span<const x128>(), w, h, depth, get_row_pitch_in_block<u128>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
break;
|
||||
}
|
||||
else if (caps.supports_zero_copy)
|
||||
|
||||
if (caps.supports_zero_copy)
|
||||
{
|
||||
result.require_upload = true;
|
||||
result.deferred_cmds = build_transfer_cmds(src_layout.data.data(), 16, w, h, depth, 0, get_row_pitch_in_block<u128>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
break;
|
||||
}
|
||||
else
|
||||
|
||||
if (src_layout.data.is_naturally_aligned<u128>())
|
||||
{
|
||||
copy_unmodified_block::copy_mipmap_level(dst_buffer.as_span<u128>(), src_layout.data.as_span<const u128>(), 1, w, h, depth, 0, get_row_pitch_in_block<u128>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
break;
|
||||
}
|
||||
|
||||
copy_unmodified_block::copy_mipmap_level(dst_buffer.as_span<x128>(), src_layout.data.as_span<const x128>(), 1, w, h, depth, 0, get_row_pitch_in_block<u128>(w, caps.alignment), src_layout.pitch_in_block);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
88
rpcs3/Emu/RSX/Common/aligned_malloc.hpp
Normal file
88
rpcs3/Emu/RSX/Common/aligned_malloc.hpp
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
namespace rsx
|
||||
{
|
||||
namespace aligned_allocator
|
||||
{
|
||||
template <size_t Align>
|
||||
requires (Align != 0) && ((Align& (Align - 1)) == 0)
|
||||
size_t align_up(size_t size)
|
||||
{
|
||||
return (size + (Align - 1)) & ~(Align - 1);
|
||||
}
|
||||
|
||||
template <size_t Align>
|
||||
requires (Align != 0) && ((Align& (Align - 1)) == 0)
|
||||
void* malloc(size_t size)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return _aligned_malloc(size, Align);
|
||||
#elif defined(__APPLE__)
|
||||
constexpr size_t NativeAlign = std::max(Align, sizeof(void*));
|
||||
return std::aligned_alloc(NativeAlign, align_up<NativeAlign>(size));
|
||||
#else
|
||||
return std::aligned_alloc(Align, align_up<Align>(size));
|
||||
#endif
|
||||
}
|
||||
|
||||
template <size_t Align>
|
||||
requires (Align != 0) && ((Align& (Align - 1)) == 0)
|
||||
void* realloc(void* prev_ptr, [[maybe_unused]] size_t prev_size, size_t new_size)
|
||||
{
|
||||
if (align_up<Align>(prev_size) >= new_size)
|
||||
{
|
||||
return prev_ptr;
|
||||
}
|
||||
|
||||
ensure(reinterpret_cast<usz>(prev_ptr) % Align == 0, "Pointer not aligned to Align");
|
||||
#if defined(_WIN32)
|
||||
return _aligned_realloc(prev_ptr, new_size, Align);
|
||||
#else
|
||||
#if defined(__APPLE__)
|
||||
constexpr size_t NativeAlign = std::max(Align, sizeof(void*));
|
||||
void* ret = std::aligned_alloc(NativeAlign, align_up<NativeAlign>(new_size));
|
||||
#else
|
||||
void* ret = std::aligned_alloc(Align, align_up<Align>(new_size));
|
||||
#endif
|
||||
std::memcpy(ret, prev_ptr, std::min(prev_size, new_size));
|
||||
std::free(prev_ptr);
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void free(void* ptr)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
_aligned_free(ptr);
|
||||
#else
|
||||
std::free(ptr);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, int Alignment = sizeof(T)>
|
||||
class aligned_pointer_t
|
||||
{
|
||||
public:
|
||||
aligned_pointer_t(size_t size)
|
||||
{
|
||||
m_ptr = aligned_allocator::malloc<Alignment>(size);
|
||||
}
|
||||
|
||||
virtual ~aligned_pointer_t()
|
||||
{
|
||||
aligned_allocator::free(m_ptr);
|
||||
}
|
||||
|
||||
T* data() const { return m_ptr; }
|
||||
|
||||
T& operator * () const { return *m_ptr; }
|
||||
|
||||
T* operator -> () const { return m_ptr; }
|
||||
|
||||
private:
|
||||
T* m_ptr;
|
||||
};
|
||||
}
|
||||
|
|
@ -81,10 +81,17 @@ namespace rsx
|
|||
std::span<T> as_span() const
|
||||
{
|
||||
auto bytes = data();
|
||||
ensure((reinterpret_cast<uintptr_t>(bytes) & (sizeof(T) - 1)) == 0, "IO buffer span cast requires naturally aligned pointers.");
|
||||
ensure(is_naturally_aligned<T>(), "IO buffer span cast requires naturally aligned pointers.");
|
||||
return { utils::bless<T>(bytes), m_size / sizeof(T) };
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool is_naturally_aligned() const
|
||||
{
|
||||
return ((reinterpret_cast<uintptr_t>(data()) & (alignof(T) - 1)) == 0) &&
|
||||
(m_size % sizeof(T)) == 0;
|
||||
}
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return m_size == 0;
|
||||
|
|
|
|||
|
|
@ -3,70 +3,12 @@
|
|||
#include <util/types.hpp>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "aligned_malloc.hpp"
|
||||
#include "reverse_ptr.hpp"
|
||||
|
||||
namespace rsx
|
||||
{
|
||||
namespace aligned_allocator
|
||||
{
|
||||
template <size_t Align>
|
||||
requires (Align != 0) && ((Align & (Align - 1)) == 0)
|
||||
size_t align_up(size_t size)
|
||||
{
|
||||
return (size + (Align - 1)) & ~(Align - 1);
|
||||
}
|
||||
|
||||
template <size_t Align>
|
||||
requires (Align != 0) && ((Align & (Align - 1)) == 0)
|
||||
void* malloc(size_t size)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return _aligned_malloc(size, Align);
|
||||
#elif defined(__APPLE__)
|
||||
constexpr size_t NativeAlign = std::max(Align, sizeof(void*));
|
||||
return std::aligned_alloc(NativeAlign, align_up<NativeAlign>(size));
|
||||
#else
|
||||
return std::aligned_alloc(Align, align_up<Align>(size));
|
||||
#endif
|
||||
}
|
||||
|
||||
template <size_t Align>
|
||||
requires (Align != 0) && ((Align & (Align - 1)) == 0)
|
||||
void* realloc(void* prev_ptr, [[maybe_unused]] size_t prev_size, size_t new_size)
|
||||
{
|
||||
if (align_up<Align>(prev_size) >= new_size)
|
||||
{
|
||||
return prev_ptr;
|
||||
}
|
||||
|
||||
ensure(reinterpret_cast<usz>(prev_ptr) % Align == 0, "Pointer not aligned to Align");
|
||||
#if defined(_WIN32)
|
||||
return _aligned_realloc(prev_ptr, new_size, Align);
|
||||
#else
|
||||
#if defined(__APPLE__)
|
||||
constexpr size_t NativeAlign = std::max(Align, sizeof(void*));
|
||||
void* ret = std::aligned_alloc(NativeAlign, align_up<NativeAlign>(new_size));
|
||||
#else
|
||||
void* ret = std::aligned_alloc(Align, align_up<Align>(new_size));
|
||||
#endif
|
||||
std::memcpy(ret, prev_ptr, std::min(prev_size, new_size));
|
||||
std::free(prev_ptr);
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void free(void* ptr)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
_aligned_free(ptr);
|
||||
#else
|
||||
std::free(ptr);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
template <typename C, typename T>
|
||||
concept span_like =
|
||||
requires(C& c) {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ namespace rsx
|
|||
u32 draw_command_barrier_mask = 0;
|
||||
|
||||
// Draw-time iterator to the draw_command_barriers struct
|
||||
mutable rsx::simple_array<barrier_t>::iterator current_barrier_it;
|
||||
mutable rsx::simple_array<barrier_t>::iterator current_barrier_it {};
|
||||
|
||||
// Subranges memory cache
|
||||
mutable rsx::simple_array<draw_range_t> subranges_store;
|
||||
|
|
|
|||
|
|
@ -2970,7 +2970,7 @@ namespace rsx
|
|||
|
||||
auto& cfg = g_fxo->get<gcm_config>();
|
||||
|
||||
std::unique_lock<shared_mutex> hle_lock;
|
||||
std::optional<std::unique_lock<shared_mutex>> hle_lock;
|
||||
|
||||
for (u32 i = 0; i < std::size(unmap_status); i++)
|
||||
{
|
||||
|
|
@ -3011,7 +3011,7 @@ namespace rsx
|
|||
|
||||
if (hle_lock)
|
||||
{
|
||||
hle_lock.unlock();
|
||||
hle_lock->unlock();
|
||||
}
|
||||
|
||||
// Pause RSX thread momentarily to handle unmapping
|
||||
|
|
|
|||
|
|
@ -70,6 +70,8 @@ LOG_CHANNEL(sys_log, "SYS");
|
|||
// Preallocate 32 MiB
|
||||
stx::manual_typemap<void, 0x20'00000, 128> g_fixed_typemap;
|
||||
|
||||
static constinit atomic_t<bool> s_emulator_available{false};
|
||||
|
||||
std::string g_cfg_defaults;
|
||||
|
||||
atomic_t<u64> g_watchdog_hold_ctr{0};
|
||||
|
|
@ -118,6 +120,21 @@ namespace rsx
|
|||
void set_native_ui_flip();
|
||||
}
|
||||
|
||||
Emulator::Emulator() noexcept
|
||||
{
|
||||
s_emulator_available = true;
|
||||
}
|
||||
|
||||
Emulator::~Emulator() noexcept
|
||||
{
|
||||
s_emulator_available = false;
|
||||
}
|
||||
|
||||
bool Emulator::IsAvailable() noexcept
|
||||
{
|
||||
return s_emulator_available.load();
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<game_boot_result>::format(std::string& out, u64 arg)
|
||||
{
|
||||
|
|
@ -142,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;
|
||||
});
|
||||
|
|
@ -156,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";
|
||||
|
|
@ -915,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::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)
|
||||
{
|
||||
|
|
@ -932,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)
|
||||
{
|
||||
|
|
@ -947,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)
|
||||
{
|
||||
|
|
@ -964,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))
|
||||
|
|
@ -1546,6 +1565,9 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||
sys_log.notice("Version: APP_VER=%s VERSION=%s", version_app, version_disc);
|
||||
|
||||
{
|
||||
// We add the database configuration if it is set, unless we are using a mode that specifically selects a different configuration.
|
||||
bool add_database_config = !m_db_config.empty() && (m_config_mode == cfg_mode::database_config || m_config_mode == cfg_mode::custom || m_config_mode == cfg_mode::continuous);
|
||||
|
||||
if (m_config_mode == cfg_mode::custom_selection || (m_config_mode == cfg_mode::continuous && !m_config_path.empty()))
|
||||
{
|
||||
if (fs::file cfg_file{ m_config_path })
|
||||
|
|
@ -1588,6 +1610,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;
|
||||
add_database_config = false; // A custom config exists. Do not add the database config.
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1596,6 +1619,21 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||
}
|
||||
}
|
||||
|
||||
if (add_database_config)
|
||||
{
|
||||
// Add database config
|
||||
sys_log.notice("Applying database config");
|
||||
|
||||
if (g_cfg.from_string(m_db_config))
|
||||
{
|
||||
g_cfg.name = "database_config";
|
||||
}
|
||||
else
|
||||
{
|
||||
sys_log.error("Failed to apply database config");
|
||||
}
|
||||
}
|
||||
|
||||
// Disable incompatible settings
|
||||
fixup_settings(&_psf);
|
||||
|
||||
|
|
@ -3325,6 +3363,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.clear();
|
||||
m_config_path.clear();
|
||||
m_config_mode = cfg_mode::custom;
|
||||
read_used_savestate_versions();
|
||||
m_savestate_extension_flags1 = {};
|
||||
m_emu_state_close_pending = false;
|
||||
m_precompilation_option = {};
|
||||
};
|
||||
|
||||
if (system_state old_state = m_state.fetch_op([](system_state& state)
|
||||
{
|
||||
if (state == system_state::stopping || state == system_state::stopped)
|
||||
|
|
@ -3343,21 +3400,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);
|
||||
|
|
@ -3405,7 +3449,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;
|
||||
|
||||
|
|
@ -3904,7 +3948,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();
|
||||
|
||||
|
|
@ -3950,20 +3994,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)
|
||||
{
|
||||
|
|
@ -4038,14 +4069,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)
|
||||
|
|
@ -145,6 +146,7 @@ class Emulator final
|
|||
|
||||
cfg_mode m_config_mode = cfg_mode::custom;
|
||||
std::string m_config_path;
|
||||
std::string m_db_config;
|
||||
std::string m_path;
|
||||
std::string m_path_old;
|
||||
std::string m_path_original;
|
||||
|
|
@ -201,8 +203,10 @@ public:
|
|||
static constexpr std::string_view game_id_boot_prefix = "%RPCS3_GAMEID%:";
|
||||
static constexpr std::string_view vfs_boot_prefix = "%RPCS3_VFS%:";
|
||||
|
||||
Emulator() noexcept = default;
|
||||
~Emulator() noexcept = default;
|
||||
Emulator() noexcept;
|
||||
~Emulator() noexcept;
|
||||
|
||||
static bool IsAvailable() noexcept;
|
||||
|
||||
void SetCallbacks(EmuCallbacks&& cb)
|
||||
{
|
||||
|
|
@ -364,6 +368,11 @@ public:
|
|||
return m_config_path;
|
||||
}
|
||||
|
||||
const std::string& GetUsedDatabaseConfig() const
|
||||
{
|
||||
return m_db_config;
|
||||
}
|
||||
|
||||
bool IsChildProcess() const
|
||||
{
|
||||
return m_config_mode == cfg_mode::continuous;
|
||||
|
|
@ -413,7 +422,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::string& db_config = "");
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -66,10 +66,10 @@ struct cfg_root : cfg::node
|
|||
cfg::_int<-64, 64> stub_ppu_traps{ this, "Stub PPU Traps", 0, true }; // Hack, skip PPU traps for rare cases where the trap is continueable (specify relative instructions to skip)
|
||||
cfg::_bool precise_spu_verification{ this, "Precise SPU Verification", false }; // Disables use of xorsum based spu verification if enabled.
|
||||
cfg::_bool ppu_llvm_nj_fixup{ this, "PPU LLVM Java Mode Handling", true }; // Partially respect current Java Mode for alti-vec ops by PPU LLVM
|
||||
cfg::_bool ppu_fix_vnan{ this, "PPU Vector NaN Handling", true }; // Accuracy. Partial.
|
||||
cfg::_bool use_accurate_dfma{ this, "Use Accurate DFMA", true }; // Enable accurate double-precision FMA for CPUs which do not support it natively
|
||||
cfg::_bool ppu_set_sat_bit{ this, "PPU Set Saturation Bit", false }; // Accuracy. If unset, completely disable saturation flag handling.
|
||||
cfg::_bool ppu_use_nj_bit{ this, "PPU Accurate Non-Java Mode", false }; // Accuracy. If set, accurately emulate NJ flag. Implies NJ fixup.
|
||||
cfg::_bool ppu_fix_vnan{ this, "PPU Fixup Vector NaN Values", false }; // Accuracy. Partial.
|
||||
cfg::_bool ppu_set_vnan{ this, "PPU Accurate Vector NaN Values", false }; // Accuracy. Implies ppu_fix_vnan.
|
||||
cfg::_bool ppu_set_fpcc{ this, "PPU Set FPCC Bits", false }; // Accuracy.
|
||||
|
||||
|
|
@ -157,6 +157,7 @@ struct cfg_root : cfg::node
|
|||
cfg::_bool force_hw_MSAA_resolve{ this, "Force Hardware MSAA Resolve", false, true };
|
||||
cfg::_bool stereo_enabled{ this, "3D Display Enabled", false };
|
||||
cfg::_enum<stereo_render_mode_options> stereo_render_mode{ this, "3D Display Mode", stereo_render_mode_options::disabled, true };
|
||||
cfg::_int<10, 99> screen_size{ this, "Screen size in inches", 24, false };
|
||||
cfg::_bool debug_program_analyser{ this, "Debug Program Analyser", false };
|
||||
cfg::_bool precise_zpass_count{ this, "Accurate ZCULL stats", true };
|
||||
cfg::_int<1, 8> consecutive_frames_to_draw{ this, "Consecutive Frames To Draw", 1, true};
|
||||
|
|
|
|||
|
|
@ -201,8 +201,11 @@ basic_mouse_handler::mouse_button basic_mouse_handler::get_mouse_button(const cf
|
|||
|
||||
if (it != mouse_list.cend())
|
||||
{
|
||||
u32 btn = it->first;
|
||||
if (btn >= mouse::button) btn -= mouse::button;
|
||||
|
||||
return mouse_button{
|
||||
.code = static_cast<int>(it->first),
|
||||
.code = static_cast<int>(btn),
|
||||
.is_key = false
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -572,7 +572,7 @@ void keyboard_pad_handler::mousePressEvent(QMouseEvent* event)
|
|||
return;
|
||||
}
|
||||
|
||||
Key(event->button(), true);
|
||||
Key(mouse::button + static_cast<u32>(event->button()), true);
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
|
|
@ -583,7 +583,7 @@ void keyboard_pad_handler::mouseReleaseEvent(QMouseEvent* event)
|
|||
return;
|
||||
}
|
||||
|
||||
Key(event->button(), false, 0);
|
||||
Key(mouse::button + static_cast<u32>(event->button()), false, 0);
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
@ -902,13 +902,13 @@ std::string keyboard_pad_handler::GetKeyName(const u32& keyCode)
|
|||
return QKeySequence(keyCode).toString(QKeySequence::NativeText).toStdString();
|
||||
}
|
||||
|
||||
std::vector<std::set<u32>> keyboard_pad_handler::GetKeyCombos(const cfg::string& cfg_string)
|
||||
std::vector<std::set<u32>> keyboard_pad_handler::GetKeyCombos(const std::string& cfg_string)
|
||||
{
|
||||
std::vector<std::set<u32>> res;
|
||||
|
||||
for (const pad::combo& combo : cfg_pad::get_combos(cfg_string.to_string()))
|
||||
for (const pad::combo& combo : cfg_pad::get_combos(cfg_string))
|
||||
{
|
||||
std::set<u32> key_codes;
|
||||
std::set<u32> key_codes = find_key_codes(mouse_list, combo);
|
||||
|
||||
for (const std::string& button : combo.buttons())
|
||||
{
|
||||
|
|
@ -1062,8 +1062,7 @@ bool keyboard_pad_handler::bindPadToDevice(std::shared_ptr<Pad> pad)
|
|||
|
||||
const auto find_combos = [this](const cfg::string& name)
|
||||
{
|
||||
std::vector<std::set<u32>> combos = find_key_combos(mouse_list, name);
|
||||
for (const std::set<u32>& combo : GetKeyCombos(name)) combos.push_back(combo);
|
||||
const std::vector<std::set<u32>> combos = GetKeyCombos(name.to_string());
|
||||
|
||||
if (!combos.empty())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
enum mouse
|
||||
enum mouse : u32
|
||||
{
|
||||
move_left = 0x05555550,
|
||||
move_right = 0x05555551,
|
||||
|
|
@ -18,40 +18,41 @@ enum mouse
|
|||
wheel_up = 0x05555554,
|
||||
wheel_down = 0x05555555,
|
||||
wheel_left = 0x05555556,
|
||||
wheel_right = 0x05555557
|
||||
wheel_right = 0x05555557,
|
||||
button = 0x80000000
|
||||
};
|
||||
|
||||
// Unique button names for the config files and our pad settings dialog
|
||||
const std::unordered_map<u32, std::string> mouse_list =
|
||||
{
|
||||
{ Qt::NoButton , "" },
|
||||
{ Qt::LeftButton , "Mouse Left" },
|
||||
{ Qt::RightButton , "Mouse Right" },
|
||||
{ Qt::MiddleButton , "Mouse Middle" },
|
||||
{ Qt::BackButton , "Mouse Back" },
|
||||
{ Qt::ForwardButton , "Mouse Fwd" },
|
||||
{ Qt::TaskButton , "Mouse Task" },
|
||||
{ Qt::ExtraButton4 , "Mouse 4" },
|
||||
{ Qt::ExtraButton5 , "Mouse 5" },
|
||||
{ Qt::ExtraButton6 , "Mouse 6" },
|
||||
{ Qt::ExtraButton7 , "Mouse 7" },
|
||||
{ Qt::ExtraButton8 , "Mouse 8" },
|
||||
{ Qt::ExtraButton9 , "Mouse 9" },
|
||||
{ Qt::ExtraButton10 , "Mouse 10" },
|
||||
{ Qt::ExtraButton11 , "Mouse 11" },
|
||||
{ Qt::ExtraButton12 , "Mouse 12" },
|
||||
{ Qt::ExtraButton13 , "Mouse 13" },
|
||||
{ Qt::ExtraButton14 , "Mouse 14" },
|
||||
{ Qt::ExtraButton15 , "Mouse 15" },
|
||||
{ Qt::ExtraButton16 , "Mouse 16" },
|
||||
{ Qt::ExtraButton17 , "Mouse 17" },
|
||||
{ Qt::ExtraButton18 , "Mouse 18" },
|
||||
{ Qt::ExtraButton19 , "Mouse 19" },
|
||||
{ Qt::ExtraButton20 , "Mouse 20" },
|
||||
{ Qt::ExtraButton21 , "Mouse 21" },
|
||||
{ Qt::ExtraButton22 , "Mouse 22" },
|
||||
{ Qt::ExtraButton23 , "Mouse 23" },
|
||||
{ Qt::ExtraButton24 , "Mouse 24" },
|
||||
{ mouse::button + static_cast<u32>(Qt::LeftButton) , "Mouse Left" },
|
||||
{ mouse::button + static_cast<u32>(Qt::RightButton) , "Mouse Right" },
|
||||
{ mouse::button + static_cast<u32>(Qt::MiddleButton) , "Mouse Middle" },
|
||||
{ mouse::button + static_cast<u32>(Qt::BackButton) , "Mouse Back" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ForwardButton) , "Mouse Fwd" },
|
||||
{ mouse::button + static_cast<u32>(Qt::TaskButton) , "Mouse Task" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton4) , "Mouse 4" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton5) , "Mouse 5" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton6) , "Mouse 6" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton7) , "Mouse 7" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton8) , "Mouse 8" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton9) , "Mouse 9" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton10) , "Mouse 10" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton11) , "Mouse 11" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton12) , "Mouse 12" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton13) , "Mouse 13" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton14) , "Mouse 14" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton15) , "Mouse 15" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton16) , "Mouse 16" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton17) , "Mouse 17" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton18) , "Mouse 18" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton19) , "Mouse 19" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton20) , "Mouse 20" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton21) , "Mouse 21" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton22) , "Mouse 22" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton23) , "Mouse 23" },
|
||||
{ mouse::button + static_cast<u32>(Qt::ExtraButton24) , "Mouse 24" },
|
||||
|
||||
{ mouse::move_left , "Mouse MLeft" },
|
||||
{ mouse::move_right , "Mouse MRight" },
|
||||
|
|
@ -93,7 +94,7 @@ public:
|
|||
static QStringList GetKeyNames(const QKeyEvent* keyEvent);
|
||||
static std::string GetKeyName(const QKeyEvent* keyEvent, bool with_modifiers);
|
||||
static std::string GetKeyName(const u32& keyCode);
|
||||
static std::vector<std::set<u32>> GetKeyCombos(const cfg::string& cfg_string);
|
||||
static std::vector<std::set<u32>> GetKeyCombos(const std::string& cfg_string);
|
||||
static u32 GetKeyCode(const QString& keyName);
|
||||
|
||||
static int native_scan_code_from_string(const std::string& key);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "ISO.h"
|
||||
#include "Emu/VFS.h"
|
||||
#include "Crypto/utils.h"
|
||||
|
||||
#include <codecvt>
|
||||
#include <algorithm>
|
||||
|
|
@ -10,6 +11,20 @@
|
|||
#include <stack>
|
||||
|
||||
LOG_CHANNEL(sys_log, "SYS");
|
||||
LOG_CHANNEL(iso_log, "ISO");
|
||||
|
||||
constexpr u64 ISO_SECTOR_SIZE = 2048;
|
||||
|
||||
struct iso_sector
|
||||
{
|
||||
u64 lba_address;
|
||||
u64 offset;
|
||||
u64 size;
|
||||
u64 address_aligned;
|
||||
u64 offset_aligned;
|
||||
u64 size_aligned;
|
||||
std::array<u8, ISO_SECTOR_SIZE> buf;
|
||||
};
|
||||
|
||||
bool is_file_iso(const std::string& path)
|
||||
{
|
||||
|
|
@ -34,65 +49,435 @@ bool is_file_iso(const fs::file& file)
|
|||
&& magic[4] == '1';
|
||||
}
|
||||
|
||||
const int ISO_BLOCK_SIZE = 2048;
|
||||
|
||||
template<typename T>
|
||||
inline T read_both_endian_int(fs::file& file)
|
||||
// Convert 4 bytes in big-endian format to an unsigned integer
|
||||
static u32 char_arr_BE_to_uint(const u8* arr)
|
||||
{
|
||||
T out;
|
||||
return arr[0] << 24 | arr[1] << 16 | arr[2] << 8 | arr[3];
|
||||
}
|
||||
|
||||
if (std::endian::little == std::endian::native)
|
||||
// Reset the iv to a particular LBA
|
||||
static void reset_iv(std::array<u8, 16>& iv, u32 lba)
|
||||
{
|
||||
memset(iv.data(), 0, 12);
|
||||
|
||||
iv[12] = (lba & 0xFF000000) >> 24;
|
||||
iv[13] = (lba & 0x00FF0000) >> 16;
|
||||
iv[14] = (lba & 0x0000FF00) >> 8;
|
||||
iv[15] = (lba & 0x000000FF) >> 0;
|
||||
}
|
||||
|
||||
// Main function that will decrypt the sector(s)
|
||||
static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u64 size)
|
||||
{
|
||||
// The following preliminary checks are good to be provided.
|
||||
// Commented out to gain a bit of performance, just because we know the caller is providing values in the expected range
|
||||
|
||||
//if (size == 0)
|
||||
//{
|
||||
// return false;
|
||||
//}
|
||||
|
||||
//if ((size % 16) != 0)
|
||||
//{
|
||||
// sys_log.error("decrypt_data(): Requested ciphertext blocks' size must be a multiple of 16 (%ull)", size);
|
||||
// return;
|
||||
//}
|
||||
|
||||
u32 cur_sector_lba = static_cast<u32>(offset / ISO_SECTOR_SIZE); // First sector's LBA
|
||||
const u32 sector_count = static_cast<u32>((offset + size - 1) / ISO_SECTOR_SIZE) - cur_sector_lba + 1;
|
||||
const u64 sector_offset = offset % ISO_SECTOR_SIZE;
|
||||
|
||||
std::array<u8, 16> iv;
|
||||
u64 cur_offset;
|
||||
u64 cur_size;
|
||||
|
||||
// If the offset is not at the beginning of a sector, the first 16 bytes in the buffer
|
||||
// represents the IV for decrypting the next data in the buffer.
|
||||
// Otherwise, the IV is based on sector's LBA
|
||||
if (sector_offset != 0)
|
||||
{
|
||||
out = file.read<T>();
|
||||
file.seek(sizeof(T), fs::seek_cur);
|
||||
memcpy(iv.data(), buffer, 16);
|
||||
cur_offset = 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
file.seek(sizeof(T), fs::seek_cur);
|
||||
out = file.read<T>();
|
||||
reset_iv(iv, cur_sector_lba);
|
||||
cur_offset = 0;
|
||||
}
|
||||
|
||||
cur_size = sector_offset + size <= ISO_SECTOR_SIZE ? size : ISO_SECTOR_SIZE - sector_offset;
|
||||
cur_size -= cur_offset;
|
||||
|
||||
// Partial (or even full) first sector
|
||||
if (aes_crypt_cbc(&aes, AES_DECRYPT, cur_size, iv.data(), &buffer[cur_offset], &buffer[cur_offset]) != 0)
|
||||
{
|
||||
iso_log.error("decrypt_data: Error decrypting data on first sector read");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sector_count < 2) // If no more sector(s)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
cur_offset += cur_size;
|
||||
|
||||
const u32 inner_sector_count = sector_count > 2 ? sector_count - 2 : 0; // Remove first and last sector
|
||||
|
||||
// Inner sector(s), if any
|
||||
for (u32 i = 0; i < inner_sector_count; i++)
|
||||
{
|
||||
reset_iv(iv, ++cur_sector_lba); // Next sector's IV
|
||||
|
||||
if (aes_crypt_cbc(&aes, AES_DECRYPT, ISO_SECTOR_SIZE, iv.data(), &buffer[cur_offset], &buffer[cur_offset]) != 0)
|
||||
{
|
||||
iso_log.error("decrypt_data: Error decrypting data on inner sector(s) read");
|
||||
return false;
|
||||
}
|
||||
|
||||
cur_offset += ISO_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
reset_iv(iv, ++cur_sector_lba); // Next sector's IV
|
||||
|
||||
// Partial (or even full) last sector
|
||||
if (aes_crypt_cbc(&aes, AES_DECRYPT, size - cur_offset, iv.data(), &buffer[cur_offset], &buffer[cur_offset]) != 0)
|
||||
{
|
||||
iso_log.error("decrypt_data: Error decrypting data on last sector read");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void iso_file_decryption::reset()
|
||||
{
|
||||
m_enc_type = iso_encryption_type::NONE;
|
||||
m_region_info.clear();
|
||||
}
|
||||
|
||||
bool iso_file_decryption::init(const std::string& path)
|
||||
{
|
||||
reset();
|
||||
|
||||
if (!is_file_iso(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Store the ISO region information (needed by both the "Redump" type (only on "decrypt()" method) and "3k3y" type)
|
||||
//
|
||||
|
||||
fs::file iso_file(path);
|
||||
|
||||
if (!iso_file)
|
||||
{
|
||||
iso_log.error("init: Failed to open file: %s", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<u8, ISO_SECTOR_SIZE * 2> sec0_sec1 {};
|
||||
|
||||
if (iso_file.size() < sec0_sec1.size())
|
||||
{
|
||||
iso_log.error("init: Found only %ull sector(s) (minimum required is 2): %s", iso_file.size(), path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (iso_file.read(sec0_sec1.data(), sec0_sec1.size()) != sec0_sec1.size())
|
||||
{
|
||||
iso_log.error("init: Failed to read file: %s", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE:
|
||||
//
|
||||
// Following checks and assigned values are based on PS3 ISO specification.
|
||||
// E.g. all even regions (0, 2, 4 etc.) are always unencrypted while the odd ones are encrypted
|
||||
|
||||
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
|
||||
{
|
||||
iso_log.error("init: Failed to read region information: '%s' (region_count=%d)", path, region_count);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_region_info.resize(region_count * 2 - 1);
|
||||
|
||||
for (size_t i = 0; i < m_region_info.size(); ++i)
|
||||
{
|
||||
// Store the region information in address format
|
||||
m_region_info[i].encrypted = (i % 2 == 1);
|
||||
m_region_info[i].region_first_addr = (i == 0 ? 0ULL : m_region_info[i - 1].region_last_addr + 1ULL);
|
||||
m_region_info[i].region_last_addr = (static_cast<u64>(char_arr_BE_to_uint(sec0_sec1.data() + 12 + (i * 4)))
|
||||
- (i % 2 == 1 ? 1ULL : 0ULL)) * ISO_SECTOR_SIZE + ISO_SECTOR_SIZE - 1ULL;
|
||||
}
|
||||
|
||||
//
|
||||
// Check for Redump type
|
||||
//
|
||||
|
||||
const usz ext_pos = path.rfind('.');
|
||||
std::string key_path;
|
||||
|
||||
// If no file extension is provided, set "key_path" appending ".dkey" to "path".
|
||||
// Otherwise, replace the extension (e.g. ".iso") with ".dkey"
|
||||
key_path = ext_pos == umax ? path + ".dkey" : path.substr(0, ext_pos) + ".dkey";
|
||||
|
||||
fs::file key_file(key_path);
|
||||
|
||||
// If no ".dkey" file exists, try with ".key"
|
||||
if (!key_file)
|
||||
{
|
||||
key_path = ext_pos == umax ? path + ".key" : path.substr(0, ext_pos) + ".key";
|
||||
key_file = fs::file(key_path);
|
||||
}
|
||||
|
||||
// Check if "key_path" exists and create the "m_aes_dec" context if so
|
||||
if (key_file)
|
||||
{
|
||||
char key_str[32];
|
||||
unsigned char key[16];
|
||||
|
||||
const u64 key_len = key_file.read(key_str, sizeof(key_str));
|
||||
|
||||
if (key_len == sizeof(key_str) || key_len == sizeof(key))
|
||||
{
|
||||
// If the key read from the key file is 16 bytes long instead of 32, consider the file as
|
||||
// binary (".key") and so not needing any further conversion from hex string to bytes
|
||||
if (key_len == sizeof(key))
|
||||
{
|
||||
memcpy(key, key_str, sizeof(key));
|
||||
}
|
||||
else
|
||||
{
|
||||
hex_to_bytes(key, std::string_view(key_str, key_len), static_cast<unsigned int>(key_len));
|
||||
}
|
||||
|
||||
if (aes_setkey_dec(&m_aes_dec, key, 128) == 0)
|
||||
{
|
||||
m_enc_type = iso_encryption_type::REDUMP; // SET ENCRYPTION TYPE: REDUMP
|
||||
}
|
||||
}
|
||||
|
||||
if (m_enc_type == iso_encryption_type::NONE) // If encryption type was not set to REDUMP for any reason
|
||||
{
|
||||
iso_log.error("init: Failed to process key file: %s", key_path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
iso_log.warning("init: Failed to open, or missing, key file: %s", key_path);
|
||||
}
|
||||
|
||||
//
|
||||
// Check for 3k3y type
|
||||
//
|
||||
|
||||
// If encryption type is still set to NONE
|
||||
if (m_enc_type == iso_encryption_type::NONE)
|
||||
{
|
||||
// The 3k3y watermarks located at offset 0xF70: (D|E)ncrypted 3K BLD
|
||||
static const unsigned char k3k3y_enc_watermark[16] =
|
||||
{0x45, 0x6E, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x33, 0x4B, 0x20, 0x42, 0x4C, 0x44};
|
||||
static const unsigned char k3k3y_dec_watermark[16] =
|
||||
{0x44, 0x6E, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x33, 0x4B, 0x20, 0x42, 0x4C, 0x44};
|
||||
|
||||
if (memcmp(&k3k3y_enc_watermark[0], &sec0_sec1[0xF70], sizeof(k3k3y_enc_watermark)) == 0)
|
||||
{
|
||||
// Grab D1 from the 3k3y sector
|
||||
unsigned char key[16];
|
||||
|
||||
memcpy(key, &sec0_sec1[0xF80], 0x10);
|
||||
|
||||
// Convert D1 to KEY and generate the "m_aes_dec" context
|
||||
unsigned char key_d1[] = {0x38, 11, 0xcf, 11, 0x53, 0x45, 0x5b, 60, 120, 0x17, 0xab, 0x4f, 0xa3, 0xba, 0x90, 0xed};
|
||||
unsigned char iv_d1[] = {0x69, 0x47, 0x47, 0x72, 0xaf, 0x6f, 0xda, 0xb3, 0x42, 0x74, 0x3a, 0xef, 170, 0x18, 0x62, 0x87};
|
||||
|
||||
aes_context aes_d1;
|
||||
|
||||
if (aes_setkey_enc(&aes_d1, key_d1, 128) == 0)
|
||||
{
|
||||
if (aes_crypt_cbc(&aes_d1, AES_ENCRYPT, 16, &iv_d1[0], key, key) == 0)
|
||||
{
|
||||
if (aes_setkey_dec(&m_aes_dec, key, 128) == 0)
|
||||
{
|
||||
m_enc_type = iso_encryption_type::ENC_3K3Y; // SET ENCRYPTION TYPE: ENC_3K3Y
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_enc_type == iso_encryption_type::NONE) // If encryption type was not set to ENC_3K3Y for any reason
|
||||
{
|
||||
iso_log.error("init: Failed to set encryption type to ENC_3K3Y: %s", path);
|
||||
}
|
||||
}
|
||||
else if (memcmp(&k3k3y_dec_watermark[0], &sec0_sec1[0xF70], sizeof(k3k3y_dec_watermark)) == 0)
|
||||
{
|
||||
m_enc_type = iso_encryption_type::DEC_3K3Y; // SET ENCRYPTION TYPE: DEC_3K3Y
|
||||
}
|
||||
}
|
||||
|
||||
switch (m_enc_type)
|
||||
{
|
||||
case iso_encryption_type::REDUMP:
|
||||
iso_log.warning("init: Set 'enc type': REDUMP, 'reg count': %u: %s", m_region_info.size(), path);
|
||||
break;
|
||||
case iso_encryption_type::ENC_3K3Y:
|
||||
iso_log.warning("init: Set 'enc type': ENC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path);
|
||||
break;
|
||||
case iso_encryption_type::DEC_3K3Y:
|
||||
iso_log.warning("init: Set 'enc type': DEC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path);
|
||||
break;
|
||||
case iso_encryption_type::NONE: // If encryption type was not set for any reason
|
||||
iso_log.warning("init: Set 'enc type': NONE, 'reg count': %u: %s", m_region_info.size(), path);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std::string& name)
|
||||
{
|
||||
// If it's a non-encrypted type, nothing more to do
|
||||
if (m_enc_type == iso_encryption_type::NONE)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If it's a 3k3y ISO and data at offset 0xF70 is being requested, we should null it out
|
||||
if (m_enc_type == iso_encryption_type::DEC_3K3Y || m_enc_type == iso_encryption_type::ENC_3K3Y)
|
||||
{
|
||||
if (offset + size >= 0xF70ULL && offset <= 0x1070ULL)
|
||||
{
|
||||
// Zero out the 0xF70 - 0x1070 overlap
|
||||
unsigned char* buf = reinterpret_cast<unsigned char*>(buffer);
|
||||
unsigned char* buf_overlap_start = offset < 0xF70ULL ? buf + 0xF70ULL - offset : buf;
|
||||
|
||||
memset(buf_overlap_start, 0x00, offset + size < 0x1070ULL ? size - (buf_overlap_start - buf) : 0x100ULL - (buf_overlap_start - buf));
|
||||
}
|
||||
|
||||
// If it's a decrypted ISO then return, otherwise go on to the decryption logic
|
||||
if (m_enc_type == iso_encryption_type::DEC_3K3Y)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If it's an encrypted type, check if the request lies in an encrypted range
|
||||
for (const iso_region_info& info : m_region_info)
|
||||
{
|
||||
if (offset >= info.region_first_addr && offset <= info.region_last_addr)
|
||||
{
|
||||
// We found the region, decrypt if needed
|
||||
if (!info.encrypted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Decrypt the region before sending it back
|
||||
decrypt_data(m_aes_dec, offset, reinterpret_cast<unsigned char*>(buffer), size);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
iso_log.error("decrypt: %s: LBA request wasn't in the 'm_region_info' for an encrypted ISO? - RP: 0x%lx, RC: 0x%lx, LR: (0x%016lx - 0x%016lx)",
|
||||
name,
|
||||
offset,
|
||||
static_cast<unsigned long int>(m_region_info.size()),
|
||||
static_cast<unsigned long int>(!m_region_info.empty() ? m_region_info.back().region_first_addr : 0),
|
||||
static_cast<unsigned long int>(!m_region_info.empty() ? m_region_info.back().region_last_addr : 0));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline T retrieve_endian_int(const u8* buf)
|
||||
{
|
||||
T out {};
|
||||
|
||||
if constexpr (std::endian::little == std::endian::native)
|
||||
{
|
||||
// first half = little-endian copy
|
||||
std::memcpy(&out, buf, sizeof(T));
|
||||
}
|
||||
else
|
||||
{
|
||||
// second half = big-endian copy
|
||||
std::memcpy(&out, buf + sizeof(T), sizeof(T));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// assumed that directory_entry is at file head
|
||||
std::optional<iso_fs_metadata> iso_read_directory_entry(fs::file& file, bool names_in_ucs2 = false)
|
||||
// Assumed that directory entry is at file head
|
||||
static std::optional<iso_fs_metadata> iso_read_directory_entry(fs::file& entry, bool names_in_ucs2 = false)
|
||||
{
|
||||
const auto start_pos = file.pos();
|
||||
const u8 entry_length = file.read<u8>();
|
||||
const auto start_pos = entry.pos();
|
||||
const u8 entry_length = entry.read<u8>();
|
||||
|
||||
if (entry_length == 0) return std::nullopt;
|
||||
if (entry_length == 0)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
file.seek(1, fs::seek_cur);
|
||||
const u32 start_sector = read_both_endian_int<u32>(file);
|
||||
const u32 file_size = read_both_endian_int<u32>(file);
|
||||
// Batch this set of file reads. This reduces overall time spent in iso_read_directory_entry by ~41%
|
||||
#pragma pack(push, 1)
|
||||
struct iso_entry_header
|
||||
{
|
||||
//u8 entry_length; // Handled separately
|
||||
u8 extended_attribute_length;
|
||||
u8 start_sector[8];
|
||||
u8 file_size[8];
|
||||
u8 year;
|
||||
u8 month;
|
||||
u8 day;
|
||||
u8 hour;
|
||||
u8 minute;
|
||||
u8 second;
|
||||
u8 timezone_value;
|
||||
u8 flags;
|
||||
u8 file_unit_size;
|
||||
u8 interleave;
|
||||
u8 volume_sequence_number[4];
|
||||
u8 file_name_length;
|
||||
//u8 file_name[file_name_length]; // Handled separately
|
||||
};
|
||||
#pragma pack(pop)
|
||||
static_assert(sizeof(iso_entry_header) == 32);
|
||||
|
||||
const iso_entry_header header = entry.read<iso_entry_header>();
|
||||
|
||||
const u32 start_sector = retrieve_endian_int<u32>(header.start_sector);
|
||||
const u32 file_size = retrieve_endian_int<u32>(header.file_size);
|
||||
|
||||
std::tm file_date = {};
|
||||
file_date.tm_year = file.read<u8>();
|
||||
file_date.tm_mon = file.read<u8>() - 1;
|
||||
file_date.tm_mday = file.read<u8>();
|
||||
file_date.tm_hour = file.read<u8>();
|
||||
file_date.tm_min = file.read<u8>();
|
||||
file_date.tm_sec = file.read<u8>();
|
||||
const s16 timezone_value = file.read<u8>();
|
||||
|
||||
file_date.tm_year = header.year;
|
||||
file_date.tm_mon = header.month - 1;
|
||||
file_date.tm_mday = header.day;
|
||||
file_date.tm_hour = header.hour;
|
||||
file_date.tm_min = header.minute;
|
||||
file_date.tm_sec = header.second;
|
||||
|
||||
const s16 timezone_value = header.timezone_value;
|
||||
const s16 timezone_offset = (timezone_value - 50) * 15 * 60;
|
||||
|
||||
const std::time_t date_time = std::mktime(&file_date) + timezone_offset;
|
||||
|
||||
const u8 flags = file.read<u8>();
|
||||
|
||||
// 2nd flag bit indicates whether a given fs node is a directory
|
||||
const bool is_directory = flags & 0b00000010;
|
||||
const bool has_more_extents = flags & 0b10000000;
|
||||
|
||||
file.seek(6, fs::seek_cur);
|
||||
|
||||
const u8 file_name_length = file.read<u8>();
|
||||
const bool is_directory = header.flags & 0b00000010;
|
||||
const bool has_more_extents = header.flags & 0b10000000;
|
||||
|
||||
std::string file_name;
|
||||
file.read(file_name, file_name_length);
|
||||
|
||||
if (file_name_length == 1 && file_name[0] == 0)
|
||||
entry.read(file_name, header.file_name_length);
|
||||
|
||||
if (header.file_name_length == 1 && file_name[0] == 0)
|
||||
{
|
||||
file_name = ".";
|
||||
}
|
||||
|
|
@ -100,14 +485,15 @@ std::optional<iso_fs_metadata> iso_read_directory_entry(fs::file& file, bool nam
|
|||
{
|
||||
file_name = "..";
|
||||
}
|
||||
else if (names_in_ucs2) // for strings in joliet descriptor
|
||||
else if (names_in_ucs2) // For strings in joliet descriptor
|
||||
{
|
||||
// characters are stored in big endian format.
|
||||
std::u16string utf16;
|
||||
utf16.resize(file_name_length / 2);
|
||||
|
||||
// Characters are stored in big endian format
|
||||
const u16* raw = reinterpret_cast<const u16*>(file_name.data());
|
||||
for (size_t i = 0; i < utf16.size(); ++i, raw++)
|
||||
std::u16string utf16;
|
||||
|
||||
utf16.resize(header.file_name_length / 2);
|
||||
|
||||
for (usz i = 0; i < utf16.size(); ++i, raw++)
|
||||
{
|
||||
utf16[i] = *reinterpret_cast<const be_t<u16>*>(raw);
|
||||
}
|
||||
|
|
@ -120,13 +506,13 @@ std::optional<iso_fs_metadata> iso_read_directory_entry(fs::file& file, bool nam
|
|||
file_name.erase(file_name.end() - 2, file_name.end());
|
||||
}
|
||||
|
||||
if (file_name_length > 1 && file_name.ends_with("."))
|
||||
if (header.file_name_length > 1 && file_name.ends_with("."))
|
||||
{
|
||||
file_name.pop_back();
|
||||
}
|
||||
|
||||
// skip the rest of the entry.
|
||||
file.seek(entry_length + start_pos);
|
||||
// Skip the rest of the entry
|
||||
entry.seek(entry_length + start_pos);
|
||||
|
||||
return iso_fs_metadata
|
||||
{
|
||||
|
|
@ -145,49 +531,58 @@ std::optional<iso_fs_metadata> iso_read_directory_entry(fs::file& file, bool nam
|
|||
};
|
||||
}
|
||||
|
||||
void iso_form_hierarchy(fs::file& file, iso_fs_node& node, bool use_ucs2_decoding = false, const std::string& parent_path = "")
|
||||
static void iso_form_hierarchy(fs::file& file, iso_fs_node& node, bool use_ucs2_decoding = false, const std::string& parent_path = "")
|
||||
{
|
||||
if (!node.metadata.is_directory) return;
|
||||
if (!node.metadata.is_directory)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<usz> multi_extent_node_indices;
|
||||
|
||||
// assuming the directory spans a single extent
|
||||
// Assuming the directory spans a single extent
|
||||
const auto& directory_extent = node.metadata.extents[0];
|
||||
const u64 end_pos = (directory_extent.start * ISO_SECTOR_SIZE) + directory_extent.size;
|
||||
|
||||
file.seek(directory_extent.start * ISO_BLOCK_SIZE);
|
||||
file.seek(directory_extent.start * ISO_SECTOR_SIZE);
|
||||
|
||||
const u64 end_pos = directory_extent.size + (directory_extent.start * ISO_BLOCK_SIZE);
|
||||
|
||||
while(file.pos() < end_pos)
|
||||
while (file.pos() < end_pos)
|
||||
{
|
||||
auto entry = iso_read_directory_entry(file, use_ucs2_decoding);
|
||||
|
||||
if (!entry)
|
||||
{
|
||||
const u64 new_sector = (file.pos() / ISO_BLOCK_SIZE) + 1;
|
||||
file.seek(new_sector * ISO_BLOCK_SIZE);
|
||||
const u64 new_sector = (file.pos() / ISO_SECTOR_SIZE) + 1;
|
||||
|
||||
file.seek(new_sector * ISO_SECTOR_SIZE);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool extent_added = false;
|
||||
|
||||
// find previous extent and merge into it, otherwise we push this node's index
|
||||
// Find previous extent and merge into it, otherwise we push this node's index
|
||||
for (usz index : multi_extent_node_indices)
|
||||
{
|
||||
auto& selected_node = ::at32(node.children, index);
|
||||
|
||||
if (selected_node->metadata.name == entry->name)
|
||||
{
|
||||
// merge into selected_node
|
||||
// Merge into selected_node
|
||||
selected_node->metadata.extents.push_back(entry->extents[0]);
|
||||
|
||||
extent_added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (extent_added) continue;
|
||||
if (extent_added)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry->has_multiple_extents)
|
||||
{
|
||||
// haven't pushed entry to node.children yet so node.children::size() == entry_index
|
||||
// Haven't pushed entry to node.children yet so node.children::size() == entry_index
|
||||
multi_extent_node_indices.push_back(node.children.size());
|
||||
}
|
||||
|
||||
|
|
@ -220,10 +615,11 @@ iso_archive::iso_archive(const std::string& path)
|
|||
{
|
||||
m_path = path;
|
||||
m_file = fs::file(path);
|
||||
m_dec = std::make_shared<iso_file_decryption>();
|
||||
|
||||
if (!is_file_iso(m_file))
|
||||
if (!m_dec->init(path))
|
||||
{
|
||||
// not iso... TODO: throw something??
|
||||
// Not ISO... TODO: throw something??
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -241,16 +637,21 @@ iso_archive::iso_archive(const std::string& path)
|
|||
{
|
||||
use_ucs2_decoding = descriptor_type == 2;
|
||||
|
||||
// skip the rest of descriptor's data
|
||||
// Skip the rest of descriptor's data
|
||||
m_file.seek(155, fs::seek_cur);
|
||||
|
||||
m_root = iso_fs_node
|
||||
const auto node = iso_read_directory_entry(m_file, use_ucs2_decoding);
|
||||
|
||||
if (node)
|
||||
{
|
||||
.metadata = iso_read_directory_entry(m_file, use_ucs2_decoding).value(),
|
||||
};
|
||||
m_root = iso_fs_node
|
||||
{
|
||||
.metadata = node.value()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
m_file.seek(descriptor_start + ISO_BLOCK_SIZE);
|
||||
m_file.seek(descriptor_start + ISO_SECTOR_SIZE);
|
||||
}
|
||||
while (descriptor_type != 255);
|
||||
|
||||
|
|
@ -264,8 +665,8 @@ iso_fs_node* iso_archive::retrieve(const std::string& passed_path)
|
|||
const std::string path = std::filesystem::path(passed_path).string();
|
||||
const std::string_view path_sv = path;
|
||||
|
||||
size_t start = 0;
|
||||
size_t end = path_sv.find_first_of(fs::delim);
|
||||
usz start = 0;
|
||||
usz end = path_sv.find_first_of(fs::delim);
|
||||
|
||||
std::stack<iso_fs_node*> search_stack;
|
||||
search_stack.push(&m_root);
|
||||
|
|
@ -334,23 +735,27 @@ bool iso_archive::is_file(const std::string& path)
|
|||
|
||||
iso_file iso_archive::open(const std::string& path)
|
||||
{
|
||||
return iso_file(fs::file(m_path), *ensure(retrieve(path)));
|
||||
return iso_file(fs::file(m_path), m_dec, *ensure(retrieve(path)));
|
||||
}
|
||||
|
||||
psf::registry iso_archive::open_psf(const std::string& path)
|
||||
{
|
||||
auto* archive_file = retrieve(path);
|
||||
if (!archive_file) return psf::registry();
|
||||
|
||||
const fs::file psf_file(std::make_unique<iso_file>(fs::file(m_path), *archive_file));
|
||||
if (!archive_file)
|
||||
{
|
||||
return psf::registry();
|
||||
}
|
||||
|
||||
const fs::file psf_file(std::make_unique<iso_file>(fs::file(m_path), m_dec, *archive_file));
|
||||
|
||||
return psf::load_object(psf_file, path);
|
||||
}
|
||||
|
||||
iso_file::iso_file(fs::file&& iso_handle, const iso_fs_node& node)
|
||||
: m_file(std::move(iso_handle)), m_meta(node.metadata)
|
||||
iso_file::iso_file(fs::file&& iso_handle, std::shared_ptr<iso_file_decryption> iso_dec, const iso_fs_node& node)
|
||||
: m_file(std::move(iso_handle)), m_dec(iso_dec), m_meta(node.metadata)
|
||||
{
|
||||
m_file.seek(ISO_BLOCK_SIZE * node.metadata.extents[0].start);
|
||||
m_file.seek(node.metadata.extents[0].start * ISO_SECTOR_SIZE);
|
||||
}
|
||||
|
||||
fs::stat_t iso_file::get_stat()
|
||||
|
|
@ -389,14 +794,6 @@ std::pair<u64, iso_extent_info> iso_file::get_extent_pos(u64 pos) const
|
|||
return {pos, *it};
|
||||
}
|
||||
|
||||
// assumed valid and in bounds.
|
||||
u64 iso_file::file_offset(u64 pos) const
|
||||
{
|
||||
const auto [local_pos, extent] = get_extent_pos(pos);
|
||||
|
||||
return (extent.start * ISO_BLOCK_SIZE) + local_pos;
|
||||
}
|
||||
|
||||
u64 iso_file::local_extent_remaining(u64 pos) const
|
||||
{
|
||||
const auto [local_pos, extent] = get_extent_pos(pos);
|
||||
|
|
@ -409,29 +806,157 @@ u64 iso_file::local_extent_size(u64 pos) const
|
|||
return get_extent_pos(pos).second.size;
|
||||
}
|
||||
|
||||
// Assumed valid and in bounds
|
||||
u64 iso_file::file_offset(u64 pos) const
|
||||
{
|
||||
const auto [local_pos, extent] = get_extent_pos(pos);
|
||||
|
||||
return (extent.start * ISO_SECTOR_SIZE) + local_pos;
|
||||
}
|
||||
|
||||
u64 iso_file::read(void* buffer, u64 size)
|
||||
{
|
||||
const auto r = read_at(m_pos, buffer, size);
|
||||
|
||||
m_pos += r;
|
||||
return r;
|
||||
}
|
||||
|
||||
u64 iso_file::read_at(u64 offset, void* buffer, u64 size)
|
||||
{
|
||||
const u64 local_remaining = local_extent_remaining(offset);
|
||||
const u64 max_size = std::min(size, local_extent_remaining(offset));
|
||||
|
||||
const u64 total_read = m_file.read_at(file_offset(offset), buffer, std::min(size, local_remaining));
|
||||
|
||||
const auto total_size = this->size();
|
||||
|
||||
if (size > total_read && (offset + total_read) < total_size)
|
||||
if (max_size == 0)
|
||||
{
|
||||
const u64 second_total_read = read_at(offset + total_read, reinterpret_cast<u8*>(buffer) + total_read, size - total_read);
|
||||
|
||||
return total_read + second_total_read;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return total_read;
|
||||
const u64 archive_first_offset = file_offset(offset);
|
||||
const u64 total_size = this->size();
|
||||
u64 total_read;
|
||||
|
||||
// If it's a non-encrypted type
|
||||
if (m_dec->get_enc_type() == iso_encryption_type::NONE)
|
||||
{
|
||||
total_read = m_file.read_at(archive_first_offset, buffer, max_size);
|
||||
|
||||
if (size > total_read && (offset + total_read) < total_size)
|
||||
{
|
||||
total_read += read_at(offset + total_read, reinterpret_cast<u8*>(buffer) + total_read, size - total_read);
|
||||
}
|
||||
|
||||
return total_read;
|
||||
}
|
||||
|
||||
// If it's an encrypted type
|
||||
|
||||
// IMPORTANT NOTE:
|
||||
//
|
||||
// "iso_file_decryption::decrypt()" method requires that offset and size are multiple of 16 bytes
|
||||
// (ciphertext block's size) and that a previous ciphertext block (used as IV) is read in case
|
||||
// offset is not a multiple of ISO_SECTOR_SIZE
|
||||
//
|
||||
// ----------------------------------------------------------------------
|
||||
// file on ISO archive: | ' ' |
|
||||
// ----------------------------------------------------------------------
|
||||
// ' '
|
||||
// ---------------------------------------------
|
||||
// buffer: | |
|
||||
// ---------------------------------------------
|
||||
// ' ' ' '
|
||||
// -------------------------------------------------------------------------------------------------------------------------------------
|
||||
// ISO archive: | sec 0 | sec 1 |xxxxx######'###########'###########'###########'##xxxxxxxxx| | ... | sec n-1 | sec n |
|
||||
// -------------------------------------------------------------------------------------------------------------------------------------
|
||||
// 16 Bytes x block read: | | | | | | | '#######'###########'###########'###########'###| | | | | | | | | | | | | | |
|
||||
// ' ' ' '
|
||||
// | first sec | inner sec(s) | last sec |
|
||||
|
||||
const u64 archive_last_offset = archive_first_offset + max_size - 1;
|
||||
iso_sector first_sec, last_sec;
|
||||
u64 offset_aligned;
|
||||
u64 offset_aligned_first_out;
|
||||
|
||||
first_sec.lba_address = (archive_first_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE;
|
||||
first_sec.offset = archive_first_offset % ISO_SECTOR_SIZE;
|
||||
first_sec.size = first_sec.offset + max_size <= ISO_SECTOR_SIZE ? max_size : ISO_SECTOR_SIZE - first_sec.offset;
|
||||
|
||||
last_sec.lba_address = last_sec.address_aligned = (archive_last_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE;
|
||||
//last_sec.offset = last_sec.offset_aligned = 0; // Always 0 so no need to set and use those attributes
|
||||
last_sec.size = (archive_last_offset % ISO_SECTOR_SIZE) + 1;
|
||||
|
||||
//
|
||||
// First sector
|
||||
//
|
||||
|
||||
offset_aligned = first_sec.offset & ~0xF;
|
||||
offset_aligned_first_out = (first_sec.offset + first_sec.size) & ~0xF;
|
||||
|
||||
first_sec.offset_aligned = offset_aligned != 0 ? offset_aligned - 16 : 0; // Eventually include the previous block (used as IV)
|
||||
first_sec.size_aligned = offset_aligned_first_out != (first_sec.offset + first_sec.size) ?
|
||||
offset_aligned_first_out + 16 - first_sec.offset_aligned :
|
||||
offset_aligned_first_out - first_sec.offset_aligned;
|
||||
first_sec.address_aligned = first_sec.lba_address + first_sec.offset_aligned;
|
||||
|
||||
total_read = m_file.read_at(first_sec.address_aligned, &first_sec.buf.data()[first_sec.offset_aligned], first_sec.size_aligned);
|
||||
|
||||
m_dec->decrypt(first_sec.address_aligned, &first_sec.buf.data()[first_sec.offset_aligned], first_sec.size_aligned, m_meta.name);
|
||||
memcpy(buffer, &first_sec.buf.data()[first_sec.offset], first_sec.size);
|
||||
|
||||
u64 sector_count = (last_sec.lba_address - first_sec.lba_address) / ISO_SECTOR_SIZE + 1;
|
||||
|
||||
if (sector_count < 2) // If no more sector(s)
|
||||
{
|
||||
if (total_read != first_sec.size_aligned)
|
||||
{
|
||||
iso_log.error("read_at: %s: Error reading from file", m_meta.name);
|
||||
|
||||
seek(m_pos, fs::seek_set);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return max_size;
|
||||
}
|
||||
|
||||
//
|
||||
// Inner sector(s), if any
|
||||
//
|
||||
|
||||
u64 expected_inner_sector_read = 0;
|
||||
|
||||
if (sector_count > 2) // If inner sector(s) are present
|
||||
{
|
||||
u64 inner_sector_size = expected_inner_sector_read = (sector_count - 2) * ISO_SECTOR_SIZE;
|
||||
|
||||
total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast<u8*>(buffer)[first_sec.size], inner_sector_size);
|
||||
|
||||
m_dec->decrypt(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast<u8*>(buffer)[first_sec.size], inner_sector_size, m_meta.name);
|
||||
}
|
||||
|
||||
//
|
||||
// Last sector
|
||||
//
|
||||
|
||||
offset_aligned_first_out = last_sec.size & ~0xF;
|
||||
last_sec.size_aligned = offset_aligned_first_out != last_sec.size ? offset_aligned_first_out + 16 : offset_aligned_first_out;
|
||||
|
||||
total_read += m_file.read_at(last_sec.address_aligned, last_sec.buf.data(), last_sec.size_aligned);
|
||||
|
||||
m_dec->decrypt(last_sec.address_aligned, last_sec.buf.data(), last_sec.size_aligned, m_meta.name);
|
||||
memcpy(&reinterpret_cast<u8*>(buffer)[max_size - last_sec.size], last_sec.buf.data(), last_sec.size);
|
||||
|
||||
//
|
||||
// As last, check for an unlikely reading error (decoding also failed due to use of partially initialized buffer)
|
||||
//
|
||||
|
||||
if (total_read != first_sec.size_aligned + last_sec.size_aligned + expected_inner_sector_read)
|
||||
{
|
||||
iso_log.error("read_at: %s: Error reading from file", m_meta.name);
|
||||
|
||||
seek(m_pos, fs::seek_set);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return max_size;
|
||||
}
|
||||
|
||||
u64 iso_file::write(const void* /*buffer*/, u64 /*size*/)
|
||||
|
|
@ -569,7 +1094,7 @@ std::unique_ptr<fs::file_base> iso_device::open(const std::string& path, bs_t<fs
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<iso_file>(fs::file(iso_path, mode), *node);
|
||||
return std::make_unique<iso_file>(fs::file(m_path, mode), m_archive.get_dec(), *node);
|
||||
}
|
||||
|
||||
std::unique_ptr<fs::dir_base> iso_device::open_dir(const std::string& path)
|
||||
|
|
@ -600,7 +1125,7 @@ void iso_dir::rewind()
|
|||
|
||||
void load_iso(const std::string& path)
|
||||
{
|
||||
sys_log.notice("Loading iso '%s'", path);
|
||||
sys_log.notice("Loading ISO '%s'", path);
|
||||
|
||||
fs::set_virtual_device("iso_overlay_fs_dev", stx::make_shared<iso_device>(path));
|
||||
|
||||
|
|
@ -609,7 +1134,7 @@ void load_iso(const std::string& path)
|
|||
|
||||
void unload_iso()
|
||||
{
|
||||
sys_log.notice("Unloading iso");
|
||||
sys_log.notice("Unloading ISO");
|
||||
|
||||
fs::set_virtual_device("iso_overlay_fs_dev", stx::shared_ptr<iso_device>());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "Utilities/File.h"
|
||||
#include "util/types.hpp"
|
||||
#include "Crypto/aes.h"
|
||||
|
||||
bool is_file_iso(const std::string& path);
|
||||
bool is_file_iso(const fs::file& path);
|
||||
|
|
@ -11,6 +12,58 @@ bool is_file_iso(const fs::file& path);
|
|||
void load_iso(const std::string& path);
|
||||
void unload_iso();
|
||||
|
||||
/*
|
||||
- Hijacked the "iso_archive::iso_archive" method to test if the ".iso" file is encrypted and sets a flag.
|
||||
The flag is set according to the first matching encryption type found following the order below:
|
||||
- Redump: ".dkey" or ".key" (as alternative) file, with the same name of the ".iso" file,
|
||||
exists in the same folder of the ".iso" file
|
||||
- 3k3y: 3k3y watermark exists at offset 0xF70
|
||||
If the flag is set then the "iso_file::read" method will decrypt the data on the fly
|
||||
|
||||
- Supported ISO encryption type:
|
||||
- Decrypted (.iso)
|
||||
- 3k3y (decrypted / encrypted) (.iso)
|
||||
- Redump (encrypted) (.iso + .dkey / .key)
|
||||
|
||||
- Unsupported ISO encryption type:
|
||||
- Encrypted split ISO files
|
||||
*/
|
||||
|
||||
// Struct to store ISO region information (storing addresses instead of LBA since we need to compare
|
||||
// the address anyway, so would have to multiply or divide every read if storing LBA)
|
||||
struct iso_region_info
|
||||
{
|
||||
bool encrypted = false;
|
||||
u64 region_first_addr = 0;
|
||||
u64 region_last_addr = 0;
|
||||
};
|
||||
|
||||
// Enum to decide ISO encryption type
|
||||
enum class iso_encryption_type
|
||||
{
|
||||
NONE,
|
||||
DEC_3K3Y,
|
||||
ENC_3K3Y,
|
||||
REDUMP
|
||||
};
|
||||
|
||||
// ISO file decryption class
|
||||
class iso_file_decryption
|
||||
{
|
||||
private:
|
||||
aes_context m_aes_dec;
|
||||
iso_encryption_type m_enc_type = iso_encryption_type::NONE;
|
||||
std::vector<iso_region_info> m_region_info;
|
||||
|
||||
void reset();
|
||||
|
||||
public:
|
||||
iso_encryption_type get_enc_type() const { return m_enc_type; }
|
||||
|
||||
bool init(const std::string& path);
|
||||
bool decrypt(u64 offset, void* buffer, u64 size, const std::string& name);
|
||||
};
|
||||
|
||||
struct iso_extent_info
|
||||
{
|
||||
u64 start = 0;
|
||||
|
|
@ -38,16 +91,17 @@ class iso_file : public fs::file_base
|
|||
{
|
||||
private:
|
||||
fs::file m_file;
|
||||
iso_fs_metadata m_meta {};
|
||||
std::shared_ptr<iso_file_decryption> m_dec;
|
||||
iso_fs_metadata m_meta;
|
||||
u64 m_pos = 0;
|
||||
|
||||
std::pair<u64, iso_extent_info> get_extent_pos(u64 pos) const;
|
||||
u64 file_offset(u64 pos) const;
|
||||
u64 local_extent_remaining(u64 pos) const;
|
||||
u64 local_extent_size(u64 pos) const;
|
||||
u64 file_offset(u64 pos) const;
|
||||
|
||||
public:
|
||||
iso_file(fs::file&& iso_handle, const iso_fs_node& node);
|
||||
iso_file(fs::file&& iso_handle, std::shared_ptr<iso_file_decryption> iso_dec, const iso_fs_node& node);
|
||||
|
||||
fs::stat_t get_stat() override;
|
||||
bool trunc(u64 length) override;
|
||||
|
|
@ -75,45 +129,47 @@ public:
|
|||
void rewind() override;
|
||||
};
|
||||
|
||||
// represents the .iso file itself.
|
||||
// Represents the .iso file itself
|
||||
class iso_archive
|
||||
{
|
||||
private:
|
||||
std::string m_path;
|
||||
iso_fs_node m_root {};
|
||||
fs::file m_file;
|
||||
std::shared_ptr<iso_file_decryption> m_dec;
|
||||
iso_fs_node m_root {};
|
||||
|
||||
public:
|
||||
iso_archive(const std::string& path);
|
||||
|
||||
const std::string& path() const { return m_path; }
|
||||
const std::shared_ptr<iso_file_decryption> get_dec() { return m_dec; }
|
||||
|
||||
iso_fs_node* retrieve(const std::string& path);
|
||||
bool exists(const std::string& path);
|
||||
bool is_file(const std::string& path);
|
||||
|
||||
iso_file open(const std::string& path);
|
||||
|
||||
psf::registry open_psf(const std::string& path);
|
||||
|
||||
const std::string& path() const { return m_path; }
|
||||
};
|
||||
|
||||
class iso_device : public fs::device_base
|
||||
{
|
||||
private:
|
||||
std::string m_path;
|
||||
iso_archive m_archive;
|
||||
std::string iso_path;
|
||||
|
||||
public:
|
||||
inline static std::string virtual_device_name = "/vfsv0_virtual_iso_overlay_fs_dev";
|
||||
|
||||
iso_device(const std::string& iso_path, const std::string& device_name = virtual_device_name)
|
||||
: m_archive(iso_path), iso_path(iso_path)
|
||||
: m_path(iso_path), m_archive(iso_path)
|
||||
{
|
||||
fs_prefix = device_name;
|
||||
}
|
||||
|
||||
~iso_device() override = default;
|
||||
|
||||
const std::string& get_loaded_iso() const { return iso_path; }
|
||||
const std::string& get_loaded_iso() const { return m_path; }
|
||||
|
||||
bool stat(const std::string& path, fs::stat_t& info) override;
|
||||
bool statfs(const std::string& path, fs::device_stat& info) override;
|
||||
|
|
|
|||
164
rpcs3/Loader/iso_cache.cpp
Normal file
164
rpcs3/Loader/iso_cache.cpp
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
#include "stdafx.h"
|
||||
|
||||
#include "iso_cache.h"
|
||||
#include "Loader/PSF.h"
|
||||
#include "util/yaml.hpp"
|
||||
#include "util/fnv_hash.hpp"
|
||||
#include "Utilities/File.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
LOG_CHANNEL(iso_cache_log, "ISOCache");
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string get_cache_dir()
|
||||
{
|
||||
const std::string dir = fs::get_cache_dir() + "cache/iso_cache/";
|
||||
fs::create_path(dir);
|
||||
return dir;
|
||||
}
|
||||
|
||||
// FNV-64 hash of the ISO path used as the cache filename stem.
|
||||
std::string get_cache_stem(const std::string& iso_path)
|
||||
{
|
||||
usz hash = rpcs3::fnv_seed;
|
||||
for (const char c : iso_path)
|
||||
{
|
||||
hash ^= static_cast<u8>(c);
|
||||
hash *= rpcs3::fnv_prime;
|
||||
}
|
||||
return fmt::format("%016llx", hash);
|
||||
}
|
||||
}
|
||||
|
||||
namespace iso_cache
|
||||
{
|
||||
bool load(const std::string& iso_path, iso_metadata_cache_entry& out_entry)
|
||||
{
|
||||
fs::stat_t iso_stat{};
|
||||
if (!fs::get_stat(iso_path, iso_stat) || iso_stat.is_directory)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string stem = get_cache_stem(iso_path);
|
||||
const std::string dir = get_cache_dir();
|
||||
const std::string yml_path = dir + stem + ".yml";
|
||||
const std::string sfo_path = dir + stem + ".sfo";
|
||||
const std::string png_path = dir + stem + ".png";
|
||||
|
||||
const fs::file yml_file(yml_path);
|
||||
if (!yml_file)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto [node, error] = yaml_load(yml_file.to_string());
|
||||
if (!error.empty())
|
||||
{
|
||||
iso_cache_log.warning("Failed to parse cache YAML for '%s': %s", iso_path, error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reject stale entries.
|
||||
const s64 cached_mtime = node["mtime"].as<s64>(0);
|
||||
if (cached_mtime != iso_stat.mtime)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const fs::file sfo_file(sfo_path);
|
||||
if (!sfo_file)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
out_entry.mtime = cached_mtime;
|
||||
out_entry.psf_data = sfo_file.to_vector<u8>();
|
||||
out_entry.icon_path = node["icon_path"].as<std::string>("");
|
||||
out_entry.movie_path = node["movie_path"].as<std::string>("");
|
||||
out_entry.audio_path = node["audio_path"].as<std::string>("");
|
||||
|
||||
// Icon bytes are optional — game may have no icon.
|
||||
if (const fs::file png_file(png_path); png_file)
|
||||
{
|
||||
out_entry.icon_data = png_file.to_vector<u8>();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void save(const std::string& iso_path, const iso_metadata_cache_entry& entry)
|
||||
{
|
||||
iso_cache_log.notice("Saving cache for '%s'", iso_path);
|
||||
|
||||
const std::string stem = get_cache_stem(iso_path);
|
||||
const std::string dir = get_cache_dir();
|
||||
const std::string yml_path = dir + stem + ".yml";
|
||||
const std::string sfo_path = dir + stem + ".sfo";
|
||||
const std::string png_path = dir + stem + ".png";
|
||||
|
||||
YAML::Emitter out;
|
||||
out << YAML::BeginMap;
|
||||
out << YAML::Key << "mtime" << YAML::Value << static_cast<long long>(entry.mtime);
|
||||
out << YAML::Key << "icon_path" << YAML::Value << entry.icon_path;
|
||||
out << YAML::Key << "movie_path" << YAML::Value << entry.movie_path;
|
||||
out << YAML::Key << "audio_path" << YAML::Value << entry.audio_path;
|
||||
out << YAML::EndMap;
|
||||
|
||||
if (fs::pending_file yml_file(yml_path); yml_file.file)
|
||||
{
|
||||
yml_file.file.write(out.c_str(), out.size());
|
||||
yml_file.commit();
|
||||
}
|
||||
else
|
||||
{
|
||||
iso_cache_log.warning("Failed to write cache YAML for '%s'", iso_path);
|
||||
}
|
||||
|
||||
if (!entry.psf_data.empty())
|
||||
{
|
||||
if (fs::pending_file sfo_file(sfo_path); sfo_file.file)
|
||||
{
|
||||
sfo_file.file.write(entry.psf_data);
|
||||
sfo_file.commit();
|
||||
}
|
||||
}
|
||||
|
||||
if (!entry.icon_data.empty())
|
||||
{
|
||||
if (fs::pending_file png_file(png_path); png_file.file)
|
||||
{
|
||||
png_file.file.write(entry.icon_data);
|
||||
png_file.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cleanup(const std::unordered_set<std::string>& valid_iso_paths)
|
||||
{
|
||||
const std::string dir = get_cache_dir();
|
||||
|
||||
// Build a set of stems that should exist.
|
||||
std::unordered_set<std::string> valid_stems;
|
||||
for (const std::string& path : valid_iso_paths)
|
||||
{
|
||||
valid_stems.insert(get_cache_stem(path));
|
||||
}
|
||||
|
||||
// Delete any cache files whose stem is not in the valid set.
|
||||
fs::dir cache_dir(dir);
|
||||
fs::dir_entry entry{};
|
||||
while (cache_dir.read(entry))
|
||||
{
|
||||
if (entry.name == "." || entry.name == "..") continue;
|
||||
|
||||
const std::string stem = entry.name.substr(0, entry.name.find_last_of('.'));
|
||||
if (valid_stems.find(stem) == valid_stems.end())
|
||||
{
|
||||
fs::remove_file(dir + entry.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
rpcs3/Loader/iso_cache.h
Normal file
32
rpcs3/Loader/iso_cache.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include "Loader/PSF.h"
|
||||
#include "Utilities/File.h"
|
||||
#include "util/types.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
// Cached metadata extracted from an ISO during game list scanning.
|
||||
struct iso_metadata_cache_entry
|
||||
{
|
||||
s64 mtime = 0;
|
||||
std::vector<u8> psf_data{};
|
||||
std::string icon_path{};
|
||||
std::vector<u8> icon_data{};
|
||||
std::string movie_path{};
|
||||
std::string audio_path{};
|
||||
};
|
||||
|
||||
namespace iso_cache
|
||||
{
|
||||
// Returns false if no valid cache entry exists or mtime has changed.
|
||||
bool load(const std::string& iso_path, iso_metadata_cache_entry& out_entry);
|
||||
|
||||
// Persists a populated cache entry to disk.
|
||||
void save(const std::string& iso_path, const iso_metadata_cache_entry& entry);
|
||||
|
||||
// Remove cache entries for ISOs that are no longer in the scanned set.
|
||||
void cleanup(const std::unordered_set<std::string>& valid_iso_paths);
|
||||
}
|
||||
|
|
@ -551,6 +551,7 @@
|
|||
<ClCompile Include="Loader\PUP.cpp" />
|
||||
<ClCompile Include="Loader\TAR.cpp" />
|
||||
<ClCompile Include="Loader\ISO.cpp" />
|
||||
<ClCompile Include="Loader\iso_cache.cpp" />
|
||||
<ClCompile Include="Loader\mself.cpp" />
|
||||
<ClCompile Include="Loader\TROPUSR.cpp" />
|
||||
<ClCompile Include="Loader\TRP.cpp" />
|
||||
|
|
@ -661,6 +662,7 @@
|
|||
<ClInclude Include="Emu\NP\ip_address.h" />
|
||||
<ClInclude Include="Emu\perf_monitor.hpp" />
|
||||
<ClInclude Include="Emu\RSX\color_utils.h" />
|
||||
<ClInclude Include="Emu\RSX\Common\aligned_malloc.hpp" />
|
||||
<ClInclude Include="Emu\RSX\Common\bitfield.hpp" />
|
||||
<ClInclude Include="Emu\RSX\Common\buffer_stream.hpp" />
|
||||
<ClInclude Include="Emu\RSX\Common\reverse_ptr.hpp" />
|
||||
|
|
|
|||
|
|
@ -2872,6 +2872,9 @@
|
|||
<ClInclude Include="Emu\RSX\Overlays\overlay_arrow.h">
|
||||
<Filter>Emu\GPU\RSX\Overlays</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\RSX\Common\aligned_malloc.hpp">
|
||||
<Filter>Emu\GPU\RSX\Common</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Emu\RSX\Program\GLSLSnippets\GPUDeswizzle.glsl">
|
||||
|
|
|
|||
|
|
@ -185,16 +185,23 @@ std::set<std::string> get_one_drive_paths()
|
|||
fmt::append(buf, "\nSerialized Object: %s", g_tls_serialize_name);
|
||||
}
|
||||
|
||||
const system_state state = Emu.GetStatus(false);
|
||||
|
||||
if (state == system_state::stopped)
|
||||
if (Emulator::IsAvailable())
|
||||
{
|
||||
fmt::append(buf, "\nEmulation is stopped");
|
||||
const system_state state = Emu.GetStatus(false);
|
||||
|
||||
if (state == system_state::stopped)
|
||||
{
|
||||
fmt::append(buf, "\nEmulation is stopped");
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string name = Emu.GetTitleAndTitleID();
|
||||
fmt::append(buf, "\nTitle: \"%s\" (emulation is %s)", name.empty() ? "N/A" : name.c_str(), state == system_state::stopping ? "stopping" : "running");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::string& name = Emu.GetTitleAndTitleID();
|
||||
fmt::append(buf, "\nTitle: \"%s\" (emulation is %s)", name.empty() ? "N/A" : name.data(), state == system_state::stopping ? "stopping" : "running");
|
||||
fmt::append(buf, "\nEmulation object is unavailable (process teardown)");
|
||||
}
|
||||
|
||||
fmt::append(buf, "\nBuild: \"%s\"", rpcs3::get_verbose_version());
|
||||
|
|
@ -656,7 +663,7 @@ int run_rpcs3(int argc, char** argv)
|
|||
// Initialize thread pool finalizer (on first use)
|
||||
static_cast<void>(named_thread("", [](int) {}));
|
||||
|
||||
static std::unique_ptr<logs::listener> log_file;
|
||||
std::unique_ptr<logs::listener> log_file;
|
||||
{
|
||||
// Check free space
|
||||
fs::device_stat stats{};
|
||||
|
|
@ -669,9 +676,17 @@ int run_rpcs3(int argc, char** argv)
|
|||
log_file = logs::make_file_listener(log_name, stats.avail_free / 4);
|
||||
}
|
||||
|
||||
static std::unique_ptr<fatal_error_listener> fatal_listener = std::make_unique<fatal_error_listener>();
|
||||
auto fatal_listener = std::make_unique<fatal_error_listener>();
|
||||
logs::listener::add(fatal_listener.get());
|
||||
|
||||
struct log_listener_shutdown_guard
|
||||
{
|
||||
~log_listener_shutdown_guard()
|
||||
{
|
||||
logs::listener::shutdown_all();
|
||||
}
|
||||
} log_listener_shutdown;
|
||||
|
||||
{
|
||||
// Write RPCS3 version
|
||||
logs::stored_message ver{sys_log.always()};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
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;
|
||||
};
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -42,10 +42,10 @@ enum class emu_settings_type
|
|||
PerformanceReport,
|
||||
FullWidthAVX512,
|
||||
PPUNJFixup,
|
||||
PPUVNANFixup,
|
||||
AccurateDFMA,
|
||||
AccuratePPUSAT,
|
||||
AccuratePPUNJ,
|
||||
FixupPPUVNAN,
|
||||
AccuratePPUVNAN,
|
||||
AccuratePPUFPCC,
|
||||
MaxPreemptCount,
|
||||
|
|
@ -82,6 +82,7 @@ enum class emu_settings_type
|
|||
ShaderPrecisionQuality,
|
||||
StereoRenderEnabled,
|
||||
StereoRenderMode,
|
||||
ScreenSize,
|
||||
AnisotropicFilterOverride,
|
||||
TextureLodBias,
|
||||
ResolutionScale,
|
||||
|
|
@ -258,10 +259,10 @@ inline static const std::map<emu_settings_type, cfg_location> settings_location
|
|||
{ emu_settings_type::FullWidthAVX512, { "Core", "Full Width AVX-512"}},
|
||||
{ emu_settings_type::NumPPUThreads, { "Core", "PPU Threads"}},
|
||||
{ emu_settings_type::PPUNJFixup, { "Core", "PPU LLVM Java Mode Handling"}},
|
||||
{ emu_settings_type::PPUVNANFixup, { "Core", "PPU Vector NaN Handling"}},
|
||||
{ emu_settings_type::AccurateDFMA, { "Core", "Use Accurate DFMA"}},
|
||||
{ emu_settings_type::AccuratePPUSAT, { "Core", "PPU Set Saturation Bit"}},
|
||||
{ emu_settings_type::AccuratePPUNJ, { "Core", "PPU Accurate Non-Java Mode"}},
|
||||
{ emu_settings_type::FixupPPUVNAN, { "Core", "PPU Fixup Vector NaN Values"}},
|
||||
{ emu_settings_type::AccuratePPUVNAN, { "Core", "PPU Accurate Vector NaN Values"}},
|
||||
{ emu_settings_type::AccuratePPUFPCC, { "Core", "PPU Set FPCC Bits"}},
|
||||
{ emu_settings_type::MaxPreemptCount, { "Core", "Max CPU Preempt Count"}},
|
||||
|
|
@ -295,6 +296,7 @@ inline static const std::map<emu_settings_type, cfg_location> settings_location
|
|||
{ emu_settings_type::DisableFIFOReordering, { "Video", "Disable FIFO Reordering"}},
|
||||
{ emu_settings_type::StereoRenderEnabled, { "Video", "3D Display Enabled"}},
|
||||
{ emu_settings_type::StereoRenderMode, { "Video", "3D Display Mode"}},
|
||||
{ emu_settings_type::ScreenSize, { "Video", "Screen size in inches"}},
|
||||
{ emu_settings_type::StrictTextureFlushing, { "Video", "Strict Texture Flushing"}},
|
||||
{ emu_settings_type::ForceCPUBlitEmulation, { "Video", "Force CPU Blit"}},
|
||||
{ emu_settings_type::DisableOnDiskShaderCache, { "Video", "Disable On-Disk Shader Cache"}},
|
||||
|
|
|
|||
|
|
@ -145,11 +145,8 @@ void find_dialog::find(find_type type)
|
|||
|
||||
m_count_lines++;
|
||||
|
||||
int pos_count = 0;
|
||||
|
||||
for (int pos : positions)
|
||||
{
|
||||
pos_count++;
|
||||
word_count++;
|
||||
|
||||
if (is_current_block && is_current_line && pos == current_pos)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "qt_utils.h"
|
||||
#include "progress_dialog.h"
|
||||
|
||||
#include "Utilities/Thread.h"
|
||||
#include "Utilities/File.h"
|
||||
#include "Loader/ISO.h"
|
||||
|
||||
|
|
@ -373,6 +374,8 @@ 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_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,7 @@
|
|||
#include "pad_settings_dialog.h"
|
||||
#include "patch_manager_dialog.h"
|
||||
#include "persistent_settings.h"
|
||||
#include "config_database.h"
|
||||
|
||||
#include "Utilities/File.h"
|
||||
#include "Emu/system_utils.hpp"
|
||||
|
|
@ -67,8 +68,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 +157,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"));
|
||||
|
|
@ -578,6 +581,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 +602,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 +623,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 +678,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 +946,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,12 +10,14 @@
|
|||
#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"
|
||||
#include "Emu/system_utils.hpp"
|
||||
#include "Loader/PSF.h"
|
||||
#include "Loader/ISO.h"
|
||||
#include "Loader/iso_cache.h"
|
||||
#include "util/types.hpp"
|
||||
#include "Utilities/File.h"
|
||||
#include "util/sysinfo.hpp"
|
||||
|
|
@ -73,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);
|
||||
|
|
@ -199,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);
|
||||
|
||||
|
|
@ -530,14 +549,29 @@ void game_list_frame::OnParsingFinished()
|
|||
(const std::string& dir_or_elf)
|
||||
{
|
||||
std::unique_ptr<iso_archive> archive;
|
||||
if (is_file_iso(dir_or_elf))
|
||||
iso_metadata_cache_entry cache_entry{};
|
||||
const bool is_iso = is_file_iso(dir_or_elf);
|
||||
|
||||
if (is_iso)
|
||||
{
|
||||
archive = std::make_unique<iso_archive>(dir_or_elf);
|
||||
// Only construct iso_archive (which walks the full directory tree)
|
||||
// when no valid cache entry exists for this ISO path + mtime.
|
||||
if (!iso_cache::load(dir_or_elf, cache_entry))
|
||||
{
|
||||
archive = std::make_unique<iso_archive>(dir_or_elf);
|
||||
}
|
||||
// Track this ISO path for cache cleanup after scan completes.
|
||||
std::lock_guard lock(m_path_mutex);
|
||||
m_scanned_iso_paths.insert(dir_or_elf);
|
||||
}
|
||||
|
||||
const auto file_exists = [&archive](const std::string& path)
|
||||
const auto file_exists = [&archive, &cache_entry](const std::string& path)
|
||||
{
|
||||
return archive ? archive->is_file(path) : fs::is_file(path);
|
||||
if (archive) return archive->is_file(path);
|
||||
// On cache hit, paths inside the ISO are not accessible via fs::is_file.
|
||||
// Return false here — cache hit paths are handled separately.
|
||||
if (!cache_entry.psf_data.empty()) return false;
|
||||
return fs::is_file(path);
|
||||
};
|
||||
|
||||
gui_game_info game{};
|
||||
|
|
@ -545,10 +579,39 @@ void game_list_frame::OnParsingFinished()
|
|||
|
||||
const Localized thread_localized;
|
||||
|
||||
const std::string sfo_dir = archive ? "PS3_GAME" : rpcs3::utils::get_sfo_dir_from_game_path(dir_or_elf);
|
||||
const std::string sfo_dir = (archive || !cache_entry.psf_data.empty()) ? "PS3_GAME" : rpcs3::utils::get_sfo_dir_from_game_path(dir_or_elf);
|
||||
const std::string sfo_path = sfo_dir + "/PARAM.SFO";
|
||||
|
||||
const psf::registry psf = archive ? archive->open_psf(sfo_path) : psf::load_object(sfo_path);
|
||||
// Load PSF: from archive on cache miss, rehydrate from cached SFO bytes on hit.
|
||||
psf::registry psf{};
|
||||
if (!cache_entry.psf_data.empty())
|
||||
{
|
||||
psf = psf::load_object(fs::make_stream<std::vector<u8>>(std::vector<u8>(cache_entry.psf_data)), sfo_path);
|
||||
// Fallback to archive scan if cached PSF is corrupted or missing critical fields.
|
||||
const bool psf_valid = !psf::get_string(psf, "TITLE_ID", "").empty()
|
||||
&& !psf::get_string(psf, "TITLE", "").empty()
|
||||
&& !psf::get_string(psf, "CATEGORY", "").empty();
|
||||
if (!psf_valid)
|
||||
{
|
||||
game_list_log.warning("Cached psf for iso not valid: '%s'", game.info.path);
|
||||
archive = std::make_unique<iso_archive>(dir_or_elf);
|
||||
cache_entry = {}; // Reset so the cache gets rewritten after scan.
|
||||
psf = {};
|
||||
}
|
||||
}
|
||||
|
||||
if (psf.empty())
|
||||
{
|
||||
if (archive)
|
||||
{
|
||||
psf = archive->open_psf(sfo_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
psf = psf::load_object(sfo_path);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string_view title_id = psf::get_string(psf, "TITLE_ID", "");
|
||||
|
||||
if (title_id.empty())
|
||||
|
|
@ -616,15 +679,22 @@ void game_list_frame::OnParsingFinished()
|
|||
|
||||
if (game.info.icon_path.empty())
|
||||
{
|
||||
if (std::string icon_path = sfo_dir + "/" + localized_icon; file_exists(icon_path))
|
||||
if (!cache_entry.icon_path.empty())
|
||||
{
|
||||
// Cache hit — icon path already resolved on a previous scan.
|
||||
game.info.icon_path = cache_entry.icon_path;
|
||||
game.icon_in_archive = true;
|
||||
}
|
||||
else if (std::string icon_path = sfo_dir + "/" + localized_icon; file_exists(icon_path))
|
||||
{
|
||||
game.info.icon_path = std::move(icon_path);
|
||||
game.icon_in_archive = archive && archive->exists(game.info.icon_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
game.info.icon_path = sfo_dir + "/ICON0.PNG";
|
||||
game.icon_in_archive = archive && archive->exists(game.info.icon_path);
|
||||
}
|
||||
game.icon_in_archive = archive && archive->exists(game.info.icon_path);
|
||||
}
|
||||
|
||||
if (play_hover_movies)
|
||||
|
|
@ -634,6 +704,12 @@ void game_list_frame::OnParsingFinished()
|
|||
game.info.movie_path = std::move(movie_path);
|
||||
game.has_hover_gif = true;
|
||||
}
|
||||
else if (!cache_entry.movie_path.empty() && !archive)
|
||||
{
|
||||
// Cache hit — restore previously resolved movie path.
|
||||
game.info.movie_path = cache_entry.movie_path;
|
||||
game.has_hover_pam = true;
|
||||
}
|
||||
else if (std::string movie_path = sfo_dir + "/" + localized_movie; file_exists(movie_path))
|
||||
{
|
||||
game.info.movie_path = std::move(movie_path);
|
||||
|
|
@ -648,13 +724,48 @@ void game_list_frame::OnParsingFinished()
|
|||
|
||||
if (play_hover_music)
|
||||
{
|
||||
if (std::string audio_path = sfo_dir + "/SND0.AT3"; file_exists(audio_path))
|
||||
if (!cache_entry.audio_path.empty() && !archive)
|
||||
{
|
||||
// Cache hit — restore previously resolved audio path.
|
||||
game.info.audio_path = cache_entry.audio_path;
|
||||
game.has_audio_file = true;
|
||||
}
|
||||
else if (std::string audio_path = sfo_dir + "/SND0.AT3"; file_exists(audio_path))
|
||||
{
|
||||
game.info.audio_path = std::move(audio_path);
|
||||
game.has_audio_file = true;
|
||||
}
|
||||
}
|
||||
|
||||
// On cache miss for an ISO, persist the resolved metadata so subsequent
|
||||
// launches skip iso_archive construction entirely.
|
||||
if (archive && is_iso)
|
||||
{
|
||||
fs::stat_t iso_stat{};
|
||||
if (fs::get_stat(dir_or_elf, iso_stat))
|
||||
{
|
||||
cache_entry.mtime = iso_stat.mtime;
|
||||
cache_entry.psf_data = psf::save_object(psf);
|
||||
cache_entry.icon_path = game.info.icon_path;
|
||||
cache_entry.movie_path = game.info.movie_path;
|
||||
cache_entry.audio_path = game.info.audio_path;
|
||||
|
||||
// Cache raw icon bytes so load_iso_icon can skip archive open.
|
||||
if (game.icon_in_archive)
|
||||
{
|
||||
auto icon_file = archive->open(game.info.icon_path);
|
||||
const auto icon_size = icon_file.size();
|
||||
if (icon_size > 0)
|
||||
{
|
||||
cache_entry.icon_data.resize(icon_size);
|
||||
icon_file.read(cache_entry.icon_data.data(), icon_size);
|
||||
}
|
||||
}
|
||||
|
||||
iso_cache::save(dir_or_elf, cache_entry);
|
||||
}
|
||||
}
|
||||
|
||||
const QString serial = QString::fromStdString(game.info.serial);
|
||||
|
||||
m_games_mutex.lock();
|
||||
|
|
@ -708,6 +819,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));
|
||||
|
||||
|
|
@ -817,6 +929,9 @@ void game_list_frame::OnRefreshFinished()
|
|||
WaitAndAbortSizeCalcThreads();
|
||||
WaitAndAbortRepaintThreads();
|
||||
|
||||
// Remove cache entries for ISOs that are no longer present in the scanned paths.
|
||||
iso_cache::cleanup(m_scanned_iso_paths);
|
||||
|
||||
for (auto&& g : m_games.pop_all())
|
||||
{
|
||||
m_game_data.push_back(g);
|
||||
|
|
@ -903,6 +1018,7 @@ void game_list_frame::OnRefreshFinished()
|
|||
m_serials.clear();
|
||||
m_path_list.clear();
|
||||
m_path_entries.clear();
|
||||
m_scanned_iso_paths.clear();
|
||||
|
||||
Refresh();
|
||||
|
||||
|
|
@ -927,6 +1043,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{};
|
||||
|
|
@ -181,6 +185,7 @@ private:
|
|||
std::vector<path_entry> m_path_entries;
|
||||
shared_mutex m_path_mutex;
|
||||
std::set<std::string> m_path_list;
|
||||
std::unordered_set<std::string> m_scanned_iso_paths;
|
||||
QSet<QString> m_serials;
|
||||
QMutex m_games_mutex;
|
||||
lf_queue<game_info> m_games;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,7 +981,7 @@ 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);
|
||||
|
|
|
|||
|
|
@ -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,17 +24,25 @@ 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, const game_compatibility* compat, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
ensure(!paths.empty());
|
||||
|
||||
setWindowTitle(tr("PKG Installation"));
|
||||
setObjectName("pkg_install_dialog");
|
||||
|
||||
m_dir_list = new QListWidget(this);
|
||||
m_dir_list->setItemDelegate(new richtext_item_delegate(m_dir_list->itemDelegate()));
|
||||
|
||||
QStringList corrupt_paths;
|
||||
|
||||
for (const QString& path : paths)
|
||||
{
|
||||
const compat::package_info info = game_compatibility::GetPkgInfo(path, compat);
|
||||
if (!info.is_valid)
|
||||
{
|
||||
corrupt_paths << path;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -44,12 +52,14 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil
|
|||
QString accumulated_info;
|
||||
QString tooltip;
|
||||
|
||||
const auto append_comma = [&accumulated_info]()
|
||||
const auto append_info = [&accumulated_info](const QString& info)
|
||||
{
|
||||
if (!accumulated_info.isEmpty())
|
||||
{
|
||||
accumulated_info += ", ";
|
||||
}
|
||||
|
||||
accumulated_info += info;
|
||||
};
|
||||
|
||||
if (!info.title_id.isEmpty())
|
||||
|
|
@ -59,27 +69,23 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil
|
|||
|
||||
if (info.type != compat::package_type::other)
|
||||
{
|
||||
append_comma();
|
||||
|
||||
if (info.type == compat::package_type::dlc)
|
||||
{
|
||||
accumulated_info += tr("DLC", "Package type info (DLC)");
|
||||
append_info(tr("DLC", "Package type info (DLC)"));
|
||||
}
|
||||
else
|
||||
{
|
||||
accumulated_info += tr("Update", "Package type info (Update)");
|
||||
append_info(tr("Update", "Package type info (Update)"));
|
||||
}
|
||||
}
|
||||
else if (!info.local_cat.isEmpty())
|
||||
{
|
||||
append_comma();
|
||||
accumulated_info += info.local_cat;
|
||||
append_info(info.local_cat);
|
||||
}
|
||||
|
||||
if (!info.version.isEmpty())
|
||||
{
|
||||
append_comma();
|
||||
accumulated_info += tr("v.%0", "Version info").arg(info.version);
|
||||
append_info(tr("v.%0", "Version info").arg(info.version));
|
||||
}
|
||||
|
||||
if (info.changelog.isEmpty())
|
||||
|
|
@ -91,8 +97,7 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil
|
|||
tooltip = tr("Changelog:\n\n%0", "Changelog info").arg(info.changelog);
|
||||
}
|
||||
|
||||
append_comma();
|
||||
accumulated_info += file_info.fileName();
|
||||
append_info(file_info.fileName());
|
||||
|
||||
const QString text = tr("<b>%0</b> (%1) - %2", "Package text").arg(info.title.simplified())
|
||||
.arg(accumulated_info).arg(gui::utils::format_byte_size(info.data_size));
|
||||
|
|
@ -107,18 +112,8 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil
|
|||
item->setToolTip(tooltip);
|
||||
}
|
||||
|
||||
m_dir_list->sortItems();
|
||||
m_dir_list->setCurrentRow(0);
|
||||
m_dir_list->setMinimumWidth((m_dir_list->sizeHintForColumn(0) * 125) / 100);
|
||||
|
||||
// Create contextual label (updated in connect(m_dir_list, &QListWidget::itemChanged ...))
|
||||
QLabel* installation_info = new QLabel();
|
||||
installation_info->setTextFormat(Qt::RichText); // Support HTML tags
|
||||
|
||||
// Create buttons
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
|
||||
buttons->button(QDialogButtonBox::Ok)->setText(tr("Install"));
|
||||
buttons->button(QDialogButtonBox::Ok)->setDefault(true);
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(corrupt_paths.isEmpty() ? (QDialogButtonBox::Cancel | QDialogButtonBox::Ok) : QDialogButtonBox::Cancel);
|
||||
|
||||
connect(buttons, &QDialogButtonBox::clicked, this, [this, buttons](QAbstractButton* button)
|
||||
{
|
||||
|
|
@ -132,6 +127,35 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil
|
|||
}
|
||||
});
|
||||
|
||||
if (!corrupt_paths.isEmpty())
|
||||
{
|
||||
m_dir_list->hide();
|
||||
|
||||
QString text = tr("Can not install packages. The following packages seem to be corrupt:") + "\n";
|
||||
|
||||
for (const QString& path : corrupt_paths)
|
||||
{
|
||||
text += "\n" + path;
|
||||
}
|
||||
|
||||
QVBoxLayout* vbox = new QVBoxLayout;
|
||||
vbox->addWidget(new QLabel(text));
|
||||
vbox->addWidget(buttons);
|
||||
setLayout(vbox);
|
||||
return;
|
||||
}
|
||||
|
||||
buttons->button(QDialogButtonBox::Ok)->setText(tr("Install"));
|
||||
buttons->button(QDialogButtonBox::Ok)->setDefault(true);
|
||||
|
||||
m_dir_list->sortItems();
|
||||
m_dir_list->setCurrentRow(0);
|
||||
m_dir_list->setMinimumWidth((m_dir_list->sizeHintForColumn(0) * 125) / 100);
|
||||
|
||||
// Create contextual label (updated in connect(m_dir_list, &QListWidget::itemChanged ...))
|
||||
QLabel* installation_info = new QLabel();
|
||||
installation_info->setTextFormat(Qt::RichText); // Support HTML tags
|
||||
|
||||
QHBoxLayout* hbox = nullptr;
|
||||
if (m_dir_list->count() > 1)
|
||||
{
|
||||
|
|
@ -210,8 +234,6 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil
|
|||
vbox->addWidget(buttons);
|
||||
|
||||
setLayout(vbox);
|
||||
setWindowTitle(tr("PKG Installation"));
|
||||
setObjectName("pkg_install_dialog");
|
||||
update_info(installation_info, buttons); // Just to show and check available and required size
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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, 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; }
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "Emu/system_utils.hpp"
|
||||
#include "Utilities/File.h"
|
||||
#include "Loader/ISO.h"
|
||||
#include "Loader/iso_cache.h"
|
||||
#include <cmath>
|
||||
|
||||
LOG_CHANNEL(gui_log, "GUI");
|
||||
|
|
@ -709,6 +710,15 @@ namespace gui
|
|||
if (icon_path.empty() || archive_path.empty()) return false;
|
||||
if (!is_file_iso(archive_path)) return false;
|
||||
|
||||
// Check cache first — avoids constructing a full iso_archive just for the icon.
|
||||
iso_metadata_cache_entry cache_entry{};
|
||||
if (iso_cache::load(archive_path, cache_entry) && !cache_entry.icon_data.empty())
|
||||
{
|
||||
const QByteArray data(reinterpret_cast<const char*>(cache_entry.icon_data.data()),
|
||||
static_cast<qsizetype>(cache_entry.icon_data.size()));
|
||||
return icon.loadFromData(data);
|
||||
}
|
||||
|
||||
iso_archive archive(archive_path);
|
||||
if (!archive.exists(icon_path)) return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -581,6 +581,7 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
|
|||
// 3D
|
||||
m_emu_settings->EnhanceComboBox(ui->stereoRenderMode, emu_settings_type::StereoRenderMode);
|
||||
m_emu_settings->EnhanceCheckBox(ui->stereoRenderEnabled, emu_settings_type::StereoRenderEnabled);
|
||||
m_emu_settings->EnhanceSpinBox(ui->sb_screen_size, emu_settings_type::ScreenSize);
|
||||
SubscribeTooltip(ui->gb_stereo, tooltips.settings.stereo_render_mode);
|
||||
if (game)
|
||||
{
|
||||
|
|
@ -591,6 +592,7 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
|
|||
const bool stereo_enabled = ui->stereoRenderEnabled->checkState() == Qt::CheckState::Checked;
|
||||
ui->stereoRenderMode->setEnabled(stereo_allowed && stereo_enabled);
|
||||
ui->stereoRenderEnabled->setEnabled(stereo_allowed);
|
||||
ui->gb_screen_size->setEnabled(stereo_allowed && stereo_enabled);
|
||||
};
|
||||
connect(ui->resBox, &QComboBox::currentIndexChanged, this, [enable_3D_modes](int){ enable_3D_modes(); });
|
||||
connect(ui->stereoRenderEnabled, &QCheckBox::checkStateChanged, this, [enable_3D_modes](Qt::CheckState){ enable_3D_modes(); });
|
||||
|
|
@ -1499,8 +1501,8 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
|
|||
m_emu_settings->EnhanceCheckBox(ui->ppuNJFixup, emu_settings_type::PPUNJFixup);
|
||||
SubscribeTooltip(ui->ppuNJFixup, tooltips.settings.fixup_ppunj);
|
||||
|
||||
m_emu_settings->EnhanceCheckBox(ui->fixupPPUVNAN, emu_settings_type::FixupPPUVNAN);
|
||||
SubscribeTooltip(ui->fixupPPUVNAN, tooltips.settings.fixup_ppuvnan);
|
||||
m_emu_settings->EnhanceCheckBox(ui->PPUVNANfixup, emu_settings_type::PPUVNANFixup);
|
||||
SubscribeTooltip(ui->PPUVNANfixup, tooltips.settings.fixup_ppuvnan);
|
||||
|
||||
m_emu_settings->EnhanceCheckBox(ui->llvmPrecompilation, emu_settings_type::LLVMPrecompilation);
|
||||
SubscribeTooltip(ui->llvmPrecompilation, tooltips.settings.llvm_precompilation);
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -557,6 +557,18 @@
|
|||
<item>
|
||||
<widget class="QComboBox" name="stereoRenderMode"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_screen_size">
|
||||
<property name="title">
|
||||
<string>Screen Size (Inch)</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="layout_gb_screen_size">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="sb_screen_size"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
@ -3543,7 +3555,7 @@
|
|||
<number>1</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.5</double>
|
||||
<double>0.500000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
@ -3571,7 +3583,7 @@
|
|||
<number>1</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.5</double>
|
||||
<double>0.500000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
@ -4309,6 +4321,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="disableAsyncHostMM">
|
||||
<property name="text">
|
||||
<string>Disable Asynchronous Memory Manager</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="disableFIFOReordering">
|
||||
<property name="text">
|
||||
|
|
@ -4316,6 +4335,20 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="disableHardwareTexelRemapping">
|
||||
<property name="text">
|
||||
<string>Disable Hardware ColorSpace Remapping</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="disableOnDiskShaderCache">
|
||||
<property name="text">
|
||||
<string>Disable On-Disk Shader Cache</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="disableVideoOutput">
|
||||
<property name="text">
|
||||
|
|
@ -4382,27 +4415,6 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="disableAsyncHostMM">
|
||||
<property name="text">
|
||||
<string>Disable Asynchronous Memory Manager</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="disableOnDiskShaderCache">
|
||||
<property name="text">
|
||||
<string>Disable On-Disk Shader Cache</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="disableHardwareTexelRemapping">
|
||||
<property name="text">
|
||||
<string>Disable Hardware ColorSpace Remapping</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacerDebugGPU">
|
||||
<property name="orientation">
|
||||
|
|
@ -4428,13 +4440,6 @@
|
|||
<string>Core</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="gb_debug_core_layout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="accurateDFMA">
|
||||
<property name="text">
|
||||
<string>Accurate DFMA</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="alwaysStart">
|
||||
<property name="text">
|
||||
|
|
@ -4456,20 +4461,6 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="ppuDebug">
|
||||
<property name="text">
|
||||
<string>PPU Debug</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="spuDebug">
|
||||
<property name="text">
|
||||
<string>SPU Debug</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="mfcDebug">
|
||||
<property name="text">
|
||||
|
|
@ -4477,59 +4468,24 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="ppuDebug">
|
||||
<property name="text">
|
||||
<string>PPU Debug</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="setDAZandFTZ">
|
||||
<property name="text">
|
||||
<string>Set DAZ and FTZ</string>
|
||||
<string>PPU Set DAZ and FTZ</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="accuratePPUSAT">
|
||||
<widget class="QCheckBox" name="spuDebug">
|
||||
<property name="text">
|
||||
<string>Accurate PPU Saturation Bit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="accuratePPUNJ">
|
||||
<property name="text">
|
||||
<string>Accurate PPU Non-Java Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="ppuNJFixup">
|
||||
<property name="text">
|
||||
<string>PPU Non-Java Mode Fixup</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="accuratePPUVNAN">
|
||||
<property name="text">
|
||||
<string>Accurate PPU Vector NaN Handling</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="accuratePPUFPCC">
|
||||
<property name="text">
|
||||
<string>Accurate PPU Float Condition Control</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="accurateClineStores">
|
||||
<property name="text">
|
||||
<string>Accurate Cache Line Stores</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="fixupPPUVNAN">
|
||||
<property name="text">
|
||||
<string>PPU Vector NaN Fixup</string>
|
||||
<string>SPU Debug</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
@ -4556,6 +4512,87 @@
|
|||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_debug_cpu">
|
||||
<property name="title">
|
||||
<string>CPU Accuracy</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="gb_debug_cpu_layout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="accurateDFMA">
|
||||
<property name="text">
|
||||
<string>Accurate PPU/SPU Double-Precision FMA</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="accurateClineStores">
|
||||
<property name="text">
|
||||
<string>Accurate PPU/SPU Cache Line Stores</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="accuratePPUFPCC">
|
||||
<property name="text">
|
||||
<string>Accurate PPU Float Condition Control</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="accuratePPUSAT">
|
||||
<property name="text">
|
||||
<string>Accurate PPU Saturation Bit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="accuratePPUNJ">
|
||||
<property name="text">
|
||||
<string>Accurate PPU Non-Java Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="accuratePPUVNAN">
|
||||
<property name="text">
|
||||
<string>Accurate PPU Vector NaN Handling</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="ppuNJFixup">
|
||||
<property name="text">
|
||||
<string>Approximate PPU Non-Java Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="PPUVNANfixup">
|
||||
<property name="text">
|
||||
<string>Approximate PPU Vector NaN Handling</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacerDebugCpu">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Policy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ public:
|
|||
const QString debug_console_mode = tr("Increases the amount of usable system memory to match a DECR console and more.\nCauses some software to behave differently than on retail hardware.");
|
||||
const QString accurate_rsx_access = tr("Forces RSX pauses on SPU MFC_GETLLAR and SPU MFC_PUTLLUC operations.");
|
||||
const QString accurate_spu_dma = tr("Accurately processes SPU DMA operations.");
|
||||
const QString fixup_ppunj = tr("Legacy option. Fixup result vector values in Non-Java Mode in PPU LLVM.\nIf unsure, do not modify this setting.");
|
||||
const QString accurate_dfma = tr("Use accurate double-precision FMA instructions in PPU and SPU backends.\nWhile disabling it might give a decent performance boost if your CPU doesn't support FMA, it may also introduce subtle bugs that otherwise do not occur.\nYou shouldn't disable it if your CPU supports FMA.");
|
||||
const QString fixup_ppunj = tr("Legacy option. Fixup result vector values in Non-Java Mode in PPU LLVM.\nIf unsure, do not modify this setting.");
|
||||
const QString fixup_ppuvnan = tr("Fixup NaN results in vector instructions in PPU backends.\nIf unsure, do not modify this setting.");
|
||||
const QString silence_all_logs = tr("Stop writing any logs after game startup. Don't use unless you believe it's necessary.");
|
||||
const QString read_color = tr("Initializes render target memory using vm memory.");
|
||||
|
|
|
|||
|
|
@ -50,6 +50,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))
|
||||
{
|
||||
|
|
@ -64,7 +66,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;
|
||||
|
|
@ -120,7 +122,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;
|
||||
|
|
@ -130,7 +141,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;
|
||||
|
|
@ -143,14 +154,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"];
|
||||
|
|
@ -308,17 +317,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)
|
||||
{
|
||||
|
|
@ -513,6 +522,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);
|
||||
}
|
||||
}
|
||||
|
|
@ -51,6 +51,10 @@ static bool has_waitv()
|
|||
#include <random>
|
||||
#include <climits>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#include "asm.hpp"
|
||||
#include "endian.hpp"
|
||||
#include "tsc.hpp"
|
||||
|
|
|
|||
|
|
@ -372,6 +372,16 @@ void logs::listener::sync_all()
|
|||
}
|
||||
}
|
||||
|
||||
void logs::listener::shutdown_all()
|
||||
{
|
||||
std::lock_guard lock(g_mutex);
|
||||
|
||||
for (listener* lis = get_logger()->m_next.exchange(nullptr); lis;)
|
||||
{
|
||||
lis = lis->m_next.exchange(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void logs::listener::close_all_prematurely()
|
||||
{
|
||||
for (listener* lis = get_logger(); lis; lis = lis->m_next)
|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ namespace logs
|
|||
// Flush log to disk
|
||||
static void sync_all();
|
||||
|
||||
// Detach all listeners before controlled shutdown tears them down.
|
||||
static void shutdown_all();
|
||||
|
||||
// Close file handle after flushing to disk (hazardous)
|
||||
static void close_all_prematurely();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@
|
|||
#if defined(ARCH_ARM64)
|
||||
#include "Emu/CPU/Backends/AArch64/AArch64Common.h"
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "windows.h"
|
||||
#include "sysinfoapi.h"
|
||||
#include "subauth.h"
|
||||
#include "stringapiset.h"
|
||||
#include "util/dyn_lib.hpp"
|
||||
DYNAMIC_IMPORT("ntdll.dll", RtlGetVersion, NTSTATUS(OSVERSIONINFOW* lpVersionInformation));
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/resource.h>
|
||||
|
|
@ -77,91 +78,6 @@ namespace Darwin_ProcessInfo
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#if !defined(ARCH_X64)
|
||||
namespace utils
|
||||
{
|
||||
// Some helpers for sanity
|
||||
const auto read_reg_dword = [](HKEY hKey, std::string_view value_name) -> std::pair<bool, DWORD>
|
||||
{
|
||||
DWORD val = 0;
|
||||
DWORD len = sizeof(val);
|
||||
if (ERROR_SUCCESS != RegQueryValueExA(hKey, value_name.data(), nullptr, nullptr, reinterpret_cast<LPBYTE>(&val), &len))
|
||||
{
|
||||
return { false, 0 };
|
||||
}
|
||||
return { true, val };
|
||||
};
|
||||
|
||||
const auto read_reg_sz = [](HKEY hKey, std::string_view value_name) -> std::pair<bool, std::string>
|
||||
{
|
||||
constexpr usz MAX_SZ_LEN = 255;
|
||||
char sz[MAX_SZ_LEN + 1] {};
|
||||
DWORD sz_len = MAX_SZ_LEN;
|
||||
|
||||
// Safety; null terminate
|
||||
sz[0] = 0;
|
||||
sz[MAX_SZ_LEN] = 0;
|
||||
|
||||
// Read string
|
||||
if (ERROR_SUCCESS != RegQueryValueExA(hKey, value_name.data(), nullptr, nullptr, reinterpret_cast<LPBYTE>(sz), &sz_len))
|
||||
{
|
||||
return { false, "" };
|
||||
}
|
||||
|
||||
// Safety, force null terminator
|
||||
if (sz_len < MAX_SZ_LEN)
|
||||
{
|
||||
sz[sz_len] = 0;
|
||||
}
|
||||
return { true, sz };
|
||||
};
|
||||
|
||||
// Alternative way to read OS version using the registry.
|
||||
static std::string get_fallback_windows_version()
|
||||
{
|
||||
HKEY hKey;
|
||||
if (ERROR_SUCCESS != RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey))
|
||||
{
|
||||
return "Unknown Windows";
|
||||
}
|
||||
|
||||
// ProductName (SZ) - Actual windows install name e.g Windows 10 Pro)
|
||||
// CurrentMajorVersionNumber (DWORD) - e.g 10 for windows 10, 11 for windows 11
|
||||
// CurrentMinorVersionNumber (DWORD) - usually 0 for newer windows, pairs with major version
|
||||
// CurrentBuildNumber (SZ) - Windows build number, e.g 19045, used to identify different releases like 23H2, 24H2, etc
|
||||
// CurrentVersion (SZ) - NT kernel version, e.g 6.3 for Windows 10
|
||||
const auto [product_valid, product_name] = read_reg_sz(hKey, "ProductName");
|
||||
if (!product_valid)
|
||||
{
|
||||
RegCloseKey(hKey);
|
||||
return "Unknown Windows";
|
||||
}
|
||||
|
||||
const auto [check_major, version_major] = read_reg_dword(hKey, "CurrentMajorVersionNumber");
|
||||
const auto [check_minor, version_minor] = read_reg_dword(hKey, "CurrentMinorVersionNumber");
|
||||
const auto [check_build_no, build_no] = read_reg_sz(hKey, "CurrentBuildNumber");
|
||||
const auto [check_nt_ver, nt_ver] = read_reg_sz(hKey, "CurrentVersion");
|
||||
|
||||
// Close the registry key
|
||||
RegCloseKey(hKey);
|
||||
|
||||
std::string version_id = "Unknown";
|
||||
if (check_major && check_minor && check_build_no)
|
||||
{
|
||||
version_id = fmt::format("%u.%u.%s", version_major, version_minor, build_no);
|
||||
if (check_nt_ver)
|
||||
{
|
||||
version_id += " NT" + nt_ver;
|
||||
}
|
||||
}
|
||||
|
||||
return fmt::format("Operating system: %s, Version %s", product_name, version_id);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
bool utils::has_ssse3()
|
||||
{
|
||||
#if defined(ARCH_X64)
|
||||
|
|
@ -794,29 +710,15 @@ utils::OS_version utils::get_OS_version()
|
|||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
// GetVersionEx is deprecated, RtlGetVersion is kernel-mode only and AnalyticsInfo is UWP only.
|
||||
// So we're forced to read PEB instead to get Windows version info. It's ugly but works.
|
||||
#if defined(ARCH_X64)
|
||||
constexpr DWORD peb_offset = 0x60;
|
||||
const INT_PTR peb = __readgsqword(peb_offset);
|
||||
res.version_major = *reinterpret_cast<const DWORD*>(peb + 0x118);
|
||||
res.version_minor = *reinterpret_cast<const DWORD*>(peb + 0x11c);
|
||||
res.version_patch = *reinterpret_cast<const WORD*>(peb + 0x120);
|
||||
#else
|
||||
HKEY hKey;
|
||||
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey) == ERROR_SUCCESS)
|
||||
if (RtlGetVersion)
|
||||
{
|
||||
const auto [check_major, version_major] = read_reg_dword(hKey, "CurrentMajorVersionNumber");
|
||||
const auto [check_minor, version_minor] = read_reg_dword(hKey, "CurrentMinorVersionNumber");
|
||||
const auto [check_build, version_patch] = read_reg_sz(hKey, "CurrentBuildNumber");
|
||||
|
||||
if (check_major) res.version_major = version_major;
|
||||
if (check_minor) res.version_minor = version_minor;
|
||||
if (check_build) res.version_patch = stoi(version_patch);
|
||||
|
||||
RegCloseKey(hKey);
|
||||
OSVERSIONINFOW osvi{};
|
||||
osvi.dwOSVersionInfoSize = sizeof(osvi);
|
||||
RtlGetVersion(&osvi);
|
||||
res.version_major = osvi.dwMajorVersion;
|
||||
res.version_minor = osvi.dwMinorVersion;
|
||||
res.version_patch = osvi.dwBuildNumber;
|
||||
}
|
||||
#endif
|
||||
#elif defined (__APPLE__)
|
||||
res.version_major = Darwin_Version::getNSmajorVersion();
|
||||
res.version_minor = Darwin_Version::getNSminorVersion();
|
||||
|
|
@ -847,33 +749,25 @@ std::string utils::get_OS_version_string()
|
|||
{
|
||||
std::string output;
|
||||
#ifdef _WIN32
|
||||
// GetVersionEx is deprecated, RtlGetVersion is kernel-mode only and AnalyticsInfo is UWP only.
|
||||
// So we're forced to read PEB instead to get Windows version info. It's ugly but works.
|
||||
#if defined(ARCH_X64)
|
||||
constexpr DWORD peb_offset = 0x60;
|
||||
const INT_PTR peb = __readgsqword(peb_offset);
|
||||
const DWORD version_major = *reinterpret_cast<const DWORD*>(peb + 0x118);
|
||||
const DWORD version_minor = *reinterpret_cast<const DWORD*>(peb + 0x11c);
|
||||
const WORD build = *reinterpret_cast<const WORD*>(peb + 0x120);
|
||||
const UNICODE_STRING service_pack = *reinterpret_cast<const UNICODE_STRING*>(peb + 0x02E8);
|
||||
const u64 compatibility_mode = *reinterpret_cast<const u64*>(peb + 0x02C8); // Two DWORDs, major & minor version
|
||||
OSVERSIONINFOW osvi{};
|
||||
osvi.dwOSVersionInfoSize = sizeof(osvi);
|
||||
RtlGetVersion(&osvi);
|
||||
|
||||
const bool has_sp = service_pack.Length > 0;
|
||||
std::vector<char> holder(service_pack.Length + 1, '\0');
|
||||
const bool has_sp = osvi.szCSDVersion[0] != L'\0';
|
||||
std::vector<char> holder;
|
||||
if (has_sp)
|
||||
{
|
||||
WideCharToMultiByte(CP_UTF8, 0, service_pack.Buffer, service_pack.Length,
|
||||
static_cast<LPSTR>(holder.data()), static_cast<int>(holder.size()), nullptr, nullptr);
|
||||
const int len = WideCharToMultiByte(CP_UTF8, 0, osvi.szCSDVersion, -1,
|
||||
nullptr, 0, nullptr, nullptr);
|
||||
holder.resize(len);
|
||||
WideCharToMultiByte(CP_UTF8, 0, osvi.szCSDVersion, -1,
|
||||
holder.data(), len, nullptr, nullptr);
|
||||
}
|
||||
|
||||
fmt::append(output,
|
||||
"Operating system: Windows, Major: %lu, Minor: %lu, Build: %u, Service Pack: %s, Compatibility mode: %llu",
|
||||
version_major, version_minor, build, has_sp ? holder.data() : "none", compatibility_mode);
|
||||
#else
|
||||
// PEB cannot be easily accessed on ARM64, fall back to registry
|
||||
static const auto s_windows_version = utils::get_fallback_windows_version();
|
||||
return s_windows_version;
|
||||
#endif
|
||||
"Operating system: Windows, Major: %lu, Minor: %lu, Build: %lu, Service Pack: %s",
|
||||
osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber,
|
||||
has_sp ? holder.data() : "none");
|
||||
#elif defined (__APPLE__)
|
||||
const int major_version = Darwin_Version::getNSmajorVersion();
|
||||
const int minor_version = Darwin_Version::getNSminorVersion();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue