diff --git a/.ci/deploy-linux.sh b/.ci/deploy-linux.sh index 3309eb89e..5a1610edd 100755 --- a/.ci/deploy-linux.sh +++ b/.ci/deploy-linux.sh @@ -37,7 +37,7 @@ if [ "$DEPLOY_APPIMAGE" = "true" ]; then # Download translations mkdir -p "./AppDir/usr/translations" - ZIP_URL=$(curl -fsSL "https://api.github.com/repos/RPCS3/rpcs3_translations/releases/latest" \ + ZIP_URL=$(curl -fsSL --retry 3 --retry-delay 60 "https://api.github.com/repos/RPCS3/rpcs3_translations/releases/latest" \ | grep "browser_download_url" \ | grep "RPCS3-languages.zip" \ | cut -d '"' -f 4) @@ -45,7 +45,7 @@ if [ "$DEPLOY_APPIMAGE" = "true" ]; then echo "Failed to find RPCS3-languages.zip in the latest release. Continuing without translations." else echo "Downloading translations from: $ZIP_URL" - curl -L -o translations.zip "$ZIP_URL" || { + curl -fsSL --retry 3 --retry-delay 60 -o translations.zip "$ZIP_URL" || { echo "Failed to download translations.zip. Continuing without translations." exit 0 } diff --git a/.ci/deploy-mac.sh b/.ci/deploy-mac.sh index e72b5c2a3..4125e2379 100755 --- a/.ci/deploy-mac.sh +++ b/.ci/deploy-mac.sh @@ -29,7 +29,7 @@ rm -rf "rpcs3.app/Contents/Frameworks/QtPdf.framework" \ mkdir -p "rpcs3.app/Contents/translations" ZIP_URL="https://github.com/RPCS3/rpcs3_translations/releases/latest/download/RPCS3-languages.zip" echo "Downloading translations from: $ZIP_URL" -if curl -fsSL "$ZIP_URL" -o "translations.zip"; then +if curl -fsSL --retry 3 --retry-delay 60 "$ZIP_URL" -o "translations.zip"; then echo "Successfully downloaded translations." if unzip -o translations.zip -d "rpcs3.app/Contents/translations" >/dev/null 2>&1; then rm -f translations.zip diff --git a/.ci/deploy-windows-clang.sh b/.ci/deploy-windows-clang.sh index 04ba1bb20..38a80d53c 100644 --- a/.ci/deploy-windows-clang.sh +++ b/.ci/deploy-windows-clang.sh @@ -28,7 +28,7 @@ curl -fsSL 'https://api.rpcs3.net/config/?api=v1' | iconv -f ISO-8859-1 -t UTF-8 # Download translations mkdir -p ./bin/share/qt6/translations -ZIP_URL=$(curl -fsSL "https://api.github.com/repos/RPCS3/rpcs3_translations/releases/latest" \ +ZIP_URL=$(curl -fsSL --retry 3 --retry-delay 60 "https://api.github.com/repos/RPCS3/rpcs3_translations/releases/latest" \ | grep "browser_download_url" \ | grep "RPCS3-languages.zip" \ | cut -d '"' -f 4) @@ -36,7 +36,7 @@ if [ -z "$ZIP_URL" ]; then echo "Failed to find RPCS3-languages.zip in the latest release. Continuing without translations." else echo "Downloading translations from: $ZIP_URL" - curl -L -o translations.zip "$ZIP_URL" || { + curl -fsSL --retry 3 --retry-delay 60 -o translations.zip "$ZIP_URL" || { echo "Failed to download translations.zip. Continuing without translations." exit 0 } diff --git a/.ci/deploy-windows.sh b/.ci/deploy-windows.sh index 3c59391a6..80cbe005e 100755 --- a/.ci/deploy-windows.sh +++ b/.ci/deploy-windows.sh @@ -18,7 +18,7 @@ curl -fsSL 'https://api.rpcs3.net/config/?api=v1' | iconv -t UTF-8 1> ./bin/GuiC # Download translations mkdir -p ./bin/qt6/translations -ZIP_URL=$(curl -fsSL "https://api.github.com/repos/RPCS3/rpcs3_translations/releases/latest" \ +ZIP_URL=$(curl -fsSL --retry 3 --retry-delay 60 "https://api.github.com/repos/RPCS3/rpcs3_translations/releases/latest" \ | grep "browser_download_url" \ | grep "RPCS3-languages.zip" \ | cut -d '"' -f 4) @@ -26,7 +26,7 @@ if [ -z "$ZIP_URL" ]; then echo "Failed to find RPCS3-languages.zip in the latest release. Continuing without translations." else echo "Downloading translations from: $ZIP_URL" - curl -L -o translations.zip "$ZIP_URL" || { + curl -fsSL --retry 3 --retry-delay 60 -o translations.zip "$ZIP_URL" || { echo "Failed to download translations.zip. Continuing without translations." exit 0 } diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml index dcf1ef733..5e435daf2 100644 --- a/.github/workflows/rpcs3.yml +++ b/.github/workflows/rpcs3.yml @@ -30,23 +30,23 @@ jobs: matrix: include: - os: ubuntu-24.04 - docker_img: "rpcs3/rpcs3-ci-jammy:2.0" + docker_img: "rpcs3/rpcs3-ci-jammy:2.1" 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:2.0" + docker_img: "rpcs3/rpcs3-ci-jammy:2.1" build_sh: "/rpcs3/.ci/build-linux.sh" compiler: gcc - os: ubuntu-24.04-arm - docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:2.0" + docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:2.1" 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:2.0" + docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:2.1" build_sh: "/rpcs3/.ci/build-linux-aarch64.sh" compiler: gcc name: RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} diff --git a/3rdparty/discord-rpc/discord-rpc b/3rdparty/discord-rpc/discord-rpc index 3dc2c326c..fb04b1766 160000 --- a/3rdparty/discord-rpc/discord-rpc +++ b/3rdparty/discord-rpc/discord-rpc @@ -1 +1 @@ -Subproject commit 3dc2c326cb4dc5815c6069970c13154898f58d48 +Subproject commit fb04b1766bbbe7d2f47985b8b82fded0366efdf9 diff --git a/3rdparty/libsdl-org/SDL b/3rdparty/libsdl-org/SDL index f87239e71..147a8ee32 160000 --- a/3rdparty/libsdl-org/SDL +++ b/3rdparty/libsdl-org/SDL @@ -1 +1 @@ -Subproject commit f87239e71e42da91ca317a12eefb82cfbf3393eb +Subproject commit 147a8ee32dbf9ac02f3794964490687b6bbda1bc diff --git a/3rdparty/lsfg/armsx3_lsfg.map b/3rdparty/lsfg/armsx3_lsfg.map index 96e4f861e..2812d26fa 100644 --- a/3rdparty/lsfg/armsx3_lsfg.map +++ b/3rdparty/lsfg/armsx3_lsfg.map @@ -19,7 +19,6 @@ armsx3_lsfg_initialize; armsx3_lsfg_create_context_ahb; armsx3_lsfg_present; - armsx3_lsfg_present_fenced; armsx3_lsfg_destroy_context; armsx3_lsfg_wait_idle; armsx3_lsfg_finalize; diff --git a/3rdparty/lsfg/armsx3_lsfg_shim.cpp b/3rdparty/lsfg/armsx3_lsfg_shim.cpp index ae0743730..986503430 100644 --- a/3rdparty/lsfg/armsx3_lsfg_shim.cpp +++ b/3rdparty/lsfg/armsx3_lsfg_shim.cpp @@ -184,22 +184,6 @@ extern "C" int32_t armsx3_lsfg_create_context_ahb(void* in0, void* in1, void* co extern "C" int armsx3_lsfg_present(int32_t ctx, int in_sem, const int* out_sems, uint32_t out_count) { - // Forwarded rather than duplicated. Asking for no fence descriptor is exactly what this - // always did, and keeping one body means the two entry points cannot drift. - return armsx3_lsfg_present_fenced(ctx, in_sem, out_sems, out_count, nullptr); -} - -extern "C" int armsx3_lsfg_present_fenced(int32_t ctx, int in_sem, const int* out_sems, - uint32_t out_count, int* out_fence_fd) -{ - if (out_fence_fd) - { - // Written before anything that can fail. Every path out of here leaves the caller with a - // value it can act on, so it can never read an uninitialised int and close a descriptor - // belonging to something else -- which on Android is somebody's socket or an open asset. - *out_fence_fd = -1; - } - if (!g_initialized) { set_error("not initialized"); @@ -214,17 +198,13 @@ extern "C" int armsx3_lsfg_present_fenced(int32_t ctx, int in_sem, const int* ou outs.push_back(out_sems ? out_sems[i] : -1); } - // framegen writes the descriptor itself and leaves it at -1 when it cannot produce one, so a - // null out_fence_fd degrades to the plain present without a second code path here. if (g_performance) { - ARMSX3_LSFG_GUARD(LSFG_3_1P::presentContextFenced(ctx, in_sem, outs, out_fence_fd), - ARMSX3_LSFG_ERR_VULKAN) + ARMSX3_LSFG_GUARD(LSFG_3_1P::presentContext(ctx, in_sem, outs), ARMSX3_LSFG_ERR_VULKAN) } else { - ARMSX3_LSFG_GUARD(LSFG_3_1::presentContextFenced(ctx, in_sem, outs, out_fence_fd), - ARMSX3_LSFG_ERR_VULKAN) + ARMSX3_LSFG_GUARD(LSFG_3_1::presentContext(ctx, in_sem, outs), ARMSX3_LSFG_ERR_VULKAN) } return ARMSX3_LSFG_OK; diff --git a/3rdparty/lsfg/armsx3_lsfg_shim.h b/3rdparty/lsfg/armsx3_lsfg_shim.h index 38214a43d..05934475c 100644 --- a/3rdparty/lsfg/armsx3_lsfg_shim.h +++ b/3rdparty/lsfg/armsx3_lsfg_shim.h @@ -29,7 +29,7 @@ extern "C" { // Bump when anything below changes shape. The loader refuses a library whose version it does not // recognise, so a stale libarmsx3_lsfg.so on a user's device fails loudly at load instead of // quietly passing mismatched structs. -#define ARMSX3_LSFG_ABI_VERSION 3u +#define ARMSX3_LSFG_ABI_VERSION 2u // Mark the exported surface explicitly. // @@ -103,29 +103,6 @@ ARMSX3_LSFG_API int32_t armsx3_lsfg_create_context_ahb(void* in0, void* in1, voi // each out_sems[i] is signalled when output image i is ready. Pass -1 for an unused slot. ARMSX3_LSFG_API int armsx3_lsfg_present(int32_t ctx, int in_sem, const int* out_sems, uint32_t out_count); -// Generate frames for one presented pair, and hand back a fence for the result. -// -// Identical to armsx3_lsfg_present in every respect except that *out_fence_fd receives a sync file -// descriptor that becomes readable once the generation this call submitted has finished. The -// caller owns that fd and must close(2) it. -// -// This is the answer to armsx3_lsfg_wait_idle() below being the only completion signal on offer. -// framegen renders on its OWN VkDevice, so the caller cannot wait on its queues; before this -// entry point existed the only way to know the generated images were ready -- and, more -// importantly, that framegen had finished READING the caller's input images -- was a -// vkDeviceWaitIdle on framegen's device, once per presented frame. A sync fd can be waited on -// with poll(2) instead, which parks a thread rather than draining a GPU. -// -// *out_fence_fd is set to -1 whenever a descriptor is not available: an older library, a driver -// without VK_KHR_external_fence_fd, or work that had already completed by the time it was asked -// for. -1 is not an error and the return code is still ARMSX3_LSFG_OK -- the caller must fall -// back to armsx3_lsfg_wait_idle(), which is always correct. -// -// Added in ABI 3. Resolve it with dlsym rather than assuming it: this is the one entry point a -// caller can do without. -ARMSX3_LSFG_API int armsx3_lsfg_present_fenced(int32_t ctx, int in_sem, const int* out_sems, - uint32_t out_count, int* out_fence_fd); - ARMSX3_LSFG_API int armsx3_lsfg_destroy_context(int32_t ctx); // Read the user's own Lossless.dll and keep the shaders it contains. diff --git a/Utilities/geometry.h b/Utilities/geometry.h index a5881d1cd..6c41be9b4 100644 --- a/Utilities/geometry.h +++ b/Utilities/geometry.h @@ -724,15 +724,24 @@ struct coord3_base struct { T width, height, depth; }; }; - constexpr coord3_base() : position{}, size{} + constexpr coord3_base() + : position{}, size{} { } - constexpr coord3_base(const position3_base& position, const size3_base& size) : position{ position }, size{ size } + constexpr coord3_base(const position3_base& position, const size3_base& size) + : position{ position }, size{ size } { } - constexpr coord3_base(T x, T y, T z, T width, T height, T depth) : x{ x }, y{ y }, z{ z }, width{ width }, height{ height }, depth{ depth } + constexpr coord3_base(T x, T y, T z, T width, T height, T depth) + : x{ x }, y{ y }, z{ z }, width{ width }, height{ height }, depth{ depth } + { + } + + constexpr coord3_base(const area_base& area, T z = 0, T depth = 1) + : x{ area.x1 }, y{ area.y1 }, z{ z } + , width{ area.x2 - area.x1 }, height{ area.y2 - area.y1 }, depth{ depth } { } @@ -755,6 +764,51 @@ struct coord3_base { return{ static_cast(x), static_cast(y), static_cast(z), static_cast(width), static_cast(height), static_cast(depth) }; } + + void flip_horizontal() + requires std::is_signed_v + { + auto x2 = x + width; + x = x2; + width = -width; + } + + void flip_vertical() + requires std::is_signed_v + { + auto y2 = y + height; + y = y2; + height = -height; + } + + bool is_flipped() const + requires std::is_signed_v + { + return width < 0 || height < 0 || depth < 0; + } + + area_base to_area() const + { + return { x, y, x + width, y + height }; + } + + T abs_width() const + requires std::is_signed_v + { + return width < 0 ? -width : width; + } + + T abs_height() const + requires std::is_signed_v + { + return height < 0 ? -height : height; + } + + T abs_depth() const + requires std::is_signed_v + { + return depth < 0 ? -depth : depth; + } }; diff --git a/rpcs3/Crypto/unedat.cpp b/rpcs3/Crypto/unedat.cpp index 6fea6c185..728b6fc9f 100644 --- a/rpcs3/Crypto/unedat.cpp +++ b/rpcs3/Crypto/unedat.cpp @@ -14,19 +14,21 @@ LOG_CHANNEL(edat_log, "EDAT"); -void generate_key(int crypto_mode, int version, unsigned char *key_final, unsigned char *iv_final, unsigned char *key, unsigned char *iv) +void generate_key(int crypto_mode, int version, u8* key_final, u8* iv_final, const u8* key, const u8* iv) { - int mode = crypto_mode & 0xF0000000; - uchar temp_iv[16]{}; + const int mode = crypto_mode & 0xF0000000; switch (mode) { case 0x10000000: + { // Encrypted ERK. // Decrypt the key with EDAT_KEY + EDAT_IV and copy the original IV. + u8 temp_iv[16]{}; memcpy(temp_iv, EDAT_IV, 0x10); aescbc128_decrypt(const_cast(version ? EDAT_KEY_1 : EDAT_KEY_0), temp_iv, key, key_final, 0x10); memcpy(iv_final, iv, 0x10); break; + } case 0x20000000: // Default ERK. // Use EDAT_KEY and EDAT_IV. @@ -42,18 +44,20 @@ void generate_key(int crypto_mode, int version, unsigned char *key_final, unsign } } -void generate_hash(int hash_mode, int version, unsigned char *hash_final, unsigned char *hash) +void generate_hash(int hash_mode, int version, u8* hash_final, const u8* hash) { - int mode = hash_mode & 0xF0000000; - uchar temp_iv[16]{}; + const int mode = hash_mode & 0xF0000000; switch (mode) { case 0x10000000: + { // Encrypted HASH. // Decrypt the hash with EDAT_KEY + EDAT_IV. + u8 temp_iv[16]{}; memcpy(temp_iv, EDAT_IV, 0x10); aescbc128_decrypt(const_cast(version ? EDAT_KEY_1 : EDAT_KEY_0), temp_iv, hash, hash_final, 0x10); break; + } case 0x20000000: // Default HASH. // Use EDAT_HASH. @@ -67,13 +71,13 @@ void generate_hash(int hash_mode, int version, unsigned char *hash_final, unsign }; } -bool decrypt(int hash_mode, int crypto_mode, int version, unsigned char *in, unsigned char *out, usz length, unsigned char *key, unsigned char *iv, unsigned char *hash, unsigned char *test_hash) +bool decrypt(int hash_mode, int crypto_mode, int version, const u8* in, u8* out, usz length, const u8* key, const u8* iv, const u8* hash, const u8* test_hash) { // Setup buffers for key, iv and hash. - unsigned char key_final[0x10] = {}; - unsigned char iv_final[0x10] = {}; - unsigned char hash_final_10[0x10] = {}; - unsigned char hash_final_14[0x14] = {}; + u8 key_final[0x10] = {}; + u8 iv_final[0x10] = {}; + u8 hash_final_10[0x10] = {}; + u8 hash_final_14[0x14] = {}; // Generate crypto key and hash. generate_key(crypto_mode, version, key_final, iv_final, key, iv); @@ -116,25 +120,25 @@ bool decrypt(int hash_mode, int crypto_mode, int version, unsigned char *in, uns } // EDAT/SDAT functions. -std::tuple dec_section(unsigned char* metadata) +std::tuple dec_section(const u8* metadata) { std::array dec; - dec[0x00] = (metadata[0xC] ^ metadata[0x8] ^ metadata[0x10]); - dec[0x01] = (metadata[0xD] ^ metadata[0x9] ^ metadata[0x11]); - dec[0x02] = (metadata[0xE] ^ metadata[0xA] ^ metadata[0x12]); - dec[0x03] = (metadata[0xF] ^ metadata[0xB] ^ metadata[0x13]); - dec[0x04] = (metadata[0x4] ^ metadata[0x8] ^ metadata[0x14]); - dec[0x05] = (metadata[0x5] ^ metadata[0x9] ^ metadata[0x15]); - dec[0x06] = (metadata[0x6] ^ metadata[0xA] ^ metadata[0x16]); - dec[0x07] = (metadata[0x7] ^ metadata[0xB] ^ metadata[0x17]); - dec[0x08] = (metadata[0xC] ^ metadata[0x0] ^ metadata[0x18]); - dec[0x09] = (metadata[0xD] ^ metadata[0x1] ^ metadata[0x19]); - dec[0x0A] = (metadata[0xE] ^ metadata[0x2] ^ metadata[0x1A]); - dec[0x0B] = (metadata[0xF] ^ metadata[0x3] ^ metadata[0x1B]); - dec[0x0C] = (metadata[0x4] ^ metadata[0x0] ^ metadata[0x1C]); - dec[0x0D] = (metadata[0x5] ^ metadata[0x1] ^ metadata[0x1D]); - dec[0x0E] = (metadata[0x6] ^ metadata[0x2] ^ metadata[0x1E]); - dec[0x0F] = (metadata[0x7] ^ metadata[0x3] ^ metadata[0x1F]); + dec[0x0] = (metadata[0xC] ^ metadata[0x8] ^ metadata[0x10]); + dec[0x1] = (metadata[0xD] ^ metadata[0x9] ^ metadata[0x11]); + dec[0x2] = (metadata[0xE] ^ metadata[0xA] ^ metadata[0x12]); + dec[0x3] = (metadata[0xF] ^ metadata[0xB] ^ metadata[0x13]); + dec[0x4] = (metadata[0x4] ^ metadata[0x8] ^ metadata[0x14]); + dec[0x5] = (metadata[0x5] ^ metadata[0x9] ^ metadata[0x15]); + dec[0x6] = (metadata[0x6] ^ metadata[0xA] ^ metadata[0x16]); + dec[0x7] = (metadata[0x7] ^ metadata[0xB] ^ metadata[0x17]); + dec[0x8] = (metadata[0xC] ^ metadata[0x0] ^ metadata[0x18]); + dec[0x9] = (metadata[0xD] ^ metadata[0x1] ^ metadata[0x19]); + dec[0xA] = (metadata[0xE] ^ metadata[0x2] ^ metadata[0x1A]); + dec[0xB] = (metadata[0xF] ^ metadata[0x3] ^ metadata[0x1B]); + dec[0xC] = (metadata[0x4] ^ metadata[0x0] ^ metadata[0x1C]); + dec[0xD] = (metadata[0x5] ^ metadata[0x1] ^ metadata[0x1D]); + dec[0xE] = (metadata[0x6] ^ metadata[0x2] ^ metadata[0x1E]); + dec[0xF] = (metadata[0x7] ^ metadata[0x3] ^ metadata[0x1F]); u64 offset = read_from_ptr>(dec, 0); s32 length = read_from_ptr>(dec, 8); @@ -143,26 +147,26 @@ std::tuple dec_section(unsigned char* metadata) return std::make_tuple(offset, length, compression_end); } -u128 get_block_key(int block, NPD_HEADER *npd) +u128 get_block_key(int block, const NPD_HEADER& npd) { - unsigned char empty_key[0x10] = {}; - unsigned char *src_key = (npd->version <= 1) ? empty_key : npd->dev_hash; + const u8 empty_key[0x10] = {}; + const u8* src_key = (npd.version <= 1) ? empty_key : npd.dev_hash; u128 dest_key{}; std::memcpy(&dest_key, src_key, 0xC); - s32 swappedBlock = std::bit_cast>(block); - std::memcpy(reinterpret_cast(&dest_key) + 0xC, &swappedBlock, sizeof(swappedBlock)); + const s32 swappedBlock = std::bit_cast>(block); + std::memcpy(reinterpret_cast(&dest_key) + 0xC, &swappedBlock, sizeof(swappedBlock)); return dest_key; } -// for out data, allocate a buffer the size of 'edat->block_size' +// for out data, allocate a buffer the size of 'edat.block_size' // Also, set 'in file' to the beginning of the encrypted data, which may be offset if inside another file, but normally just reset to beginning of file // returns number of bytes written, -1 for error -s64 decrypt_block(const fs::file* in, u8* out, EDAT_HEADER *edat, NPD_HEADER *npd, u8* crypt_key, u32 block_num, u32 total_blocks, u64 size_left, bool is_out_buffer_aligned = false) +s64 decrypt_block(const fs::file& in, std::vector& out, const EDAT_HEADER& edat, const NPD_HEADER& npd, const u8* crypt_key, u32 block_num, u32 total_blocks, u64 size_left, bool is_out_buffer_aligned = false) { // Get metadata info and setup buffers. - const int metadata_section_size = ((edat->flags & EDAT_COMPRESSED_FLAG) != 0 || (edat->flags & EDAT_FLAG_0x20) != 0) ? 0x20 : 0x10; - const int metadata_offset = 0x100; + const u64 metadata_section_size = ((edat.flags & EDAT_COMPRESSED_FLAG) != 0 || (edat.flags & EDAT_FLAG_0x20) != 0) ? 0x20 : 0x10; + constexpr u64 metadata_offset = 0x100; u8 hash[0x10] = { 0 }; u8 key_result[0x10] = { 0 }; @@ -172,20 +176,20 @@ s64 decrypt_block(const fs::file* in, u8* out, EDAT_HEADER *edat, NPD_HEADER *np u64 metadata_sec_offset = 0; u64 length = 0; s32 compression_end = 0; - unsigned char empty_iv[0x10] = {}; + u8 empty_iv[0x10] = {}; // Decrypt the metadata. - if ((edat->flags & EDAT_COMPRESSED_FLAG) != 0) + if ((edat.flags & EDAT_COMPRESSED_FLAG) != 0) { metadata_sec_offset = metadata_offset + u64{block_num} * metadata_section_size; u8 metadata[0x20]{}; - in->read_at(metadata_sec_offset, metadata, 0x20); + in.read_at(metadata_sec_offset, metadata, 0x20); // If the data is compressed, decrypt the metadata. // NOTE: For NPD version 1 the metadata is not encrypted. - if (npd->version <= 1) + if (npd.version <= 1) { offset = read_from_ptr>(metadata, 0x10); length = read_from_ptr>(metadata, 0x18); @@ -198,13 +202,13 @@ s64 decrypt_block(const fs::file* in, u8* out, EDAT_HEADER *edat, NPD_HEADER *np std::memcpy(hash_result, metadata, 0x10); } - else if ((edat->flags & EDAT_FLAG_0x20) != 0) + else if ((edat.flags & EDAT_FLAG_0x20) != 0) { // If FLAG 0x20, the metadata precedes each data block. - metadata_sec_offset = metadata_offset + u64{block_num} * (metadata_section_size + edat->block_size); + metadata_sec_offset = metadata_offset + u64{block_num} * (metadata_section_size + edat.block_size); u8 metadata[0x20]{}; - in->read_at(metadata_sec_offset, metadata, 0x20); + in.read_at(metadata_sec_offset, metadata, 0x20); std::memcpy(hash_result, metadata, 0x14); @@ -213,22 +217,22 @@ s64 decrypt_block(const fs::file* in, u8* out, EDAT_HEADER *edat, NPD_HEADER *np hash_result[j] = metadata[j] ^ metadata[j + 0x10]; offset = metadata_sec_offset + 0x20; - length = edat->block_size; + length = edat.block_size; - if ((block_num == (total_blocks - 1)) && (edat->file_size % edat->block_size)) - length = static_cast(edat->file_size % edat->block_size); + if ((block_num == (total_blocks - 1)) && (edat.file_size % edat.block_size)) + length = static_cast(edat.file_size % edat.block_size); } else { metadata_sec_offset = metadata_offset + u64{block_num} * metadata_section_size; - in->read_at(metadata_sec_offset, hash_result, 0x10); + in.read_at(metadata_sec_offset, hash_result, 0x10); - offset = metadata_offset + u64{block_num} * edat->block_size + total_blocks * metadata_section_size; - length = edat->block_size; + offset = metadata_offset + u64{block_num} * edat.block_size + total_blocks * metadata_section_size; + length = edat.block_size; - if ((block_num == (total_blocks - 1)) && (edat->file_size % edat->block_size)) - length = static_cast(edat->file_size % edat->block_size); + if ((block_num == (total_blocks - 1)) && (edat.file_size % edat.block_size)) + length = static_cast(edat.file_size % edat.block_size); } // Locate the real data. @@ -240,23 +244,24 @@ s64 decrypt_block(const fs::file* in, u8* out, EDAT_HEADER *edat, NPD_HEADER *np std::vector dec_data_buf(length); // Try to use out buffer for file reads if no padding is needed instead of a new buffer - u8* enc_data = enc_data_buf.empty() ? out : enc_data_buf.data(); + std::vector* enc_data = enc_data_buf.empty() ? &out : &enc_data_buf; // Variable to avoid copies when possible - u8* dec_data = dec_data_buf.data(); + std::vector* dec_data = &dec_data_buf; std::memset(hash, 0, 0x10); std::memset(key_result, 0, 0x10); - in->read_at(offset, enc_data, length); + ensure(enc_data->size() >= length); + in.read_at(offset, enc_data->data(), length); // Generate a key for the current block. auto b_key = get_block_key(block_num, npd); // Encrypt the block key with the crypto key. - aesecb128_encrypt(crypt_key, reinterpret_cast(&b_key), key_result); + aesecb128_encrypt(crypt_key, reinterpret_cast(&b_key), key_result); - if ((edat->flags & EDAT_FLAG_0x10) != 0) + if ((edat.flags & EDAT_FLAG_0x10) != 0) { aesecb128_encrypt(crypt_key, key_result, hash); // If FLAG 0x10 is set, encrypt again to get the final hash. } @@ -266,25 +271,25 @@ s64 decrypt_block(const fs::file* in, u8* out, EDAT_HEADER *edat, NPD_HEADER *np } // Setup the crypto and hashing mode based on the extra flags. - int crypto_mode = ((edat->flags & EDAT_FLAG_0x02) == 0) ? 0x2 : 0x1; + int crypto_mode = ((edat.flags & EDAT_FLAG_0x02) == 0) ? 0x2 : 0x1; int hash_mode; - if ((edat->flags & EDAT_FLAG_0x10) == 0) + if ((edat.flags & EDAT_FLAG_0x10) == 0) hash_mode = 0x02; - else if ((edat->flags & EDAT_FLAG_0x20) == 0) + else if ((edat.flags & EDAT_FLAG_0x20) == 0) hash_mode = 0x04; else hash_mode = 0x01; - if ((edat->flags & EDAT_ENCRYPTED_KEY_FLAG) != 0) + if ((edat.flags & EDAT_ENCRYPTED_KEY_FLAG) != 0) { crypto_mode |= 0x10000000; hash_mode |= 0x10000000; } - const bool should_decompress = ((edat->flags & EDAT_COMPRESSED_FLAG) != 0) && compression_end; + const bool should_decompress = ((edat.flags & EDAT_COMPRESSED_FLAG) != 0) && compression_end; - if ((edat->flags & EDAT_DEBUG_DATA_FLAG) != 0) + if ((edat.flags & EDAT_DEBUG_DATA_FLAG) != 0) { // Reset the flags. crypto_mode |= 0x01000000; @@ -293,7 +298,7 @@ s64 decrypt_block(const fs::file* in, u8* out, EDAT_HEADER *edat, NPD_HEADER *np // Simply copy the data without the header or the footer. if (should_decompress) { - std::memcpy(dec_data, enc_data, length); + std::memcpy(dec_data->data(), enc_data->data(), length); } else { @@ -304,10 +309,10 @@ s64 decrypt_block(const fs::file* in, u8* out, EDAT_HEADER *edat, NPD_HEADER *np else { // IV is null if NPD version is 1 or 0. - u8* iv = (npd->version <= 1) ? empty_iv : npd->digest; + const u8* iv = (npd.version <= 1) ? empty_iv : npd.digest; // Call main crypto routine on this data block. - if (!decrypt(hash_mode, crypto_mode, (npd->version == 4), enc_data, dec_data, length, key_result, iv, hash, hash_result)) + if (!decrypt(hash_mode, crypto_mode, (npd.version == 4), enc_data->data(), dec_data->data(), length, key_result, iv, hash, hash_result)) { edat_log.error("Block at offset 0x%llx has invalid hash!", offset); return -1; @@ -317,7 +322,7 @@ s64 decrypt_block(const fs::file* in, u8* out, EDAT_HEADER *edat, NPD_HEADER *np // Apply additional de-compression if needed and write the decrypted data. if (should_decompress) { - const int res = decompress(out, dec_data, edat->block_size); + const int res = decompress(out.data(), dec_data->data(), edat.block_size); size_left -= res; @@ -333,44 +338,46 @@ s64 decrypt_block(const fs::file* in, u8* out, EDAT_HEADER *edat, NPD_HEADER *np return res; } - if (dec_data != out) + if (dec_data != &out) { - std::memcpy(out, dec_data, pad_length); + ensure(out.size() >= pad_length); + ensure(dec_data->size() >= pad_length); + std::memcpy(out.data(), dec_data->data(), pad_length); } return pad_length; } // set file offset to beginning before calling -bool check_data(u8* key, EDAT_HEADER* edat, NPD_HEADER* npd, const fs::file* f, bool verbose) +bool check_data(const u8* key, const EDAT_HEADER& edat, const NPD_HEADER& npd, const fs::file& f, bool verbose) { u8 header[0xA0] = { 0 }; u8 empty_header[0xA0] = { 0 }; u8 header_hash[0x10] = { 0 }; u8 metadata_hash[0x10] = { 0 }; - const u64 file_offset = f->pos(); + const u64 file_offset = f.pos(); // Check NPD version and flags. - if ((npd->version == 0) || (npd->version == 1)) + if ((npd.version == 0) || (npd.version == 1)) { - if (edat->flags & 0x7EFFFFFE) + if (edat.flags & 0x7EFFFFFE) { edat_log.error("Bad header flags!"); return false; } } - else if (npd->version == 2) + else if (npd.version == 2) { - if (edat->flags & 0x7EFFFFE0) + if (edat.flags & 0x7EFFFFE0) { edat_log.error("Bad header flags!"); return false; } } - else if ((npd->version == 3) || (npd->version == 4)) + else if ((npd.version == 3) || (npd.version == 4)) { - if (edat->flags & 0x7EFFFFC0) + if (edat.flags & 0x7EFFFFC0) { edat_log.error("Bad header flags!"); return false; @@ -383,17 +390,17 @@ bool check_data(u8* key, EDAT_HEADER* edat, NPD_HEADER* npd, const fs::file* f, } // Read in the file header. - f->read(header, 0xA0); + f.read(header, 0xA0); // Read in the header and metadata section hashes. - f->seek(file_offset + 0x90); - f->read(metadata_hash, 0x10); - f->read(header_hash, 0x10); + f.seek(file_offset + 0x90); + f.read(metadata_hash, 0x10); + f.read(header_hash, 0x10); // Setup the hashing mode and the crypto mode used in the file. - const int crypto_mode = 0x1; - int hash_mode = ((edat->flags & EDAT_ENCRYPTED_KEY_FLAG) == 0) ? 0x00000002 : 0x10000002; - if ((edat->flags & EDAT_DEBUG_DATA_FLAG) != 0) + constexpr int crypto_mode = 0x1; + int hash_mode = ((edat.flags & EDAT_ENCRYPTED_KEY_FLAG) == 0) ? 0x00000002 : 0x10000002; + if ((edat.flags & EDAT_DEBUG_DATA_FLAG) != 0) { hash_mode |= 0x01000000; @@ -402,17 +409,17 @@ bool check_data(u8* key, EDAT_HEADER* edat, NPD_HEADER* npd, const fs::file* f, } // Setup header key and iv buffers. - unsigned char header_key[0x10] = { 0 }; - unsigned char header_iv[0x10] = { 0 }; + u8 header_key[0x10] = { 0 }; + u8 header_iv[0x10] = { 0 }; // Test the header hash (located at offset 0xA0). - if (!decrypt(hash_mode, crypto_mode, (npd->version == 4), header, empty_header, 0xA0, header_key, header_iv, key, header_hash)) + if (!decrypt(hash_mode, crypto_mode, (npd.version == 4), header, empty_header, 0xA0, header_key, header_iv, key, header_hash)) { if (verbose) edat_log.warning("Header hash is invalid!"); // If the header hash test fails and the data is not DEBUG, then RAP/RIF/KLIC key is invalid. - if ((edat->flags & EDAT_DEBUG_DATA_FLAG) != EDAT_DEBUG_DATA_FLAG) + if ((edat.flags & EDAT_DEBUG_DATA_FLAG) != EDAT_DEBUG_DATA_FLAG) { edat_log.error("RAP/RIF/KLIC key is invalid!"); return false; @@ -420,24 +427,25 @@ bool check_data(u8* key, EDAT_HEADER* edat, NPD_HEADER* npd, const fs::file* f, } // Parse the metadata info. - const int metadata_section_size = ((edat->flags & EDAT_COMPRESSED_FLAG) != 0 || (edat->flags & EDAT_FLAG_0x20) != 0) ? 0x20 : 0x10; - if (((edat->flags & EDAT_COMPRESSED_FLAG) != 0)) + if ((edat.flags & EDAT_COMPRESSED_FLAG) != 0) { if (verbose) edat_log.warning("COMPRESSED data detected!"); } - if (!edat->block_size) + if (!edat.block_size) { return false; } - const usz block_num = utils::aligned_div(edat->file_size, edat->block_size); + const u64 metadata_section_size = ((edat.flags & EDAT_COMPRESSED_FLAG) != 0 || (edat.flags & EDAT_FLAG_0x20) != 0) ? 0x20 : 0x10; + + const usz block_num = utils::aligned_div(edat.file_size, edat.block_size); constexpr usz metadata_offset = 0x100; const usz metadata_size = utils::mul_saturate(metadata_section_size, block_num); u64 metadata_section_offset = metadata_offset; - if (utils::add_saturate(utils::add_saturate(file_offset, metadata_section_offset), metadata_size) > f->size()) + if (utils::add_saturate(utils::add_saturate(file_offset, metadata_section_offset), metadata_size) > f.size()) { return false; } @@ -452,43 +460,43 @@ bool check_data(u8* key, EDAT_HEADER* edat, NPD_HEADER* npd, const fs::file* f, const usz offset = file_offset + metadata_section_offset; // Read in the metadata. - f->read_at(offset, metadata.get() + bytes_read, metadata_section_size); + f.read_at(offset, metadata.get() + bytes_read, metadata_section_size); // Adjust sizes. bytes_read += metadata_section_size; - if (((edat->flags & EDAT_FLAG_0x20) != 0)) // Metadata block before each data block. - metadata_section_offset += (metadata_section_size + edat->block_size); + if (((edat.flags & EDAT_FLAG_0x20) != 0)) // Metadata block before each data block. + metadata_section_offset += (metadata_section_size + edat.block_size); else metadata_section_offset += metadata_section_size; } // Test the metadata section hash (located at offset 0x90). - if (!decrypt(hash_mode, crypto_mode, (npd->version == 4), metadata.get(), empty_metadata.get(), metadata_size, header_key, header_iv, key, metadata_hash)) + if (!decrypt(hash_mode, crypto_mode, (npd.version == 4), metadata.get(), empty_metadata.get(), metadata_size, header_key, header_iv, key, metadata_hash)) { if (verbose) edat_log.warning("Metadata section hash is invalid!"); } // Checking ECDSA signatures. - if ((edat->flags & EDAT_DEBUG_DATA_FLAG) == 0) + if ((edat.flags & EDAT_DEBUG_DATA_FLAG) == 0) { // Setup buffers. - unsigned char metadata_signature[0x28] = { 0 }; - unsigned char header_signature[0x28] = { 0 }; - unsigned char signature_hash[20] = { 0 }; - unsigned char signature_r[0x15] = { 0 }; - unsigned char signature_s[0x15] = { 0 }; - unsigned char zero_buf[0x15] = { 0 }; + u8 metadata_signature[0x28] = { 0 }; + u8 header_signature[0x28] = { 0 }; + u8 signature_hash[20] = { 0 }; + u8 signature_r[0x15] = { 0 }; + u8 signature_s[0x15] = { 0 }; + u8 zero_buf[0x15] = { 0 }; // Setup ECDSA curve and public key. ecdsa_set_curve(VSH_CURVE_P, VSH_CURVE_A, VSH_CURVE_B, VSH_CURVE_N, VSH_CURVE_GX, VSH_CURVE_GY); ecdsa_set_pub(VSH_PUB); // Read in the metadata and header signatures. - f->seek(0xB0); - f->read(metadata_signature, 0x28); - f->read(header_signature, 0x28); + f.seek(0xB0); + f.read(metadata_signature, 0x28); + f.read(header_signature, 0x28); // Checking metadata signature. // Setup signature r and s. @@ -503,13 +511,13 @@ bool check_data(u8* key, EDAT_HEADER* edat, NPD_HEADER* npd, const fs::file* f, else { // Setup signature hash. - if ((edat->flags & EDAT_FLAG_0x20) != 0) //Sony failed again, they used buffer from 0x100 with half size of real metadata. + if ((edat.flags & EDAT_FLAG_0x20) != 0) //Sony failed again, they used buffer from 0x100 with half size of real metadata. { const usz metadata_buf_size = block_num * 0x10; std::vector metadata_buf(metadata_buf_size); - f->read_at(file_offset + metadata_offset, metadata_buf.data(), metadata_buf_size); + f.read_at(file_offset + metadata_offset, metadata_buf.data(), metadata_buf_size); sha1(metadata_buf.data(), metadata_buf_size, signature_hash); } @@ -519,7 +527,7 @@ bool check_data(u8* key, EDAT_HEADER* edat, NPD_HEADER* npd, const fs::file* f, if (!ecdsa_verify(signature_hash, signature_r, signature_s)) { edat_log.warning("Metadata signature is invalid!"); - if (((edat->block_size + 0ull) * block_num) > 0x100000000) + if (((edat.block_size + 0ull) * block_num) > 0x100000000) edat_log.warning("*Due to large file size, metadata signature status may be incorrect!"); } } @@ -542,7 +550,7 @@ bool check_data(u8* key, EDAT_HEADER* edat, NPD_HEADER* npd, const fs::file* f, u8 header_buf[0xD8]{}; - f->read_at(file_offset, header_buf, 0xD8); + f.read_at(file_offset, header_buf, 0xD8); sha1(header_buf, 0xD8, signature_hash); if (!ecdsa_verify(signature_hash, signature_r, signature_s)) @@ -553,27 +561,27 @@ bool check_data(u8* key, EDAT_HEADER* edat, NPD_HEADER* npd, const fs::file* f, return true; } -bool validate_dev_klic(const u8* klicensee, NPD_HEADER *npd) +bool validate_dev_klic(const u8* klicensee, const NPD_HEADER& npd) { - if ((npd->license & 0x3) != 0x3) + if ((npd.license & 0x3) != 0x3) { return true; } - unsigned char dev[0x60]{}; + u8 dev[0x60]{}; // Build the dev buffer (first 0x60 bytes of NPD header in big-endian). - std::memcpy(dev, npd, 0x60); + std::memcpy(dev, &npd, 0x60); // Fix endianness. - s32 version = std::bit_cast>(npd->version); - s32 license = std::bit_cast>(npd->license); - s32 type = std::bit_cast>(npd->type); + s32 version = std::bit_cast>(npd.version); + s32 license = std::bit_cast>(npd.license); + s32 type = std::bit_cast>(npd.type); std::memcpy(dev + 0x4, &version, 4); std::memcpy(dev + 0x8, &license, 4); std::memcpy(dev + 0xC, &type, 4); - // Check for an empty dev_hash (can't validate if devklic is NULL); + // Check for an empty dev_hash (can't validate if devklic is nullptr); u128 klic; std::memcpy(&klic, klicensee, sizeof(klic)); @@ -581,13 +589,13 @@ bool validate_dev_klic(const u8* klicensee, NPD_HEADER *npd) u128 key = klic ^ std::bit_cast(NP_OMAC_KEY_2); // Hash with generated key and compare with dev_hash. - return cmac_hash_compare(reinterpret_cast(&key), 0x10, dev, 0x60, npd->dev_hash, 0x10); + return cmac_hash_compare(reinterpret_cast(&key), 0x10, dev, 0x60, npd.dev_hash, 0x10); } -bool validate_npd_hashes(std::string_view file_name, const u8* klicensee, NPD_HEADER* npd, EDAT_HEADER* edat, bool verbose) +bool validate_npd_hashes(std::string_view file_name, const u8* klicensee, const NPD_HEADER& npd, const EDAT_HEADER& edat, bool verbose) { // Ignore header validation in DEBUG data. - if (edat->flags & EDAT_DEBUG_DATA_FLAG) + if (edat.flags & EDAT_DEBUG_DATA_FLAG) { return true; } @@ -609,7 +617,7 @@ bool validate_npd_hashes(std::string_view file_name, const u8* klicensee, NPD_HE std::unique_ptr buf_upper(new u8[buf_len]); // Build the title buffer (content_id + file_name). - std::memcpy(buf.get(), npd->content_id, 0x30); + std::memcpy(buf.get(), npd.content_id, 0x30); std::memcpy(buf.get() + 0x30, file_name.data(), file_name.size()); std::memcpy(buf_lower.get(), buf.get(), buf_len); @@ -627,9 +635,9 @@ bool validate_npd_hashes(std::string_view file_name, const u8* klicensee, NPD_HE // Hash with NPDRM_OMAC_KEY_3 and compare with title_hash. // Try to ignore case sensivity with file extension const bool title_hash_result = - cmac_hash_compare(const_cast(NP_OMAC_KEY_3), 0x10, buf.get(), buf_len, npd->title_hash, 0x10) || - cmac_hash_compare(const_cast(NP_OMAC_KEY_3), 0x10, buf_lower.get(), buf_len, npd->title_hash, 0x10) || - cmac_hash_compare(const_cast(NP_OMAC_KEY_3), 0x10, buf_upper.get(), buf_len, npd->title_hash, 0x10); + cmac_hash_compare(const_cast(NP_OMAC_KEY_3), 0x10, buf.get(), buf_len, npd.title_hash, 0x10) || + cmac_hash_compare(const_cast(NP_OMAC_KEY_3), 0x10, buf_lower.get(), buf_len, npd.title_hash, 0x10) || + cmac_hash_compare(const_cast(NP_OMAC_KEY_3), 0x10, buf_upper.get(), buf_len, npd.title_hash, 0x10); if (verbose) { @@ -644,8 +652,8 @@ bool validate_npd_hashes(std::string_view file_name, const u8* klicensee, NPD_HE void read_npd_edat_header(const fs::file* input, NPD_HEADER& NPD, EDAT_HEADER& EDAT) { - char npd_header[0x80]{}; - char edat_header[0x10]{}; + u8 npd_header[0x80]{}; + u8 edat_header[0x10]{}; usz pos = input->pos(); pos += input->read_at(pos, npd_header, sizeof(npd_header)); @@ -663,7 +671,7 @@ void read_npd_edat_header(const fs::file* input, NPD_HEADER& NPD, EDAT_HEADER& E NPD.expire_time = read_from_ptr>(npd_header, 120); EDAT.flags = read_from_ptr>(edat_header, 0); - EDAT.block_size = read_from_ptr>(edat_header, 4); + EDAT.block_size = read_from_ptr>(edat_header, 4); EDAT.file_size = read_from_ptr>(edat_header, 8); } @@ -674,7 +682,7 @@ u128 GetEdatRifKeyFromRapFile(const fs::file& rap_file) rap_file.read(rapkey); - rap_to_rif(reinterpret_cast(&rapkey), reinterpret_cast(&rifkey)); + rap_to_rif(reinterpret_cast(&rapkey), reinterpret_cast(&rifkey)); return rifkey; } @@ -703,7 +711,7 @@ bool VerifyEDATHeaderWithKLicense(const fs::file& input, std::string_view input_ // Perform header validation (EDAT only). char real_file_name[CRYPTO_MAX_PATH]{}; extract_file_name(input_file_name, real_file_name); - if (!validate_npd_hashes(real_file_name, custom_klic, &NPD, &EDAT, false)) + if (!validate_npd_hashes(real_file_name, custom_klic, NPD, EDAT, false)) { edat_log.error("NPD hash validation failed!"); return false; @@ -758,16 +766,16 @@ fs::file DecryptEDAT(const fs::file& input, const std::string& input_file_name, memcpy(&devklic, NP_PSP_KEY_2, 0x10); break; case 8: + { + if (custom_klic) + memcpy(&devklic, custom_klic, 0x10); + else { - if (custom_klic != NULL) - memcpy(&devklic, custom_klic, 0x10); - else - { - edat_log.error("Invalid custom klic!"); - return fs::file{}; - } - break; + edat_log.error("Invalid custom klic!"); + return fs::file{}; } + break; + } default: edat_log.error("Invalid mode!"); return fs::file{}; @@ -812,7 +820,7 @@ bool EDATADecrypter::ReadHeader() char real_file_name[CRYPTO_MAX_PATH]{}; extract_file_name(m_file_name, real_file_name); - if (!validate_npd_hashes(real_file_name, reinterpret_cast(&dec_key), &npdHeader, &edatHeader, false)) + if (!validate_npd_hashes(real_file_name, reinterpret_cast(&dec_key), npdHeader, edatHeader, false)) { edat_log.error("NPD hash validation failed!"); return true; @@ -860,7 +868,7 @@ bool EDATADecrypter::ReadHeader() //} file_size = edatHeader.file_size; - total_blocks = ::narrow(utils::aligned_div(edatHeader.file_size, edatHeader.block_size)); + total_blocks = (edatHeader.block_size == 0) ? 0 : ::narrow(utils::aligned_div(edatHeader.file_size, edatHeader.block_size)); // Try decrypting the first block instead u8 data_sample[1]; @@ -898,7 +906,7 @@ u64 EDATADecrypter::ReadData(u64 pos, u8* data, u64 size) for (u32 i = starting_block; i < ending_block; i++) { - u64 res = decrypt_block(&edata_file, data_buf.data(), &edatHeader, &npdHeader, reinterpret_cast(&dec_key), i, total_blocks, edatHeader.file_size, true); + u64 res = decrypt_block(edata_file, data_buf, edatHeader, npdHeader, reinterpret_cast(&dec_key), i, total_blocks, edatHeader.file_size, true); if (res == umax) { diff --git a/rpcs3/Crypto/unedat.h b/rpcs3/Crypto/unedat.h index cc91509a5..e049c44aa 100644 --- a/rpcs3/Crypto/unedat.h +++ b/rpcs3/Crypto/unedat.h @@ -54,7 +54,7 @@ struct NPD_HEADER struct EDAT_HEADER { s32 flags; - s32 block_size; + u32 block_size; u64 file_size; }; diff --git a/rpcs3/Crypto/unpkg.cpp b/rpcs3/Crypto/unpkg.cpp index 28fca43e4..f22d917a4 100644 --- a/rpcs3/Crypto/unpkg.cpp +++ b/rpcs3/Crypto/unpkg.cpp @@ -2,6 +2,7 @@ #include "aes.h" #include "sha1.h" #include "key_vault.h" +#include "util/asm.hpp" #include "util/logs.hpp" #include "Utilities/StrUtil.h" #include "Utilities/Thread.h" @@ -190,7 +191,8 @@ bool package_reader::read_header() m_file = fs::make_gather(std::move(filelist)); } - if (m_header.data_size + m_header.data_offset > m_header.pkg_size) + if ((m_header.data_size + m_header.data_offset) > m_header.pkg_size || + m_header.data_size > (u64{umax} - m_header.data_offset)) // Check for overflow { pkg_log.error("PKG data size mismatch (data_size=0x%llx, data_offset=0x%llx, file_size=0x%llx)", m_header.data_size, m_header.data_offset, m_header.pkg_size); return false; @@ -203,7 +205,7 @@ bool package_reader::read_metadata() { // Read title ID and use it as an installation directory m_install_dir.resize(9); - archive_read_block(55, &m_install_dir.front(), m_install_dir.size()); + archive_read_block(55, {reinterpret_cast(m_install_dir.data()), m_install_dir.size()}, m_install_dir.size()); // Read package metadata @@ -548,7 +550,7 @@ bool package_reader::read_entries(std::vector& entries) entries.clear(); entries.resize(m_header.file_count + BUF_PADDING / sizeof(PKGEntry) + 1); - const usz read_size = decrypt(0, m_header.file_count * sizeof(PKGEntry), m_header.pkg_platform == PKG_PLATFORM_TYPE_PSP_PSVITA ? PKG_AES_KEY2 : m_dec_key.data(), entries.data()); + const usz read_size = decrypt(0, m_header.file_count * sizeof(PKGEntry), m_header.pkg_platform == PKG_PLATFORM_TYPE_PSP_PSVITA ? PKG_AES_KEY2 : m_dec_key.data(), std::span{reinterpret_cast(entries.data()), entries.size() * sizeof(PKGEntry)}); if (read_size < m_header.file_count * sizeof(PKGEntry)) { @@ -620,7 +622,7 @@ bool package_reader::read_param_sfo() std::string name_buf(entry.name_size + BUF_PADDING, '\0'); - if (usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name_buf.data()); read_size < entry.name_size) + if (usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), std::span{reinterpret_cast(name_buf.data()), name_buf.size()}); read_size < entry.name_size) { pkg_log.error("PKG name could not be read (size=0x%x, offset=0x%x)", entry.name_size, entry.name_offset); continue; @@ -643,7 +645,7 @@ bool package_reader::read_param_sfo() data_buf.resize(block_size + BUF_PADDING); - if (decrypt(entry.file_offset + pos, block_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), data_buf.data()) != block_size) + if (decrypt(entry.file_offset + pos, block_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), data_buf) != block_size) { pkg_log.error("Failed to decrypt PARAM.SFO file"); return false; @@ -956,7 +958,7 @@ bool package_reader::fill_data(std::map& all_instal const bool is_psp = (entry.type & PKG_FILE_ENTRY_PSP) != 0u; - if (const usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name_buf.data()); read_size < entry.name_size) + if (const usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), std::span{reinterpret_cast(name_buf.data()), name_buf.size()}); read_size < entry.name_size) { num_failures++; pkg_log.error("PKG name could not be read (size=0x%x, offset=0x%x)", entry.name_size, entry.name_offset); @@ -1135,7 +1137,7 @@ void package_reader::extract_worker() const install_entry& m_entry; usz m_pos; - explicit pkg_file_reader(std::function read_func, const install_entry& entry) noexcept + explicit pkg_file_reader(std::function read_func, const install_entry& entry) noexcept : m_read_func(std::move(read_func)) , m_entry(entry) , m_pos(0) @@ -1204,6 +1206,9 @@ void package_reader::extract_worker() read_cache.clear(); + // 16MB buffer + std::vector buffer(std::min(entry.file_size, 1u << 24) + BUF_PADDING); + auto reader = std::make_unique([&, cache_off = u64{umax}](usz pos, void* ptr, usz size) mutable -> u64 { if (pos >= entry.file_size || !size) @@ -1211,6 +1216,7 @@ void package_reader::extract_worker() return 0; } + const usz original_size = size; size = std::min(entry.file_size - pos, size); u64 size_cache_end = 0; @@ -1245,7 +1251,7 @@ void package_reader::extract_worker() read_cache.resize(block_size + BUF_PADDING); cache_off = pos; - const usz advance_size = decrypt(entry.file_offset + pos, block_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), read_cache.data()); + const usz advance_size = decrypt(entry.file_offset + pos, block_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), read_cache); if (!advance_size) { @@ -1263,8 +1269,13 @@ void package_reader::extract_worker() while (read_size < size) { const u64 block_size = std::min(BUF_SIZE, size - read_size); + const u64 available_buffer_size = buffer.size() - read_size; - const usz advance_size = decrypt(entry.file_offset + pos, block_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), static_cast(ptr) + read_size); + ensure(buffer.data() == ptr); + ensure(buffer.size() == original_size + BUF_PADDING); + ensure(available_buffer_size >= block_size); + + const usz advance_size = decrypt(entry.file_offset + pos, block_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), std::span{static_cast(ptr) + read_size, available_buffer_size}); if (!advance_size) { @@ -1299,9 +1310,6 @@ void package_reader::extract_worker() break; } - // 16MB buffer - std::vector buffer(std::min(entry.file_size, 1u << 24) + BUF_PADDING); - while (usz read_size = final_data.read(buffer.data(), buffer.size() - BUF_PADDING)) { // Check what actually landed. @@ -1498,14 +1506,16 @@ u64 package_reader::archive_read(void* data_ptr, const u64 num_bytes) return m_file ? m_file.read(data_ptr, num_bytes) : 0; } -std::span package_reader::archive_read_block(u64 offset, void* data_ptr, u64 num_bytes) +std::span package_reader::archive_read_block(u64 offset, std::span dst, u64 num_bytes) { - const usz read_n = m_file.read_at(offset, data_ptr, num_bytes); + ensure(dst.size() >= num_bytes); - return {static_cast(data_ptr), read_n}; + const usz read_n = m_file.read_at(offset, dst.data(), num_bytes); + + return {reinterpret_cast(dst.data()), read_n}; } -usz package_reader::decrypt(u64 offset, u64 size, const uchar* key, void* local_buf) +usz package_reader::decrypt(u64 offset, u64 size, const uchar* key, std::span local_buf) { if (!m_is_valid) { @@ -1517,15 +1527,26 @@ usz package_reader::decrypt(u64 offset, u64 size, const uchar* key, void* local_ return 0; } + ensure(local_buf.size() >= size); + // Read the data and set available size const auto data_span = archive_read_block(m_header.data_offset + offset, local_buf, size); - ensure(data_span.data() == static_cast(local_buf)); + ensure(data_span.data() == static_cast(local_buf.data())); + ensure(data_span.size() <= size); - // Get block count - const u64 blocks = (data_span.size() + 15) / 16; - const auto out_data = reinterpret_cast(local_buf); + // Clear padding + if (data_span.size() < local_buf.size()) + { + std::memset(&local_buf[data_span.size()], 0, local_buf.size() - data_span.size()); + } - if (m_header.pkg_type == PKG_RELEASE_TYPE_DEBUG) + // Get block count. Round up. + const u64 blocks = utils::aligned_div(data_span.size(), sizeof(u128)); + const u64 read_size = blocks * sizeof(u128); + + switch (m_header.pkg_type) + { + case PKG_RELEASE_TYPE_DEBUG: { // Debug key be_t input[8] = @@ -1539,7 +1560,7 @@ usz package_reader::decrypt(u64 offset, u64 size, const uchar* key, void* local_ for (u64 i = 0; i < blocks; i++) { // Initialize stream cipher for current position - input[7] = offset / 16 + i; + input[7] = offset / sizeof(u128) + i; struct sha1_hash { @@ -1548,11 +1569,13 @@ usz package_reader::decrypt(u64 offset, u64 size, const uchar* key, void* local_ sha1(reinterpret_cast(input), sizeof(input), hash.data); - const u128 v = read_from_ptr_unsafe(out_data, i * 16); - write_to_ptr_unsafe(out_data, i * 16, v ^ read_from_ptr(hash.data)); + const u128 v = read_from_ptr(local_buf, i * sizeof(u128)); + write_to_ptr(local_buf, i * sizeof(u128), v ^ read_from_ptr(hash.data)); } + + break; } - else if (m_header.pkg_type == PKG_RELEASE_TYPE_RELEASE) + case PKG_RELEASE_TYPE_RELEASE: { aes_context ctx; @@ -1560,7 +1583,7 @@ usz package_reader::decrypt(u64 offset, u64 size, const uchar* key, void* local_ aes_setkey_enc(&ctx, key, 128); // Initialize stream cipher for start position - be_t input = m_header.klicensee.value() + offset / 16; + be_t input = m_header.klicensee.value() + offset / sizeof(u128); // Increment stream position for every block for (u64 i = 0; i < blocks; i++, input++) @@ -1569,19 +1592,25 @@ usz package_reader::decrypt(u64 offset, u64 size, const uchar* key, void* local_ aes_crypt_ecb(&ctx, AES_ENCRYPT, reinterpret_cast(&input), reinterpret_cast(&key)); - const u128 v = read_from_ptr_unsafe(out_data, i * 16); - write_to_ptr_unsafe(out_data, i * 16, v ^ key); + const u128 v = read_from_ptr(local_buf, i * sizeof(u128)); + write_to_ptr(local_buf, i * sizeof(u128), v ^ key); } + + break; } - else + default: { pkg_log.error("Unknown release type (0x%x)", m_header.pkg_type); + break; + } } - if (blocks * 16 != size) + if (read_size > size) { // Put NTS and other zeroes on unaligned reads - std::memset(out_data + size, 0, blocks * 16 - size); + const u64 pad_size = read_size - size; + ensure(local_buf.size() >= (size + pad_size)); + std::memset(&local_buf[size], 0, pad_size); } // Return the amount of data written in buf diff --git a/rpcs3/Crypto/unpkg.h b/rpcs3/Crypto/unpkg.h index 79af9fee2..76494fb7e 100644 --- a/rpcs3/Crypto/unpkg.h +++ b/rpcs3/Crypto/unpkg.h @@ -401,8 +401,8 @@ private: u64 archive_read(void* data_ptr, u64 num_bytes); bool set_install_path(); bool fill_data(std::map& all_install_entries); - std::span archive_read_block(u64 offset, void* data_ptr, u64 num_bytes); - usz decrypt(u64 offset, u64 size, const uchar* key, void* local_buf); + std::span archive_read_block(u64 offset, std::span dst, u64 num_bytes); + usz decrypt(u64 offset, u64 size, const uchar* key, std::span local_buf); void extract_worker(); std::deque m_install_entries; diff --git a/rpcs3/Crypto/unself.cpp b/rpcs3/Crypto/unself.cpp index 96feb2c37..4a2e1b7c7 100644 --- a/rpcs3/Crypto/unself.cpp +++ b/rpcs3/Crypto/unself.cpp @@ -90,7 +90,7 @@ inline void Write64(const fs::file& f, const be_t data) f.write(&data, sizeof(data)); } -void WriteEhdr(const fs::file& f, Elf64_Ehdr& ehdr) +void WriteEhdr(const fs::file& f, const Elf64_Ehdr& ehdr) { Write32(f, ehdr.e_magic); Write8(f, ehdr.e_class); @@ -113,7 +113,7 @@ void WriteEhdr(const fs::file& f, Elf64_Ehdr& ehdr) Write16(f, ehdr.e_shstrndx); } -void WritePhdr(const fs::file& f, Elf64_Phdr& phdr) +void WritePhdr(const fs::file& f, const Elf64_Phdr& phdr) { Write32(f, phdr.p_type); Write32(f, phdr.p_flags); @@ -125,7 +125,7 @@ void WritePhdr(const fs::file& f, Elf64_Phdr& phdr) Write64(f, phdr.p_align); } -void WriteShdr(const fs::file& f, Elf64_Shdr& shdr) +void WriteShdr(const fs::file& f, const Elf64_Shdr& shdr) { Write32(f, shdr.sh_name); Write32(f, shdr.sh_type); @@ -139,7 +139,7 @@ void WriteShdr(const fs::file& f, Elf64_Shdr& shdr) Write64(f, shdr.sh_entsize); } -void WriteEhdr(const fs::file& f, Elf32_Ehdr& ehdr) +void WriteEhdr(const fs::file& f, const Elf32_Ehdr& ehdr) { Write32(f, ehdr.e_magic); Write8(f, ehdr.e_class); @@ -162,7 +162,7 @@ void WriteEhdr(const fs::file& f, Elf32_Ehdr& ehdr) Write16(f, ehdr.e_shstrndx); } -void WritePhdr(const fs::file& f, Elf32_Phdr& phdr) +void WritePhdr(const fs::file& f, const Elf32_Phdr& phdr) { Write32(f, phdr.p_type); Write32(f, phdr.p_offset); @@ -174,7 +174,7 @@ void WritePhdr(const fs::file& f, Elf32_Phdr& phdr) Write32(f, phdr.p_align); } -void WriteShdr(const fs::file& f, Elf32_Shdr& shdr) +void WriteShdr(const fs::file& f, const Elf32_Shdr& shdr) { Write32(f, shdr.sh_name); Write32(f, shdr.sh_type); @@ -683,7 +683,7 @@ bool SCEDecrypter::LoadMetadata(const u8 erk[32], const u8 riv[16]) // Load the metadata section headers. meta_shdr.clear(); - for (unsigned int i = 0; i < meta_hdr.section_count; i++) + for (u32 i = 0; i < meta_hdr.section_count; i++) { const usz shdr_offset = sizeof(meta_hdr) + sizeof(MetadataSectionHeader) * i; ensure(metadata_headers.size() > shdr_offset); @@ -693,10 +693,11 @@ bool SCEDecrypter::LoadMetadata(const u8 erk[32], const u8 riv[16]) } // Copy the decrypted data keys. - data_keys.resize(meta_hdr.key_count * 0x10); + data_keys.resize(static_cast(meta_hdr.key_count) * 0x10); const usz data_keys_offset = sizeof(meta_hdr) + meta_hdr.section_count * sizeof(MetadataSectionHeader); ensure(metadata_headers.size() >= (data_keys_offset + data_keys.size())); + ensure(data_keys.size() <= (usz{umax} - data_keys_offset)); // Check for overflow std::memcpy(data_keys.data(), metadata_headers.data() + data_keys_offset, data_keys.size()); return true; @@ -708,9 +709,9 @@ bool SCEDecrypter::DecryptData() usz data_buf_length = 0; // Calculate the total data size. - for (unsigned int i = 0; i < meta_hdr.section_count; i++) + for (const MetadataSectionHeader& hdr : meta_shdr) { - data_buf_length += ::narrow(meta_shdr[i].data_size); + data_buf_length += ::narrow(hdr.data_size); } // Allocate a buffer to store decrypted data. @@ -719,52 +720,55 @@ bool SCEDecrypter::DecryptData() // Set initial offset. u32 data_buf_offset = 0; - // Parse the metadata section headers to find the offsets of encrypted data. - for (unsigned int i = 0; i < meta_hdr.section_count; i++) - { - usz ctr_nc_off = 0; - u8 ctr_stream_block[0x10]; - u8 data_key[0x10]; - u8 data_iv[0x10]; + // Tmp buffer + std::vector buf; + u8 ctr_stream_block[0x10]; + // Parse the metadata section headers to find the offsets of encrypted data. + for (const MetadataSectionHeader& hdr : meta_shdr) + { // Check if this is an encrypted section. - if (meta_shdr[i].encrypted == 3) + if (hdr.encrypted == 3) { // Make sure the key and iv are not out of boundaries. - if ((meta_shdr[i].key_idx <= meta_hdr.key_count - 1) && (meta_shdr[i].iv_idx <= meta_hdr.key_count)) + if ((hdr.key_idx < meta_hdr.key_count) && (hdr.iv_idx <= meta_hdr.key_count)) { // Get the key and iv from the previously stored key buffer. - std::memcpy(data_key, data_keys.data() + meta_shdr[i].key_idx * 0x10, 0x10); - std::memcpy(data_iv, data_keys.data() + meta_shdr[i].iv_idx * 0x10, 0x10); - - // Allocate a buffer to hold the data. - auto buf = std::make_unique(meta_shdr[i].data_size); + const std::array data_key = read_from_ptr>(data_keys, static_cast(hdr.key_idx) * 0x10); + std::array data_iv = read_from_ptr>(data_keys, static_cast(hdr.iv_idx) * 0x10); // Seek to the section data offset and read the encrypted data. - sce_f.seek(meta_shdr[i].data_offset); - sce_f.read(buf.get(), meta_shdr[i].data_size); + buf.resize(hdr.data_size); + sce_f.seek(hdr.data_offset); + sce_f.read(buf.data(), buf.size()); // Zero out our ctr nonce. std::memset(ctr_stream_block, 0, sizeof(ctr_stream_block)); // Perform AES-CTR encryption on the data blocks. - aes_setkey_enc(&aes, data_key, 128); - aes_crypt_ctr(&aes, meta_shdr[i].data_size, &ctr_nc_off, data_iv, ctr_stream_block, buf.get(), buf.get()); + usz ctr_nc_off = 0; + aes_setkey_enc(&aes, data_key.data(), 128); + aes_crypt_ctr(&aes, buf.size(), &ctr_nc_off, data_iv.data(), ctr_stream_block, buf.data(), buf.data()); // Copy the decrypted data. - std::memcpy(data_buf.data() + data_buf_offset, buf.get(), meta_shdr[i].data_size); + ensure(data_buf.size() >= (buf.size() + data_buf_offset)); + ensure(buf.size() <= (usz{umax} - static_cast(data_buf_offset))); // Check for overflow + std::memcpy(data_buf.data() + data_buf_offset, buf.data(), buf.size()); } } else { - auto buf = std::make_unique(meta_shdr[i].data_size); - sce_f.seek(meta_shdr[i].data_offset); - sce_f.read(buf.get(), meta_shdr[i].data_size); - std::memcpy(data_buf.data() + data_buf_offset, buf.get(), meta_shdr[i].data_size); + buf.resize(hdr.data_size); + sce_f.seek(hdr.data_offset); + sce_f.read(buf.data(), buf.size()); + + ensure(data_buf.size() >= (buf.size() + data_buf_offset)); + ensure(buf.size() <= (usz{umax} - static_cast(data_buf_offset))); // Check for overflow + std::memcpy(data_buf.data() + data_buf_offset, buf.data(), buf.size()); } // Advance the buffer's offset. - data_buf_offset += ::narrow(meta_shdr[i].data_size); + data_buf_offset += ::narrow(hdr.data_size); } return true; @@ -779,9 +783,8 @@ std::vector SCEDecrypter::MakeFile() u32 data_buf_offset = 0; // Write data. - for (u32 i = 0; i < meta_hdr.section_count; i++) + for (const MetadataSectionHeader& hdr : meta_shdr) { - const MetadataSectionHeader& hdr = meta_shdr[i]; const u8* src = data_buf.data() + data_buf_offset; fs::file out_f = fs::make_stream>(); @@ -860,13 +863,13 @@ bool SELFDecrypter::LoadHeaders(bool isElf32, SelfAdditionalInfo* out_info) if (isElf32) { phdr32_arr.clear(); - if(elf32_hdr.e_phoff == 0 && elf32_hdr.e_phnum) + if (elf32_hdr.e_phoff == 0 && elf32_hdr.e_phnum) { self_log.error("ELF program header offset is null!"); return false; } self_f.seek(m_ext_hdr.phdr_offset); - for(u32 i = 0; i < elf32_hdr.e_phnum; ++i) + for (u32 i = 0; i < elf32_hdr.e_phnum; ++i) { phdr32_arr.emplace_back(); phdr32_arr.back().Load(self_f); @@ -895,7 +898,7 @@ bool SELFDecrypter::LoadHeaders(bool isElf32, SelfAdditionalInfo* out_info) m_seg_ext_hdr.clear(); self_f.seek(m_ext_hdr.segment_ext_hdr_offset); - for(u32 i = 0; i < (isElf32 ? elf32_hdr.e_phnum : elf64_hdr.e_phnum); ++i) + for (u32 i = 0; i < (isElf32 ? elf32_hdr.e_phnum : elf64_hdr.e_phnum); ++i) { if (self_f.pos() >= self_size) { @@ -961,7 +964,7 @@ bool SELFDecrypter::LoadHeaders(bool isElf32, SelfAdditionalInfo* out_info) self_f.seek(m_ext_hdr.shdr_offset); - for(u32 i = 0; i < elf32_hdr.e_shnum; ++i) + for (u32 i = 0; i < elf32_hdr.e_shnum; ++i) { shdr32_arr.emplace_back(); shdr32_arr.back().Load(self_f); @@ -978,7 +981,7 @@ bool SELFDecrypter::LoadHeaders(bool isElf32, SelfAdditionalInfo* out_info) self_f.seek(m_ext_hdr.shdr_offset); - for(u32 i = 0; i < elf64_hdr.e_shnum; ++i) + for (u32 i = 0; i < elf64_hdr.e_shnum; ++i) { shdr64_arr.emplace_back(); shdr64_arr.back().Load(self_f); @@ -1013,13 +1016,21 @@ void SELFDecrypter::ShowHeaders(bool isElf32) self_log.notice("----------------------------------------------------"); self_log.notice("ELF program headers"); self_log.notice("----------------------------------------------------"); - for(unsigned int i = 0; i < ((isElf32) ? phdr32_arr.size() : phdr64_arr.size()); i++) - isElf32 ? phdr32_arr[i].Show() : phdr64_arr[i].Show(); + if (isElf32) + { + for (const Elf32_Phdr& hdr : phdr32_arr) + hdr.Show(); + } + else + { + for (const Elf64_Phdr& hdr : phdr64_arr) + hdr.Show(); + } self_log.notice("----------------------------------------------------"); self_log.notice("Section info"); self_log.notice("----------------------------------------------------"); - for(unsigned int i = 0; i < m_seg_ext_hdr.size(); i++) - m_seg_ext_hdr[i].Show(); + for (const segment_ext_header& hdr : m_seg_ext_hdr) + hdr.Show(); self_log.notice("----------------------------------------------------"); self_log.notice("SCE version info"); self_log.notice("----------------------------------------------------"); @@ -1027,13 +1038,21 @@ void SELFDecrypter::ShowHeaders(bool isElf32) self_log.notice("----------------------------------------------------"); self_log.notice("Control info"); self_log.notice("----------------------------------------------------"); - for(unsigned int i = 0; i < m_supplemental_hdr_arr.size(); i++) - m_supplemental_hdr_arr[i].Show(); + for (const supplemental_header& hdr : m_supplemental_hdr_arr) + hdr.Show(); self_log.notice("----------------------------------------------------"); self_log.notice("ELF section headers"); self_log.notice("----------------------------------------------------"); - for(unsigned int i = 0; i < ((isElf32) ? shdr32_arr.size() : shdr64_arr.size()); i++) - isElf32 ? shdr32_arr[i].Show() : shdr64_arr[i].Show(); + if (isElf32) + { + for (const Elf32_Shdr& hdr : shdr32_arr) + hdr.Show(); + } + else + { + for (const Elf64_Shdr& hdr : shdr64_arr) + hdr.Show(); + } self_log.notice("----------------------------------------------------"); } @@ -1180,7 +1199,7 @@ bool SELFDecrypter::LoadMetadata(const u8* klic_key) // Load the metadata section headers. meta_shdr.clear(); - for (unsigned int i = 0; i < meta_hdr.section_count; i++) + for (u32 i = 0; i < meta_hdr.section_count; i++) { const usz shdr_offset = sizeof(meta_hdr) + sizeof(MetadataSectionHeader) * i; ensure(metadata_headers.size() > shdr_offset); @@ -1190,10 +1209,11 @@ bool SELFDecrypter::LoadMetadata(const u8* klic_key) } // Copy the decrypted data keys. - data_keys.resize(meta_hdr.key_count * 0x10); + data_keys.resize(static_cast(meta_hdr.key_count) * 0x10); const usz data_keys_offset = sizeof(meta_hdr) + meta_hdr.section_count * sizeof(MetadataSectionHeader); ensure(metadata_headers.size() >= (data_keys_offset + data_keys.size())); + ensure(data_keys.size() <= (usz{umax} - data_keys_offset)); // Check for overflow std::memcpy(data_keys.data(), metadata_headers.data() + data_keys_offset, data_keys.size()); return true; @@ -1205,12 +1225,12 @@ bool SELFDecrypter::DecryptData() usz data_buf_length = 0; // Calculate the total data size. - for (unsigned int i = 0; i < meta_hdr.section_count; i++) + for (const MetadataSectionHeader& hdr : meta_shdr) { - if (meta_shdr[i].encrypted == 3) + if (hdr.encrypted == 3) { - if ((meta_shdr[i].key_idx <= meta_hdr.key_count - 1) && (meta_shdr[i].iv_idx <= meta_hdr.key_count)) - data_buf_length += ::narrow(meta_shdr[i].data_size); + if ((hdr.key_idx < meta_hdr.key_count) && (hdr.iv_idx <= meta_hdr.key_count)) + data_buf_length += ::narrow(hdr.data_size); } } @@ -1220,43 +1240,43 @@ bool SELFDecrypter::DecryptData() // Set initial offset. u32 data_buf_offset = 0; - // Parse the metadata section headers to find the offsets of encrypted data. - for (unsigned int i = 0; i < meta_hdr.section_count; i++) - { - usz ctr_nc_off = 0; - u8 ctr_stream_block[0x10]; - u8 data_key[0x10]; - u8 data_iv[0x10]; + // Tmp buffer + std::vector buf; + u8 ctr_stream_block[0x10]; + // Parse the metadata section headers to find the offsets of encrypted data. + for (const MetadataSectionHeader& hdr : meta_shdr) + { // Check if this is an encrypted section. - if (meta_shdr[i].encrypted == 3) + if (hdr.encrypted == 3) { // Make sure the key and iv are not out of boundaries. - if((meta_shdr[i].key_idx <= meta_hdr.key_count - 1) && (meta_shdr[i].iv_idx <= meta_hdr.key_count)) + if ((hdr.key_idx < meta_hdr.key_count) && (hdr.iv_idx <= meta_hdr.key_count)) { // Get the key and iv from the previously stored key buffer. - std::memcpy(data_key, data_keys.data() + meta_shdr[i].key_idx * 0x10, 0x10); - std::memcpy(data_iv, data_keys.data() + meta_shdr[i].iv_idx * 0x10, 0x10); - - // Allocate a buffer to hold the data. - auto buf = std::make_unique(meta_shdr[i].data_size); + const std::array data_key = read_from_ptr>(data_keys, static_cast(hdr.key_idx) * 0x10); + std::array data_iv = read_from_ptr>(data_keys, static_cast(hdr.iv_idx) * 0x10); // Seek to the section data offset and read the encrypted data. - self_f.seek(meta_shdr[i].data_offset); - self_f.read(buf.get(), meta_shdr[i].data_size); + buf.resize(hdr.data_size); + self_f.seek(hdr.data_offset); + self_f.read(buf.data(), buf.size()); // Zero out our ctr nonce. std::memset(ctr_stream_block, 0, sizeof(ctr_stream_block)); // Perform AES-CTR encryption on the data blocks. - aes_setkey_enc(&aes, data_key, 128); - aes_crypt_ctr(&aes, meta_shdr[i].data_size, &ctr_nc_off, data_iv, ctr_stream_block, buf.get(), buf.get()); + usz ctr_nc_off = 0; + aes_setkey_enc(&aes, data_key.data(), 128); + aes_crypt_ctr(&aes, buf.size(), &ctr_nc_off, data_iv.data(), ctr_stream_block, buf.data(), buf.data()); // Copy the decrypted data. - std::memcpy(data_buf.data() + data_buf_offset, buf.get(), meta_shdr[i].data_size); + ensure(data_buf.size() >= (buf.size() + data_buf_offset)); + ensure(buf.size() <= (usz{umax} - static_cast(data_buf_offset))); // Check for overflow + std::memcpy(data_buf.data() + data_buf_offset, buf.data(), buf.size()); // Advance the buffer's offset. - data_buf_offset += ::narrow(meta_shdr[i].data_size); + data_buf_offset += ::narrow(hdr.data_size); } } } diff --git a/rpcs3/Crypto/unself.h b/rpcs3/Crypto/unself.h index cdda6d3f0..22eafb074 100644 --- a/rpcs3/Crypto/unself.h +++ b/rpcs3/Crypto/unself.h @@ -482,7 +482,7 @@ public: private: template - void WriteElf(fs::file& e, EHdr ehdr, SHdr shdr, PHdr phdr) + void WriteElf(fs::file& e, EHdr ehdr, const std::vector& shdrs, const std::vector& phdrs) { // Set initial offset. u32 data_buf_offset = 0; @@ -491,32 +491,35 @@ private: WriteEhdr(e, ehdr); // Write program headers. - for (u32 i = 0; i < ehdr.e_phnum; ++i) + for (const PHdr& phdr : phdrs) { - WritePhdr(e, phdr[i]); + WritePhdr(e, phdr); } - for (unsigned int i = 0; i < meta_hdr.section_count; i++) + // Tmp buffer + std::vector decomp_buf; + + for (const MetadataSectionHeader& hdr : meta_shdr) { // PHDR type. - if (meta_shdr[i].type == 2) + if (hdr.type == 2) { - // Decompress if necessary. - if (meta_shdr[i].compressed == 2) - { - const auto filesz = phdr[meta_shdr[i].program_idx].p_filesz; + const PHdr& phdr = ::at32(phdrs, hdr.program_idx); - // Create a pointer to a buffer for decompression. - std::unique_ptr decomp_buf(new u8[filesz]); + // Decompress if necessary. + if (hdr.compressed == 2) + { + const auto filesz = phdr.p_filesz; + decomp_buf.resize(filesz); // Create a buffer separate from data_buf to uncompress. - std::vector zlib_buf = data_buf; - - uLongf decomp_buf_length = ::narrow(filesz); - + ensure(data_buf.size() > data_buf_offset); + const std::vector zlib_buf(data_buf.cbegin() + data_buf_offset, data_buf.cend()); + // Use zlib uncompress on the new buffer. // decomp_buf_length changes inside the call to uncompress - const int rv = uncompress(decomp_buf.get(), &decomp_buf_length, zlib_buf.data() + data_buf_offset, ::size32(zlib_buf)); + uLongf decomp_buf_length = ::narrow(filesz); + const int rv = uncompress(decomp_buf.data(), &decomp_buf_length, zlib_buf.data(), ::size32(zlib_buf)); // Check for errors (TODO: Probably safe to remove this once these changes have passed testing.) switch (rv) @@ -527,19 +530,30 @@ private: default: break; } + ensure(decomp_buf_length <= decomp_buf.size()); + + // Warn if the final data is smaller than expected + if (decomp_buf_length < decomp_buf.size()) + { + self_log.warning("zlib uncompress returned a buffer of less size than expected (size=%d, expected=%d)", decomp_buf_length, decomp_buf.size()); + } + // Seek to the program header data offset and write the data. - e.seek(phdr[meta_shdr[i].program_idx].p_offset); - e.write(decomp_buf.get(), filesz); + e.seek(phdr.p_offset); + e.write(decomp_buf.data(), decomp_buf_length); } else { // Seek to the program header data offset and write the data. - e.seek(phdr[meta_shdr[i].program_idx].p_offset); - e.write(data_buf.data() + data_buf_offset, meta_shdr[i].data_size); + ensure(data_buf.size() >= (hdr.data_size + data_buf_offset)); + ensure(hdr.data_size <= (u64{umax} - static_cast(data_buf_offset))); // Check for overflow + + e.seek(phdr.p_offset); + e.write(data_buf.data() + data_buf_offset, hdr.data_size); } // Advance the data buffer offset by data size. - data_buf_offset += ::narrow(meta_shdr[i].data_size); + data_buf_offset += ::narrow(hdr.data_size); } } @@ -548,9 +562,9 @@ private: { e.seek(ehdr.e_shoff); - for (u32 i = 0; i < ehdr.e_shnum; ++i) + for (const SHdr& shdr : shdrs) { - WriteShdr(e, shdr[i]); + WriteShdr(e, shdr); } } } diff --git a/rpcs3/Emu/CPU/CPUTranslator.h b/rpcs3/Emu/CPU/CPUTranslator.h index 85540fee2..e11a27509 100644 --- a/rpcs3/Emu/CPU/CPUTranslator.h +++ b/rpcs3/Emu/CPU/CPUTranslator.h @@ -8,6 +8,10 @@ #include "Utilities/JIT.h" #include "util/v128.hpp" +#ifdef ARCH_X64 +#include +#endif + #ifdef _MSC_VER #pragma warning(push, 0) #else @@ -43,6 +47,13 @@ #pragma GCC diagnostic pop #endif +// MSVC can use intrinsics without compiling for its target feature +#if defined(_MSC_VER) || !defined(ARCH_X64) +#define GNUC_X64_TARGET(x) +#else +#define GNUC_X64_TARGET(x) [[gnu::target(x)]] +#endif + #include #include #include @@ -3807,13 +3818,25 @@ public: } template - value_t gf2p8affineqb(T1 a, T2 b, u8 c) + GNUC_X64_TARGET("gfni") value_t gf2p8affineqb(T1 a, T2 b, u8 c) { value_t result; const auto data0 = a.eval(m_ir); const auto data1 = b.eval(m_ir); +#ifdef ARCH_X64 + const auto [a_is_const, a_data] = get_const_vector(data0, -1); + const auto [b_is_const, b_data] = get_const_vector(data1, -1); + + if (a_is_const && b_is_const) + { + const auto affine = _mm_xor_si128(_mm_gf2p8affine_epi64_epi8(a_data, b_data, 0), _mm_set1_epi8(c)); + result.value = llvm::ConstantDataVector::get(m_context, llvm::ArrayRef(static_cast(affine)._u8.m_data, 16)); + return result; + } +#endif + const auto immediate = (llvm_const_int{c}); const auto imm8 = immediate.eval(m_ir); @@ -3829,6 +3852,24 @@ public: const auto data0 = a.eval(m_ir); const auto data1 = b.eval(m_ir); const auto data2 = c.eval(m_ir); + +#ifdef ARCH_X64 + const auto [a_is_const, a_data] = get_const_vector(data0, -1); + const auto [b_is_const, b_data] = get_const_vector(data1, -1); + const auto [c_is_const, c_data] = get_const_vector(data2, -1); + + if (a_is_const && b_is_const && c_is_const) + { + __m128i dpbusd; + if (utils::has_avx512_icl()) + dpbusd = _mm_wrapper_dpbusd_avx512vnni(a_data, b_data, c_data); + else + dpbusd = _mm_wrapper_dpbusd_avxvnni(a_data, b_data, c_data); + + result.value = llvm::ConstantDataVector::get(m_context, llvm::ArrayRef(static_cast(dpbusd)._u32.m_data, 4)); + return result; + } +#endif result.value = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::x86_avx512_vpdpbusd_128), {data0, m_ir->CreateBitCast(data1, get_type()), m_ir->CreateBitCast(data2, get_type())}); @@ -4197,13 +4238,27 @@ template } template - value_t vfixupimmps(T1 a, T2 b, T3 c, u8 d, u8 e) + GNUC_X64_TARGET("avx512vl") value_t vfixupimmps(T1 a, T2 b, T3 c, u8 d, u8 e) { value_t result; const auto data0 = a.eval(m_ir); const auto data1 = b.eval(m_ir); const auto data2 = c.eval(m_ir); + +#ifdef ARCH_X64 + const auto [a_is_const, a_data] = get_const_vector(data0, -1); + const auto [b_is_const, b_data] = get_const_vector(data1, -1); + const auto [c_is_const, c_data] = get_const_vector(data2, -1); + + if (a_is_const && b_is_const && c_is_const) + { + const auto vfixup = _mm_mask_fixupimm_ps(a_data, e, b_data, c_data, 0); // flag reporting doesn't matter for constants + result.value = llvm::ConstantDataVector::get(m_context, llvm::ArrayRef(static_cast(vfixup)._f.m_data, 4)); + return result; + } +#endif + const auto immediate = (llvm_const_int{d}); const auto imm32 = immediate.eval(m_ir); const auto immediate2 = (llvm_const_int{e}); @@ -4314,6 +4369,19 @@ private: // Custom intrinsic table std::unordered_map> m_intrinsics; +#ifdef ARCH_X64 + // LLVM uses the same intrinsic despite different encodings + GNUC_X64_TARGET("avx512vnni,avx512vl") __m128i _mm_wrapper_dpbusd_avx512vnni(__m128i a, __m128i b, __m128i c) + { + return _mm_dpbusd_epi32(a, b, c); + } + + GNUC_X64_TARGET("avxvnni") __m128i _mm_wrapper_dpbusd_avxvnni(__m128i a, __m128i b, __m128i c) + { + return _mm_dpbusd_avx_epi32(a, b, c); + } +#endif + public: // Call custom intrinsic by name template diff --git a/rpcs3/Emu/Cell/Modules/cellGifDec.cpp b/rpcs3/Emu/Cell/Modules/cellGifDec.cpp index 03566183e..be2befb8c 100644 --- a/rpcs3/Emu/Cell/Modules/cellGifDec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGifDec.cpp @@ -502,8 +502,8 @@ error_code cellGifDecDecodeData(vm::ptr mainHandle, vm::cptr + int width = 0, height = 0, actual_components = 0; + auto image = std::unique_ptr ( stbi_load_from_memory(gif.get(), ::narrow(fileSize), &width, &height, &actual_components, 4), &::free @@ -515,6 +515,7 @@ error_code cellGifDecDecodeData(vm::ptr mainHandle, vm::cptr(dataCtrlParam->outputBytesPerLine); constexpr char nComponents = 4; const u32 image_size = width * height * nComponents; + u8* src = image.get(); switch(current_outParam.outputColorSpace) { @@ -527,12 +528,12 @@ error_code cellGifDecDecodeData(vm::ptr mainHandle, vm::cptr mainHandle, vm::cptr mainHandle, vm::cptr(image_size); - uint* source_current = reinterpret_cast(image.get()); + uint* source_current = reinterpret_cast(src); uint* dest_current = img.get(); for (uint i = 0; i < image_size / nComponents; i++) { diff --git a/rpcs3/Emu/Cell/Modules/sceNp.cpp b/rpcs3/Emu/Cell/Modules/sceNp.cpp index e82491eac..1e09b8f49 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNp.cpp @@ -6800,6 +6800,11 @@ error_code sceNpSignalingActivateConnection(u32 ctx_id, vm::ptr npId, v return SCE_NP_SIGNALING_ERROR_NOT_INITIALIZED; } + if (!get_signaling_context(ctx_id)) + { + return SCE_NP_SIGNALING_ERROR_CTX_NOT_FOUND; + } + if (!npId || !conn_id) { return SCE_NP_SIGNALING_ERROR_INVALID_ARGUMENT; diff --git a/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp b/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp index dc6add3eb..3304d2b98 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp @@ -14,6 +14,7 @@ #include "sceNp.h" #include "sceNpTrophy.h" #include "cellSysutil.h" +#include "Emu/NP/np_handler.h" #include "Utilities/StrUtil.h" @@ -39,6 +40,7 @@ struct trophy_context_t SAVESTATE_INIT_POS(42); std::string trp_name; + SceNpCommunicationId comm_id{}; // set at CreateContext, not serialized std::unique_ptr tropusr; bool read_only = false; @@ -506,6 +508,7 @@ error_code sceNpTrophyCreateContext(vm::ptr context, vm::cptrtrp_name = name; + ctxt->comm_id = *commId; // stored for RPCN trophy sync/unlock ctxt->read_only = !!(options & SCE_NP_TROPHY_OPTIONS_CREATE_CONTEXT_READ_ONLY); *context = idm::last_id(); @@ -714,7 +717,46 @@ error_code sceNpTrophyRegisterContext(ppu_thread& ppu, u32 context, u32 handle, ensure(tropusr->Load(trophyUsrPath, trophyConfPath).success); - lock2.unlock(); + if (g_cfg.net.psn_status == np_psn_status::psn_rpcn) + { + const SceNpCommunicationId ctx_comm_id = ctxt->comm_id; + const u32 trophy_count = tropusr->GetTrophiesCount(); + + std::vector> local_unlocked; + local_unlocked.reserve(trophy_count); + for (u32 i = 0; i < trophy_count; i++) + { + if (tropusr->GetTrophyUnlockState(static_cast(i))) + { + local_unlocked.emplace_back( + static_cast(i), + static_cast(tropusr->GetTrophyTimestamp(static_cast(i)))); + } + } + + // Release lock before the blocking network call + lock2.unlock(); + + auto& np = g_fxo->get>(); + std::vector> srv_trophies = np.rpcn_trophy_sync(ctx_comm_id, local_unlocked); + + bool changed = false; + for (const auto& [tid, ts] : srv_trophies) + { + if (tid >= 0 && tid < static_cast(trophy_count) && ts >= 0 && !tropusr->GetTrophyUnlockState(tid)) + { + static_cast(tropusr->UnlockTrophy(tid, static_cast(ts), static_cast(ts))); + changed = true; + } + } + + if (changed && !tropusr->Save(trophyUsrPath)) + sceNpTrophy.error("sceNpTrophyRegisterContext(): Failed to save trophy data after RPCN sync"); + } + else + { + lock2.unlock(); + } lv2_obj::sleep(ppu); { @@ -1110,6 +1152,15 @@ error_code sceNpTrophyUnlockTrophy(ppu_thread& ppu, u32 context, u32 handle, s32 } } + if (g_cfg.net.psn_status == np_psn_status::psn_rpcn) + { + auto& np = g_fxo->get>(); + np.rpcn_trophy_unlock(ctxt->comm_id, trophyId, static_cast(tick->tick)); + + if (unlocked_platinum_id != SCE_NP_TROPHY_INVALID_TROPHY_ID) + np.rpcn_trophy_unlock(ctxt->comm_id, static_cast(unlocked_platinum_id), static_cast(tick->tick)); + } + return CELL_OK; } diff --git a/rpcs3/Emu/Cell/Modules/sys_net_.h b/rpcs3/Emu/Cell/Modules/sys_net_.h index 9ee344b97..e8cab7883 100644 --- a/rpcs3/Emu/Cell/Modules/sys_net_.h +++ b/rpcs3/Emu/Cell/Modules/sys_net_.h @@ -2,19 +2,6 @@ #include "Emu/Cell/lv2/sys_net.h" -struct sys_net_sockinfo_t -{ - be_t s; - be_t proto; - be_t recv_queue_length; - be_t send_queue_length; - sys_net_in_addr local_adr; - be_t local_port; - sys_net_in_addr remote_adr; - be_t remote_port; - be_t state; -}; - struct sys_net_sockinfo_ex_t { be_t s; diff --git a/rpcs3/Emu/Cell/PPUTranslator.cpp b/rpcs3/Emu/Cell/PPUTranslator.cpp index 253bde546..60b098357 100644 --- a/rpcs3/Emu/Cell/PPUTranslator.cpp +++ b/rpcs3/Emu/Cell/PPUTranslator.cpp @@ -4820,15 +4820,13 @@ void PPUTranslator::FCTIW(ppu_opcode_t op) const auto b = GetFpr(op.frb); #if defined(ARCH_X64) - // fix result saturation (0x80000000 -> 0x7fffffff) - // x86 returns the "integer indefinite" value 0x80000000 for anything it cannot represent, - // positive overflow included, so the result has to be flipped back for that case. const auto xormask = m_ir->CreateSExt(m_ir->CreateFCmpOGE(b, ConstantFP::get(GetType(), std::exp2l(31.))), GetType()); + + // fix result saturation (0x80000000 -> 0x7fffffff) SetFpr(op.frd, m_ir->CreateXor(xormask, Call(GetType(), "llvm.x86.sse2.cvtsd2si", m_ir->CreateInsertElement(GetUndef(), b, u64{0})))); #elif defined(ARCH_ARM64) - // No correction here. FCVTNS already saturates positive overflow to 0x7fffffff, which is what - // PowerPC produces; applying the x86 fixup on top would XOR that back into 0x80000000 and turn - // a saturated-high value into a saturated-low one. + // No correction: FCVTNS/FCVTZS already saturate positive overflow the way + // PowerPC does, and the x86 fixup would XOR a saturated-high value low again. SetFpr(op.frd, Call(GetType(), "llvm.aarch64.neon.fcvtns.i32.f64", b)); #endif @@ -4845,11 +4843,13 @@ void PPUTranslator::FCTIWZ(ppu_opcode_t op) const auto b = GetFpr(op.frb); #if defined(ARCH_X64) - // fix result saturation (0x80000000 -> 0x7fffffff); see FCTIW for why this is x86-only. const auto xormask = m_ir->CreateSExt(m_ir->CreateFCmpOGE(b, ConstantFP::get(GetType(), std::exp2l(31.))), GetType()); + + // fix result saturation (0x80000000 -> 0x7fffffff) SetFpr(op.frd, m_ir->CreateXor(xormask, Call(GetType(), "llvm.x86.sse2.cvttsd2si", m_ir->CreateInsertElement(GetUndef(), b, u64{0})))); #elif defined(ARCH_ARM64) - // FCVTZS saturates to 0x7fffffff on its own. + // No correction: FCVTNS/FCVTZS already saturate positive overflow the way + // PowerPC does, and the x86 fixup would XOR a saturated-high value low again. SetFpr(op.frd, Call(GetType(), "llvm.aarch64.neon.fcvtzs.i32.f64", b)); #endif } @@ -5128,11 +5128,13 @@ void PPUTranslator::FCTID(ppu_opcode_t op) const auto b = GetFpr(op.frb); #if defined(ARCH_X64) - // fix result saturation (0x8000000000000000 -> 0x7fffffffffffffff); see FCTIW, x86-only. const auto xormask = m_ir->CreateSExt(m_ir->CreateFCmpOGE(b, ConstantFP::get(GetType(), std::exp2l(63.))), GetType()); + + // fix result saturation (0x8000000000000000 -> 0x7fffffffffffffff) SetFpr(op.frd, m_ir->CreateXor(xormask, Call(GetType(), "llvm.x86.sse2.cvtsd2si64", m_ir->CreateInsertElement(GetUndef(), b, u64{0})))); #elif defined(ARCH_ARM64) - // FCVTNS saturates to 0x7fffffffffffffff on its own. + // No correction: FCVTNS/FCVTZS already saturate positive overflow the way + // PowerPC does, and the x86 fixup would XOR a saturated-high value low again. SetFpr(op.frd, Call(GetType(), "llvm.aarch64.neon.fcvtns.i64.f64", b)); #endif @@ -5150,11 +5152,13 @@ void PPUTranslator::FCTIDZ(ppu_opcode_t op) const auto b = GetFpr(op.frb); #if defined(ARCH_X64) - // fix result saturation (0x8000000000000000 -> 0x7fffffffffffffff); see FCTIW, x86-only. const auto xormask = m_ir->CreateSExt(m_ir->CreateFCmpOGE(b, ConstantFP::get(GetType(), std::exp2l(63.))), GetType()); + + // fix result saturation (0x8000000000000000 -> 0x7fffffffffffffff) SetFpr(op.frd, m_ir->CreateXor(xormask, Call(GetType(), "llvm.x86.sse2.cvttsd2si64", m_ir->CreateInsertElement(GetUndef(), b, u64{0})))); #elif defined(ARCH_ARM64) - // FCVTZS saturates to 0x7fffffffffffffff on its own. + // No correction: FCVTNS/FCVTZS already saturate positive overflow the way + // PowerPC does, and the x86 fixup would XOR a saturated-high value low again. SetFpr(op.frd, Call(GetType(), "llvm.aarch64.neon.fcvtzs.i64.f64", b)); #endif } diff --git a/rpcs3/Emu/Cell/RawSPUThread.cpp b/rpcs3/Emu/Cell/RawSPUThread.cpp index c77911223..c8349bcf2 100644 --- a/rpcs3/Emu/Cell/RawSPUThread.cpp +++ b/rpcs3/Emu/Cell/RawSPUThread.cpp @@ -299,13 +299,8 @@ bool spu_thread::write_reg(const u32 addr, const u32 value) { case MFC_LSA_offs: { - if (value >= SPU_LS_SIZE) - { - break; - } - std::lock_guard lock(mfc_prxy_mtx); - mfc_prxy_cmd.lsa = value; + mfc_prxy_cmd.lsa = value & (SPU_LS_SIZE - 1); mfc_prxy_write_state.lsa = true; return true; } diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index e29b1b29b..7faca7144 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -7422,7 +7422,7 @@ public: if (auto [a, b] = match_vrs(op.ra, op.rb); a || b) { - set_vr(op.rt4, select(sel_bool, get_vr(op.rb), get_vr(op.ra))); + set_vr(op.rt4, select(sel_bool, get_vr(op.rb), get_vr(op.ra)), nullptr, !(a && b)); return true; } @@ -7480,7 +7480,7 @@ public: { if (const auto [a_f64, b_f64] = match_vrs(op.ra, op.rb); a_f64 || b_f64) { - set_vr(op.rt4, select(noncast(c) != 0, get_vr(op.rb), get_vr(op.ra))); + set_vr(op.rt4, select(noncast(c) != 0, get_vr(op.rb), get_vr(op.ra)), nullptr, !(a_f64 && b_f64)); return; } @@ -7781,7 +7781,7 @@ public: // Calculate shuffle - bool shuf_zero_when_msb = false; + bool or_combine_safe = false; value_t ab_shuf; if (single_src) @@ -7793,7 +7793,7 @@ public: else { ab_shuf = eval(pshufb(single_src.value(), cv)); - shuf_zero_when_msb = true; + or_combine_safe = true; } } else if (a_is_splat && b_is_splat) @@ -7823,7 +7823,7 @@ public: ab_shuf = eval(select_by_bit4(c, a_shuf, b_shuf)); // pshufb zeros when the MSB is set - shuf_zero_when_msb = !(a_is_splat || b_is_splat); + or_combine_safe = !(a_is_splat || b_is_splat); } if (perm_only) @@ -7839,10 +7839,14 @@ public: { idx_consts = eval(splat(0)); } - else if (m_use_avx512_icl) + else if (m_use_gfni) { + // TODO: Due to vpblendvb, the pshufb OR combine path is one fewer micro-ops post Rocket Lake. Check if it is faster. const auto gfni = gf2p8affineqb(c, build(0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x40, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20), 0x7f); idx_consts = eval(select(noncast(gfni) >= 0, splat(0), gfni)); + + // Logic assumes that the MSB is always set + or_combine_safe = false; } else { @@ -7852,7 +7856,7 @@ public: // Combine shuffle and special index constants - if (shuf_zero_when_msb) + if (or_combine_safe) set_vr(op.rt4, ab_shuf | idx_consts); else set_vr(op.rt4, select(noncast(c) >= 0, ab_shuf, idx_consts)); diff --git a/rpcs3/Emu/Cell/lv2/sys_net.cpp b/rpcs3/Emu/Cell/lv2/sys_net.cpp index 19e90235f..9a328f8ea 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net.cpp @@ -236,6 +236,18 @@ void fmt_class_string::format(std::string& out, u64 arg) fmt::append(out, "%u.%u.%u.%u", data[0], data[1], data[2], data[3]); } +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + const auto& info = get_object(arg); + + fmt::append(out, "{ s=%d, proto=%s, recv_queue_length=%d, send_queue_length=%d, local=%s:%d, remote=%s:%d, state=%d }", + info.s, static_cast(info.proto.value()), info.recv_queue_length, info.send_queue_length, + np::ip_to_string(std::bit_cast(info.local_adr._s_addr)), info.local_port, + np::ip_to_string(std::bit_cast(info.remote_adr._s_addr)), info.remote_port, + info.state); +} + lv2_socket::lv2_socket(utils::serial& ar, lv2_socket_type _type) : family(ar) , type(_type) @@ -789,7 +801,9 @@ error_code sys_net_bnet_recvfrom(ppu_thread& ppu, s32 s, vm::ptr buf, u32 const auto sock = idm::check(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock) { - const auto success = sock.recvfrom(flags, len); + auto lock = sock.lock(); + + const auto success = sock.recvfrom(flags, len, false); if (success) { @@ -805,8 +819,6 @@ error_code sys_net_bnet_recvfrom(ppu_thread& ppu, s32 s, vm::ptr buf, u32 return true; } - auto lock = sock.lock(); - sock.poll_queue(idm::get_unlocked>(ppu.id), lv2_socket::poll_t::read, [&](bs_t events) -> bool { if (events & lv2_socket::poll_t::read) @@ -1245,8 +1257,8 @@ error_code sys_net_bnet_close(ppu_thread& ppu, s32 s) sock->close(); { - // Ensures the socket has no lingering copy from the network thread - std::lock_guard nw_lock(g_fxo->get().mutex_thread_loop); + // Ensures the socket has no lingering copy from the network threads + std::scoped_lock threads_lock(g_fxo->get().mutex_thread_loop, g_fxo->get().mutex_thread_loop); sock.reset(); } @@ -1265,7 +1277,7 @@ error_code sys_net_bnet_poll(ppu_thread& ppu, vm::ptr fds, s32 n } atomic_t signaled{0}; - + bool has_sockets = false; u64 timeout = ms < 0 ? 0 : ms * 1000ull; std::vector fds_buf; @@ -1276,6 +1288,7 @@ error_code sys_net_bnet_poll(ppu_thread& ppu, vm::ptr fds, s32 n lv2_obj::prepare_for_sleep(ppu); std::unique_lock nw_lock(g_fxo->get().mutex_thread_loop); + std::unique_lock p2p_lock(g_fxo->get().mutex_thread_loop); std::shared_lock lock(id_manager::g_mutex); std::vector<::pollfd> _fds(nfds); @@ -1295,6 +1308,7 @@ error_code sys_net_bnet_poll(ppu_thread& ppu, vm::ptr fds, s32 n if (auto sock = idm::check_unlocked(fds_buf[i].fd)) { + has_sockets = true; sock->poll(fds_buf[i], _fds[i]); #ifdef _WIN32 connecting[i] = sock->is_connecting(); @@ -1329,6 +1343,7 @@ error_code sys_net_bnet_poll(ppu_thread& ppu, vm::ptr fds, s32 n if (ms == 0 || signaled) { lock.unlock(); + p2p_lock.unlock(); nw_lock.unlock(); std::memcpy(fds.get_ptr(), fds_buf.data(), nfds * sizeof(sys_net_pollfd)); return not_an_error(signaled); @@ -1407,7 +1422,7 @@ error_code sys_net_bnet_poll(ppu_thread& ppu, vm::ptr fds, s32 n return {}; } - has_timedout = network_clear_queue(ppu); + has_timedout = network_clear_queue(ppu) || !has_sockets; clear_ppu_to_awake(ppu); ppu.state -= cpu_flag::signal; break; @@ -1454,6 +1469,7 @@ error_code sys_net_bnet_select(ppu_thread& ppu, s32 nfds, vm::ptrtv_sec * 1000000ull + _timeout->tv_usec; + bool has_sockets = false; if (nfds > 0 && nfds <= 1024) { @@ -1465,6 +1481,7 @@ error_code sys_net_bnet_select(ppu_thread& ppu, s32 nfds, vm::ptrget().mutex_thread_loop); + std::lock_guard p2p_lock(g_fxo->get().mutex_thread_loop); reader_lock lock(id_manager::g_mutex); std::vector<::pollfd> _fds(nfds); @@ -1487,6 +1504,7 @@ error_code sys_net_bnet_select(ppu_thread& ppu, s32 nfds, vm::ptrget().mutex_thread_loop); + std::scoped_lock threads_lock(g_fxo->get().mutex_thread_loop, g_fxo->get().mutex_thread_loop); const auto sock = idm::get_unlocked(static_cast(arg)); @@ -1808,8 +1826,9 @@ error_code sys_net_abort(ppu_thread& ppu, s32 type, u64 arg, s32 flags) sys_net.success("lv2_socket::handle_abort(): Closed socket %d", id); } - // Ensures the socket has no lingering copy from the network thread + // Ensures the socket has no lingering copy from the network threads g_fxo->get().mutex_thread_loop.lock_unlock(); + g_fxo->get().mutex_thread_loop.lock_unlock(); return not_an_error(::narrow(sockets.size()) - failed); } @@ -1825,6 +1844,16 @@ error_code sys_net_abort(ppu_thread& ppu, s32 type, u64 arg, s32 flags) return CELL_OK; } +struct net_infoctl_cmd_6_t +{ + be_t sock_id; + be_t zero_0; + be_t zero_1; + vm::bptr sock_info; + be_t n; + be_t zero_2; +}; + struct net_infoctl_cmd_9_t { be_t zero; @@ -1832,29 +1861,132 @@ struct net_infoctl_cmd_9_t // More (TODO) }; +static void net_write_sockinfo(s32 s, lv2_socket& sock, sys_net_sockinfo_t& info) +{ + info = {}; + info.s = s; + + switch (sock.get_type()) + { + case SYS_NET_SOCK_DGRAM: + case SYS_NET_SOCK_DGRAM_P2P: + info.proto = SYS_NET_IPPROTO_UDP; + break; + case SYS_NET_SOCK_STREAM: + case SYS_NET_SOCK_STREAM_P2P: + info.proto = SYS_NET_IPPROTO_TCP; + break; + default: + info.proto = static_cast(sock.get_protocol()); + break; + } + + if (const auto [res, sn_addr] = sock.getsockname(); res == CELL_OK) + { + const auto* addr_in = reinterpret_cast(&sn_addr); + info.local_adr._s_addr = addr_in->sin_addr; + info.local_port = addr_in->sin_port; + } + + if (const auto [res, sn_addr] = sock.getpeername(); res == CELL_OK) + { + const auto* addr_in = reinterpret_cast(&sn_addr); + info.remote_adr._s_addr = addr_in->sin_addr; + info.remote_port = addr_in->sin_port; + } + + sock.get_sockinfo(info); + + sys_net.trace("sys_net_infoctl(cmd=6): %s", info); +} + error_code sys_net_infoctl(ppu_thread& ppu, s32 cmd, vm::ptr arg) { ppu.state += cpu_flag::wait; - sys_net.todo("sys_net_infoctl(cmd=%d, arg=*0x%x)", cmd, arg); + if (cmd == 6 || cmd == 9) + sys_net.notice("sys_net_infoctl(cmd=%d, arg=*0x%x)", cmd, arg); + else + sys_net.todo("sys_net_infoctl(cmd=%d, arg=*0x%x)", cmd, arg); // TODO switch (cmd) { + case 6: + { + if (!arg) + { + return -SYS_NET_EINVAL; + } + + vm::ptr cmd_arg = vm::static_ptr_cast(arg); + + const vm::bptr sock_info = cmd_arg->sock_info; + const s32 sock_id = cmd_arg->sock_id; + const s32 max_infos = cmd_arg->n; + + sys_net.trace("cmd 6: sock_id: %d, sock_info: *0x%x, max_infos: %d", sock_id, sock_info, max_infos); + + std::scoped_lock threads_lock(g_fxo->get().mutex_thread_loop, g_fxo->get().mutex_thread_loop); + + if (sock_id == -1) + { + if (!sock_info) + { + return not_an_error(static_cast(idm::select([](u32, lv2_socket&) {}))); + } + + if (max_infos < 0) + { + return -SYS_NET_EINVAL; + } + + s32 num_infos = 0; + + idm::select([&](u32 id, lv2_socket& sock) + { + if (num_infos >= max_infos) + { + return; + } + + net_write_sockinfo(static_cast(id), sock, sock_info[num_infos]); + num_infos++; + }); + + return not_an_error(num_infos); + } + + if (!sock_info || max_infos < 1) + { + return -SYS_NET_EINVAL; + } + + const auto sock = idm::check(sock_id, [&](lv2_socket& sock) + { + net_write_sockinfo(sock_id, sock, sock_info[0]); + }); + + if (!sock) + { + return -SYS_NET_EBADF; + } + + break; + } case 9: { - constexpr auto nameserver = "nameserver \0"sv; + if (!arg) + { + return -SYS_NET_EINVAL; + } - char buffer[nameserver.size() + 80]{}; - std::memcpy(buffer, nameserver.data(), nameserver.size()); + auto& nph = g_fxo->get>(); + std::string nameserver = "nameserver " + np::ip_to_string(nph.get_dns_ip()); - auto& nph = g_fxo->get>(); - const auto dns_str = np::ip_to_string(nph.get_dns_ip()); - std::memcpy(buffer + nameserver.size() - 1, dns_str.data(), dns_str.size()); - - std::string_view name{buffer}; - vm::static_ptr_cast(arg)->zero = 0; - std::memcpy(vm::static_ptr_cast(arg)->server_name.get_ptr(), name.data(), name.size()); + vm::ptr cmd_arg = vm::static_ptr_cast(arg); + cmd_arg->zero = 0; + std::memcpy(cmd_arg->server_name.get_ptr(), nameserver.c_str(), nameserver.size() + 1); break; } default: break; diff --git a/rpcs3/Emu/Cell/lv2/sys_net.h b/rpcs3/Emu/Cell/lv2/sys_net.h index 68e7041df..9297df0ab 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net.h +++ b/rpcs3/Emu/Cell/lv2/sys_net.h @@ -181,6 +181,24 @@ enum SYS_NET_POLLWRBAND = 0x0100, }; +enum +{ + SYS_NET_STATE_UNKNOWN = 0, + SYS_NET_STATE_CLOSED = 1, + SYS_NET_STATE_CREATED = 2, + SYS_NET_STATE_OPENED = 3, + SYS_NET_STATE_LISTEN = 4, + SYS_NET_STATE_SYN_SENT = 5, + SYS_NET_STATE_SYN_RECEIVED = 6, + SYS_NET_STATE_ESTABLISHED = 7, + SYS_NET_STATE_FIN_WAIT_1 = 8, + SYS_NET_STATE_FIN_WAIT_2 = 9, + SYS_NET_STATE_CLOSE_WAIT = 10, + SYS_NET_STATE_CLOSING = 11, + SYS_NET_STATE_LAST_ACK = 12, + SYS_NET_STATE_TIME_WAIT = 13, +}; + enum lv2_socket_abort_flags : s32 { SYS_NET_ABORT_STRICT_CHECK = 1, @@ -335,6 +353,19 @@ struct sys_net_linger be_t l_linger; }; +struct sys_net_sockinfo_t +{ + be_t s; + be_t proto; + be_t recv_queue_length; + be_t send_queue_length; + sys_net_in_addr local_adr; + be_t local_port; + sys_net_in_addr remote_adr; + be_t remote_port; + be_t state; +}; + class ppu_thread; // Syscalls diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.h index 1deb6efd2..4cf9d0239 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.h +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.h @@ -107,6 +107,8 @@ public: virtual void poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) = 0; virtual std::tuple select(bs_t selected, pollfd& native_pfd) = 0; + virtual void get_sockinfo(sys_net_sockinfo_t& info) = 0; + error_code abort_socket(s32 flags); public: diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp index b422db814..8bfcda66a 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp @@ -1211,6 +1211,36 @@ void lv2_socket_native::set_non_blocking() np::set_socket_non_blocking(native_socket); } +void lv2_socket_native::get_sockinfo(sys_net_sockinfo_t& info) +{ + if (type != SYS_NET_SOCK_STREAM) + { + info.state = info.local_port ? SYS_NET_STATE_OPENED : SYS_NET_STATE_CREATED; + return; + } + + if (info.remote_port) + { + info.state = SYS_NET_STATE_ESTABLISHED; + return; + } + + { + std::lock_guard lock(mutex); + + int listening = 0; + socklen_t len = sizeof(listening); + + if (::getsockopt(native_socket, SOL_SOCKET, SO_ACCEPTCONN, reinterpret_cast(&listening), &len) == 0 && listening) + { + info.state = SYS_NET_STATE_LISTEN; + return; + } + } + + info.state = info.local_port ? SYS_NET_STATE_OPENED : SYS_NET_STATE_CREATED; +} + bool lv2_socket_native::is_socket_connected() { if (type != SYS_NET_SOCK_STREAM) diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h index af9e6a57b..e864dc40b 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h @@ -52,6 +52,7 @@ public: void poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override; std::tuple select(bs_t selected, pollfd& native_pfd) override; + void get_sockinfo(sys_net_sockinfo_t& info) override; bool is_socket_connected(); diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.cpp index 903752085..d6f98d218 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.cpp @@ -92,8 +92,7 @@ s32 lv2_socket_p2p::connect_followup() std::pair lv2_socket_p2p::getpeername() { - sys_net.fatal("[P2P] getpeername() called on a P2P socket"); - return {}; + return {-SYS_NET_ENOTCONN, {}}; } s32 lv2_socket_p2p::listen([[maybe_unused]] s32 backlog) @@ -364,20 +363,39 @@ s32 lv2_socket_p2p::shutdown([[maybe_unused]] s32 how) return CELL_OK; } +void lv2_socket_p2p::get_sockinfo(sys_net_sockinfo_t& info) +{ + std::lock_guard lock(mutex); + info.state = vport ? SYS_NET_STATE_OPENED : SYS_NET_STATE_CREATED; +} + +bs_t lv2_socket_p2p::get_pending_events() const +{ + bs_t pending{}; + + if (vport && !data.empty()) + { + sys_net.trace("[P2P] p2p_data for vport %d contains %d elements", vport, data.size()); + pending += lv2_socket::poll_t::read; + } + + pending += lv2_socket::poll_t::write; + + return pending; +} + void lv2_socket_p2p::poll(sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd) { std::lock_guard lock(mutex); - ensure(vport); - // Check if it's a bound P2P socket - if ((sn_pfd.events & SYS_NET_POLLIN) && !data.empty()) + const bs_t pending = get_pending_events(); + + if ((sn_pfd.events & SYS_NET_POLLIN) && (pending & lv2_socket::poll_t::read)) { - sys_net.trace("[P2P] p2p_data for vport %d contains %d elements", vport, data.size()); sn_pfd.revents |= SYS_NET_POLLIN; } - // Data can always be written on a dgram socket - if (sn_pfd.events & SYS_NET_POLLOUT) + if ((sn_pfd.events & SYS_NET_POLLOUT) && (pending & lv2_socket::poll_t::write)) { sn_pfd.revents |= SYS_NET_POLLOUT; } @@ -387,20 +405,10 @@ std::tuple lv2_socket_p2p::select(bs_t sel { std::lock_guard lock(mutex); - bool read_set = false; - bool write_set = false; + const bs_t pending = get_pending_events() & selected; - // Check if it's a bound P2P socket - if ((selected & lv2_socket::poll_t::read) && vport && !data.empty()) - { - sys_net.trace("[P2P] p2p_data for vport %d contains %d elements", vport, data.size()); - read_set = true; - } - - if (selected & lv2_socket::poll_t::write) - { - write_set = true; - } + const bool read_set = !!(pending & lv2_socket::poll_t::read); + const bool write_set = !!(pending & lv2_socket::poll_t::write); return {read_set, write_set, false}; } diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.h index ec6c1d8b3..0556b2269 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.h +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.h @@ -32,10 +32,13 @@ public: void poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override; std::tuple select(bs_t selected, pollfd& native_pfd) override; + void get_sockinfo(sys_net_sockinfo_t& info) override; void handle_new_data(sys_net_sockaddr_in_p2p p2p_addr, std::vector p2p_data); protected: + virtual bs_t get_pending_events() const; + // Port(actual bound port) and Virtual Port(indicated by u16 at the start of the packet) u16 port = 3658, vport = 0; u32 bound_addr = 0; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp index cb5955745..512f6dd62 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp @@ -100,93 +100,102 @@ public: if (thread_ctrl::state() == thread_state::aborting) return; - std::lock_guard lock(data_mutex); + std::vector streams_to_close; - const auto now = steady_clock::now(); - // Check for messages that haven't been acked - std::set rtt_increased; - for (auto it = msgs.begin(); it != msgs.end();) { - if (it->first > now) - break; + std::lock_guard lock(data_mutex); - // reply is late, increases rtt - auto& msg = it->second; - rtt_info rtt = rtts[msg.sock_id]; - // Only increases rtt once per loop(in case a big number of packets are sent at once) - if (!rtt_increased.count(msg.sock_id)) + const auto now = steady_clock::now(); + // Check for messages that haven't been acked + std::set rtt_increased; + for (auto it = msgs.begin(); it != msgs.end();) { - rtt.num_retries += 1; - // Increases current rtt by 10% - rtt.rtt_time += (rtt.rtt_time / 10); - rtts[msg.sock_id] = rtt; + if (it->first > now) + break; - rtt_increased.emplace(msg.sock_id); - } + // reply is late, increases rtt + auto& msg = it->second; + rtt_info rtt = rtts[msg.sock_id]; + // Only increases rtt once per loop(in case a big number of packets are sent at once) + if (!rtt_increased.count(msg.sock_id)) + { + rtt.num_retries += 1; + // Increases current rtt by 10% + rtt.rtt_time += (rtt.rtt_time / 10); + rtts[msg.sock_id] = rtt; - if (rtt.num_retries >= 10) - { - // Too many retries, need to notify the socket that the connection is dead - idm::check(msg.sock_id, [&](lv2_socket& sock) + rtt_increased.emplace(msg.sock_id); + } + + if (rtt.num_retries >= 10) + { + // Too many retries, need to notify the socket that the connection is dead + sys_net.error("[P2PS] Too many retries, closing the stream"); + streams_to_close.push_back(msg.sock_id); + it = msgs.erase(it); + continue; + } + + // resend the message + const auto res = idm::check(msg.sock_id, [&](lv2_socket& sock) -> bool { - sys_net.error("[P2PS] Too many retries, closing the stream"); ensure(sock.get_type() == SYS_NET_SOCK_STREAM_P2P); auto& sock_p2ps = reinterpret_cast(sock); - sock_p2ps.close_stream(); - }); - it = msgs.erase(it); - continue; - } - // resend the message - const auto res = idm::check(msg.sock_id, [&](lv2_socket& sock) -> bool - { - ensure(sock.get_type() == SYS_NET_SOCK_STREAM_P2P); - auto& sock_p2ps = reinterpret_cast(sock); - - while (np::sendto_possibly_ipv6(sock_p2ps.get_socket(), reinterpret_cast(msg.data.data()), ::size32(msg.data), &msg.dst_addr, 0) == -1) - { - const sys_net_error err = get_last_error(false); - // concurrency on the socket(from a sendto for example) can result in EAGAIN error in which case we try again - if (err == SYS_NET_EAGAIN) + while (np::sendto_possibly_ipv6(sock_p2ps.get_socket(), reinterpret_cast(msg.data.data()), ::size32(msg.data), &msg.dst_addr, 0) == -1) { - continue; + const sys_net_error err = get_last_error(false); + // concurrency on the socket(from a sendto for example) can result in EAGAIN error in which case we try again + if (err == SYS_NET_EAGAIN) + { + continue; + } + + sys_net.error("[P2PS] Resending the packet failed(%s), closing the stream", err); + streams_to_close.push_back(msg.sock_id); + return false; } + return true; + }); - sys_net.error("[P2PS] Resending the packet failed(%s), closing the stream", err); - sock_p2ps.close_stream(); - return false; - } - return true; - }); + if (!res || !res.ret) + { + it = msgs.erase(it); + continue; + } - if (!res || !res.ret) - { + // Update key timeout + msgs.insert(std::make_pair(now + rtt.rtt_time, std::move(msg))); it = msgs.erase(it); - continue; } - // Update key timeout - msgs.insert(std::make_pair(now + rtt.rtt_time, std::move(msg))); - it = msgs.erase(it); - } - - if (!msgs.empty()) - { - const auto current_timepoint = steady_clock::now(); - const auto expected_timepoint = msgs.begin()->first; - if (current_timepoint > expected_timepoint) + if (!msgs.empty()) { - wakey = 1; + const auto current_timepoint = steady_clock::now(); + const auto expected_timepoint = msgs.begin()->first; + if (current_timepoint > expected_timepoint) + { + wakey = 1; + } + else + { + timeout = static_cast(std::chrono::duration_cast(expected_timepoint - current_timepoint).count()); + } } else { - timeout = static_cast(std::chrono::duration_cast(expected_timepoint - current_timepoint).count()); + timeout = atomic_wait_timeout::inf; } } - else + + for (const s32 sock_id : streams_to_close) { - timeout = atomic_wait_timeout::inf; + idm::check(sock_id, [](lv2_socket& sock) + { + ensure(sock.get_type() == SYS_NET_SOCK_STREAM_P2P); + auto& sock_p2ps = reinterpret_cast(sock); + sock_p2ps.close_stream(); + }); } } } @@ -292,6 +301,11 @@ lv2_socket_p2ps::lv2_socket_p2ps(utils::serial& ar, lv2_socket_type type) : lv2_socket_p2p(ar, type) { ar(status, max_backlog, backlog, op_port, op_vport, op_addr, data_beg_seq, received_data, cur_seq); + + if (GET_SERIALIZATION_VERSION(lv2_net) < 3 && status == p2ps_stream_status::stream_closed && op_addr) + { + status = p2ps_stream_status::stream_disconnected; + } } void lv2_socket_p2ps::save(utils::serial& ar) @@ -300,6 +314,38 @@ void lv2_socket_p2ps::save(utils::serial& ar) ar(status, max_backlog, backlog, op_port, op_vport, op_addr, data_beg_seq, received_data, cur_seq); } +void lv2_socket_p2ps::signal_pending_events() +{ + const bs_t pending = get_pending_events(); + bs_t events_happening{}; + + if ((pending & lv2_socket::poll_t::read) && events.test_and_reset(lv2_socket::poll_t::read)) + events_happening += lv2_socket::poll_t::read; + if ((pending & lv2_socket::poll_t::write) && events.test_and_reset(lv2_socket::poll_t::write)) + events_happening += lv2_socket::poll_t::write; + + if (!events_happening) + { + return; + } + + for (auto it = queue.begin(); it != queue.end();) + { + if (it->second(events_happening)) + { + it = queue.erase(it); + continue; + } + + it++; + } + + if (queue.empty()) + { + events.store({}); + } +} + bool lv2_socket_p2ps::handle_connected(p2ps_encapsulated_tcp* tcp_header, u8* data, ::sockaddr_storage* op_addr, nt_p2p_port* p2p_port) { std::lock_guard lock(mutex); @@ -334,25 +380,8 @@ bool lv2_socket_p2ps::handle_connected(p2ps_encapsulated_tcp* tcp_header, u8* da sys_net.trace("[P2PS] Sent ack %d", final_ack); send_u2s_packet(std::move(packet), reinterpret_cast<::sockaddr_in*>(op_addr), 0, false); - // check if polling is happening - if (data_available && events.test_and_reset(lv2_socket::poll_t::read)) - { - bs_t read_event = lv2_socket::poll_t::read; - for (auto it = queue.begin(); it != queue.end();) - { - if (it->second(read_event)) - { - it = queue.erase(it); - continue; - } - it++; - } - - if (queue.empty()) - { - events.store({}); - } - } + // Report that it is writeable if any poll/select are waiting on it + signal_pending_events(); }; if (status == p2ps_stream_status::stream_handshaking) @@ -428,7 +457,7 @@ bool lv2_socket_p2ps::handle_listening(p2ps_encapsulated_tcp* tcp_header, [[mayb if (status != p2ps_stream_status::stream_listening) { - sys_net.error("[P2PS] lv2_socket_p2ps::handle_listening() called on a non listening socket(%d)!", static_cast(status)); + sys_net.warning("[P2PS] lv2_socket_p2ps::handle_listening() called on a non listening socket(%d)!", static_cast(status)); return false; } @@ -483,24 +512,7 @@ bool lv2_socket_p2ps::handle_listening(p2ps_encapsulated_tcp* tcp_header, [[mayb } backlog.push_back(new_sock_id); - if (events.test_and_reset(lv2_socket::poll_t::read)) - { - bs_t read_event = lv2_socket::poll_t::read; - for (auto it = queue.begin(); it != queue.end();) - { - if (it->second(read_event)) - { - it = queue.erase(it); - continue; - } - it++; - } - - if (queue.empty()) - { - events.store({}); - } - } + signal_pending_events(); } else { @@ -541,7 +553,7 @@ void lv2_socket_p2ps::send_u2s_packet(std::vector data, const ::sockaddr_in* void lv2_socket_p2ps::close_stream_nl(nt_p2p_port* p2p_port) { - status = p2ps_stream_status::stream_closed; + status = p2ps_stream_status::stream_disconnected; for (auto it = p2p_port->bound_p2p_streams.begin(); it != p2p_port->bound_p2p_streams.end();) { @@ -555,17 +567,28 @@ void lv2_socket_p2ps::close_stream_nl(nt_p2p_port* p2p_port) auto& tcpm = g_fxo->get>(); tcpm.clear_all_messages(lv2_id); + + // Notify poll/select waiting on read, now that the stream is closed, it will read as readable with 0 bytes + signal_pending_events(); } void lv2_socket_p2ps::close_stream() { auto& nc = g_fxo->get(); - std::lock_guard lock(nc.list_p2p_ports_mutex); - auto& p2p_port = ::at32(nc.list_p2p_ports, port); + std::lock_guard threads_lock(nc.mutex_thread_loop); - std::scoped_lock more_lock(p2p_port.bound_p2p_vports_mutex, mutex); - close_stream_nl(&p2p_port); + { + std::lock_guard lock(nc.list_p2p_ports_mutex); + auto& p2p_port = ::at32(nc.list_p2p_ports, port); + + std::scoped_lock more_lock(p2p_port.bound_p2p_vports_mutex, mutex); + close_stream_nl(&p2p_port); + } + + // close_stream() is only called from tcp_timeout_monitor which is outside of p2p thread scope so + // we need to wake the threads here. + nc.wake_threads(); } p2ps_stream_status lv2_socket_p2ps::get_status() const @@ -738,51 +761,60 @@ std::pair lv2_socket_p2ps::getsockname() std::optional lv2_socket_p2ps::connect(const sys_net_sockaddr& addr) { - std::lock_guard lock(mutex); - - if (status != p2ps_stream_status::stream_closed) - { - sys_net.error("[P2PS] Called connect on a socket that is not closed!"); - return -SYS_NET_EALREADY; - } - p2ps_encapsulated_tcp send_hdr; const auto psa_in_p2p = reinterpret_cast(&addr); auto name = sys_net_addr_to_native_addr(addr); // This is purposefully inverted, not a bug const u16 dst_vport = psa_in_p2p->sin_port; - const u16 dst_port = psa_in_p2p->sin_vport; + u16 dst_port = psa_in_p2p->sin_vport; - socket_type real_socket{}; + sys_net.trace("[P2PS] Connecting to %s:%d:%d", name.sin_addr, dst_port, dst_vport); - auto& nc = g_fxo->get(); + if (!dst_vport) { - std::lock_guard list_lock(nc.list_p2p_ports_mutex); - - nc.create_p2p_port(port); - auto& pport = ::at32(nc.list_p2p_ports, port); - real_socket = pport.p2p_socket; - - { - std::lock_guard lock(pport.bound_p2p_vports_mutex); - if (vport == 0) - { - // Unassigned vport, assigns one - sys_net.warning("[P2PS] vport was unassigned before connect!"); - vport = pport.get_port(); - - while (pport.bound_p2p_vports.count(vport) || pport.bound_p2p_streams.count(static_cast(vport) << 32)) - { - vport = pport.get_port(); - } - } - const u64 key = name.sin_addr.s_addr | (static_cast(vport) << 32) | (static_cast(dst_vport) << 48); - pport.bound_p2p_streams.emplace(key, lv2_id); - } + return -SYS_NET_EADDRNOTAVAIL; } - native_socket = real_socket; + if (!dst_port) + { + dst_port = SCE_NP_PORT; + } + + auto& nc = g_fxo->get(); + std::lock_guard list_lock(nc.list_p2p_ports_mutex); + + nc.create_p2p_port(port); + auto& pport = ::at32(nc.list_p2p_ports, port); + + std::lock_guard vport_lock(pport.bound_p2p_vports_mutex); + std::lock_guard lock(mutex); + + if (status != p2ps_stream_status::stream_closed && status != p2ps_stream_status::stream_disconnected) + { + sys_net.error("[P2PS] Called connect on a socket that is not closed!"); + return -SYS_NET_EALREADY; + } + + if (vport == 0) + { + // Unassigned vport, assigns one + sys_net.warning("[P2PS] vport was unassigned before connect!"); + vport = pport.get_port(); + + while (pport.bound_p2ps_vports.count(vport)) + { + vport = pport.get_port(); + } + + std::set bound_ports{lv2_id}; + pport.bound_p2ps_vports.insert(std::make_pair(vport, std::move(bound_ports))); + } + + const u64 key = name.sin_addr.s_addr | (static_cast(vport) << 32) | (static_cast(dst_vport) << 48); + pport.bound_p2p_streams.emplace(key, lv2_id); + + native_socket = pport.p2p_socket; send_hdr.src_port = vport; send_hdr.dst_port = dst_vport; @@ -826,10 +858,16 @@ std::optional, sys_net_sockaddr>> lv2_socket_p2p if (!data_available) { + if (status == p2ps_stream_status::stream_disconnected) + { + sys_net.trace("[P2PS] Called recvfrom on a disconnected socket"); + return {{0, {}, {}}}; + } + if (status == p2ps_stream_status::stream_closed) { - sys_net.error("[P2PS] Called recvfrom on closed socket!"); - return {{0, {}, {}}}; + sys_net.error("[P2PS] Called recvfrom on an unconnected socket!"); + return {{-SYS_NET_ENOTCONN, {}, {}}}; } if (so_nbio || (flags & SYS_NET_MSG_DONTWAIT)) @@ -888,10 +926,16 @@ std::optional lv2_socket_p2ps::sendto([[maybe_unused]] s32 flags, const std lock.lock(); } + if (status == p2ps_stream_status::stream_disconnected) + { + sys_net.error("[P2PS] Called sendto on a disconnected socket!"); + return -SYS_NET_ECONNRESET; + } + if (status == p2ps_stream_status::stream_closed) { - sys_net.error("[P2PS] Called sendto on a closed socket!"); - return -SYS_NET_ECONNRESET; + sys_net.error("[P2PS] Called sendto on an unconnected socket!"); + return -SYS_NET_ENOTCONN; } constexpr u32 max_data_len = (65535 - (VPORT_P2P_HEADER_SIZE + sizeof(p2ps_encapsulated_tcp))); @@ -985,23 +1029,71 @@ s32 lv2_socket_p2ps::shutdown([[maybe_unused]] s32 how) return CELL_OK; } +void lv2_socket_p2ps::get_sockinfo(sys_net_sockinfo_t& info) +{ + std::lock_guard lock(mutex); + + switch (status) + { + case p2ps_stream_status::stream_closed: + info.state = vport ? SYS_NET_STATE_OPENED : SYS_NET_STATE_CREATED; + break; + case p2ps_stream_status::stream_listening: info.state = SYS_NET_STATE_LISTEN; break; + case p2ps_stream_status::stream_handshaking: info.state = SYS_NET_STATE_SYN_SENT; break; + case p2ps_stream_status::stream_connected: info.state = SYS_NET_STATE_ESTABLISHED; break; + case p2ps_stream_status::stream_disconnected: info.state = SYS_NET_STATE_CLOSED; break; + } + + info.recv_queue_length = static_cast(data_available); +} + +bs_t lv2_socket_p2ps::get_pending_events() const +{ + bs_t pending{}; + + if (status == p2ps_stream_status::stream_connected) + { + if (data_available) + { + sys_net.trace("[P2PS] socket has %d bytes available", data_available); + pending += lv2_socket::poll_t::read; + } + + pending += lv2_socket::poll_t::write; + } + else if (status == p2ps_stream_status::stream_listening) + { + if (const auto bsize = backlog.size()) + { + sys_net.trace("[P2PS] socket has %d clients available", bsize); + pending += lv2_socket::poll_t::read; + } + } + else if (status == p2ps_stream_status::stream_disconnected) + { + // Reads return EOF(0 bytes) and writes fail with ECONNRESET, report both so that + // waiters blocked on either one wake up instead of hanging until their timeout + pending += lv2_socket::poll_t::read; + pending += lv2_socket::poll_t::write; + } + + return pending; +} + void lv2_socket_p2ps::poll(sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd) { std::lock_guard lock(mutex); - sys_net.trace("[P2PS] poll checking for 0x%X", sn_pfd.events); - if (status == p2ps_stream_status::stream_connected) - { - if ((sn_pfd.events & SYS_NET_POLLIN) && data_available) - { - sys_net.trace("[P2PS] p2ps has %u bytes available", data_available); - sn_pfd.revents |= SYS_NET_POLLIN; - } - // Data can only be written if the socket is connected - if (sn_pfd.events & SYS_NET_POLLOUT && status == p2ps_stream_status::stream_connected) - { - sn_pfd.revents |= SYS_NET_POLLOUT; - } + const bs_t pending = get_pending_events(); + + if ((sn_pfd.events & SYS_NET_POLLIN) && (pending & lv2_socket::poll_t::read)) + { + sn_pfd.revents |= SYS_NET_POLLIN; + } + + if ((sn_pfd.events & SYS_NET_POLLOUT) && (pending & lv2_socket::poll_t::write)) + { + sn_pfd.revents |= SYS_NET_POLLOUT; } } @@ -1009,32 +1101,10 @@ std::tuple lv2_socket_p2ps::select(bs_t se { std::lock_guard lock(mutex); - bool read_set = false; - bool write_set = false; + const bs_t pending = get_pending_events() & selected; - if (status == p2ps_stream_status::stream_connected) - { - if ((selected & lv2_socket::poll_t::read) && data_available) - { - sys_net.trace("[P2PS] socket has %d bytes available", data_available); - read_set = true; - } - - if (selected & lv2_socket::poll_t::write) - { - sys_net.trace("[P2PS] socket is writeable"); - write_set = true; - } - } - else if (status == p2ps_stream_status::stream_listening) - { - const auto bsize = backlog.size(); - if ((selected & lv2_socket::poll_t::read) && bsize) - { - sys_net.trace("[P2PS] socket has %d clients available", bsize); - read_set = true; - } - } + const bool read_set = !!(pending & lv2_socket::poll_t::read); + const bool write_set = !!(pending & lv2_socket::poll_t::write); return {read_set, write_set, false}; } diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.h index ac23528d5..6fccdcd29 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.h +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.h @@ -34,10 +34,11 @@ struct p2ps_encapsulated_tcp enum p2ps_stream_status { - stream_closed, // Default when port is not listening nor connected - stream_listening, // Stream is listening, accepting SYN packets - stream_handshaking, // Currently handshaking - stream_connected, // This is an established connection(after tcp handshake) + stream_closed, // Default when the stream was never listening nor connected + stream_listening, // Stream is listening, accepting SYN packets + stream_handshaking, // Currently handshaking + stream_connected, // This is an established connection(after tcp handshake) + stream_disconnected, // Was connected but is now closed }; enum p2ps_tcp_flags : u8 @@ -91,8 +92,11 @@ public: void poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override; std::tuple select(bs_t selected, pollfd& native_pfd) override; + void get_sockinfo(sys_net_sockinfo_t& info) override; private: + bs_t get_pending_events() const override; + void signal_pending_events(); void close_stream_nl(nt_p2p_port* p2p_port); private: diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.cpp index 39ae39e5b..2b68a0617 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.cpp @@ -139,6 +139,11 @@ void lv2_socket_raw::poll([[maybe_unused]] sys_net_pollfd& sn_pfd, [[maybe_unuse LOG_ONCE(raw_poll, "lv2_socket_raw::poll"); } +void lv2_socket_raw::get_sockinfo(sys_net_sockinfo_t& info) +{ + info.state = SYS_NET_STATE_UNKNOWN; +} + std::tuple lv2_socket_raw::select([[maybe_unused]] bs_t selected, [[maybe_unused]] pollfd& native_pfd) { LOG_ONCE(raw_select, "lv2_socket_raw::select"); diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.h index a03339354..3d1c64e37 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.h +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.h @@ -34,4 +34,5 @@ public: void poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override; std::tuple select(bs_t selected, pollfd& native_pfd) override; + void get_sockinfo(sys_net_sockinfo_t& info) override; }; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/network_context.cpp b/rpcs3/Emu/Cell/lv2/sys_net/network_context.cpp index 18a1c2a93..37715cb7e 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/network_context.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/network_context.cpp @@ -302,7 +302,7 @@ void p2p_thread::operator()() #endif if (ret_p2p > 0) { - std::lock_guard lock(list_p2p_ports_mutex); + std::scoped_lock lock(mutex_thread_loop, list_p2p_ports_mutex); auto fd_index = 0; auto process_fd = [&](nt_p2p_port& p2p_port) diff --git a/rpcs3/Emu/Cell/lv2/sys_net/network_context.h b/rpcs3/Emu/Cell/lv2/sys_net/network_context.h index 658196c4b..aa60962a9 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/network_context.h +++ b/rpcs3/Emu/Cell/lv2/sys_net/network_context.h @@ -14,13 +14,13 @@ struct base_network_thread shared_mutex mutex_ppu_to_awake; std::vector ppu_to_awake; + shared_mutex mutex_thread_loop; void wake_threads(); }; struct network_thread : base_network_thread { - shared_mutex mutex_thread_loop; atomic_t num_polls = 0; static constexpr auto thread_name = "Network Thread"; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/nt_p2p_port.cpp b/rpcs3/Emu/Cell/lv2/sys_net/nt_p2p_port.cpp index 67be0df63..b410e7036 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/nt_p2p_port.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/nt_p2p_port.cpp @@ -337,13 +337,20 @@ bool nt_p2p_port::recv_data() if (bound_p2ps_vports.contains(tcp_header->dst_port)) { const auto& bound_sockets = ::at32(bound_p2ps_vports, tcp_header->dst_port); + bool handled = false; for (const auto sock_id : bound_sockets) { sys_net.trace("Received packet for listening STREAM-P2P socket(s=%d)", sock_id); - handle_listening(sock_id, tcp_header, p2p_data.data() + sizeof(p2ps_encapsulated_tcp), &native_addr); + handled |= handle_listening(sock_id, tcp_header, p2p_data.data() + sizeof(p2ps_encapsulated_tcp), &native_addr); } - return true; + + if (handled) + { + return true; + } + + // The vport is only reserved(e.g. by a connect()ed socket), no socket is listening on it, reply with RST as if it was unbound } if (tcp_header->flags == p2ps_tcp_flags::RST) diff --git a/rpcs3/Emu/NP/np_handler.cpp b/rpcs3/Emu/NP/np_handler.cpp index 34300ae36..c7346061a 100644 --- a/rpcs3/Emu/NP/np_handler.cpp +++ b/rpcs3/Emu/NP/np_handler.cpp @@ -1638,6 +1638,32 @@ namespace np } } + void np_handler::rpcn_trophy_unlock(const SceNpCommunicationId& communication_id, s32 trophy_id, s64 timestamp) + { + if (!is_psn_active || g_cfg.net.psn_status != np_psn_status::psn_rpcn) + return; + + std::lock_guard lock(mutex_rpcn); + if (!rpcn || !rpcn->is_authentified()) + return; + + rpcn->unlock_trophy(communication_id, trophy_id, timestamp); + } + + std::vector> np_handler::rpcn_trophy_sync( + const SceNpCommunicationId& communication_id, + const std::vector>& local_unlocked) + { + if (!is_psn_active || g_cfg.net.psn_status != np_psn_status::psn_rpcn) + return {}; + + std::lock_guard lock(mutex_rpcn); + if (!rpcn || !rpcn->is_authentified()) + return {}; + + return rpcn->sync_trophies(communication_id, local_unlocked); + } + template error_code np_handler::get_friend_presence_by_index(u32 index, SceNpUserInfo* user, T* pres) { diff --git a/rpcs3/Emu/NP/np_handler.h b/rpcs3/Emu/NP/np_handler.h index af30eb3c5..86eaca3bb 100644 --- a/rpcs3/Emu/NP/np_handler.h +++ b/rpcs3/Emu/NP/np_handler.h @@ -249,6 +249,11 @@ namespace np std::pair> get_friend_by_index(u32 index); void set_presence(std::optional status, std::optional> data); + // RPCN trophy support + void rpcn_trophy_unlock(const SceNpCommunicationId& communication_id, s32 trophy_id, s64 timestamp); + std::vector> rpcn_trophy_sync(const SceNpCommunicationId& communication_id, + const std::vector>& local_unlocked); + template error_code get_friend_presence_by_index(u32 index, SceNpUserInfo* user, T* pres); diff --git a/rpcs3/Emu/NP/rpcn_client.cpp b/rpcs3/Emu/NP/rpcn_client.cpp index bf01fcc2a..1b357f908 100644 --- a/rpcs3/Emu/NP/rpcn_client.cpp +++ b/rpcs3/Emu/NP/rpcn_client.cpp @@ -161,6 +161,8 @@ void fmt_class_string::format(std::string& out, u64 arg) case rpcn::CommandType::GetRoomInfoGUI: return "GetRoomInfoGUI"; case rpcn::CommandType::QuickMatchGUI: return "QuickMatchGUI"; case rpcn::CommandType::SearchJoinRoomGUI: return "SearchJoinRoomGUI"; + case rpcn::CommandType::UnlockTrophy: return "UnlockTrophy"; + case rpcn::CommandType::SyncTrophies: return "SyncTrophies"; } return unknown; @@ -257,14 +259,6 @@ namespace rpcn rpcn_log.notice("online: %s, pr_com_id: %s, pr_title: %s, pr_status: %s, pr_comment: %s, pr_data: %s", online ? "true" : "false", pr_com_id.data, pr_title, pr_status, pr_comment, fmt::buf_to_hexstring(pr_data.data(), pr_data.size())); } - // 31 since upstream cb175278b (2026-08-18), which added trophy sync. np.rpcs3.net - // enforces it: a client on 30 completes the TLS handshake, is told the server speaks 31 - // and is disconnected -- "Protocol Version Error (outdated RPCS3?)". - // - // The two commands that version added are appended to the end of CommandType, so every - // existing command keeps its ordinal and the wire format for everything this client - // actually sends is unchanged. Trophy sync itself is not implemented here; not sending a - // command is always safe. constexpr u32 RPCN_PROTOCOL_VERSION = 31; constexpr usz RPCN_HEADER_SIZE = 15; @@ -677,7 +671,8 @@ namespace rpcn command == CommandType::AddBlock || command == CommandType::RemoveBlock || command == CommandType::SendMessage || command == CommandType::SendToken || command == CommandType::SendResetToken || command == CommandType::ResetPassword || - command == CommandType::GetNetworkTime || command == CommandType::SetPresence || command == CommandType::Terminate) + command == CommandType::GetNetworkTime || command == CommandType::SetPresence || command == CommandType::Terminate || + command == CommandType::SyncTrophies) { std::lock_guard lock(mutex_replies_sync); replies_sync.insert(std::make_pair(packet_id, std::make_pair(command, std::move(data)))); @@ -2619,6 +2614,64 @@ namespace rpcn return forge_request_with_com_id(serialized, pr_com_id, CommandType::SetPresence, rpcn_request_counter.fetch_add(1)); } + bool rpcn_client::unlock_trophy(const SceNpCommunicationId& communication_id, s32 trophy_id, s64 timestamp) + { + std::vector data(COMMUNICATION_ID_SIZE + sizeof(s32) + sizeof(s64)); + rpcn_client::write_communication_id(communication_id, data); + reinterpret_cast&>(data[COMMUNICATION_ID_SIZE]) = trophy_id; + reinterpret_cast&>(data[COMMUNICATION_ID_SIZE + sizeof(s32)]) = timestamp; + return forge_send(CommandType::UnlockTrophy, rpcn_request_counter.fetch_add(1), data); + } + + std::vector> rpcn_client::sync_trophies( + const SceNpCommunicationId& communication_id, + const std::vector>& local_unlocked) + { + const u32 count = static_cast(local_unlocked.size()); + + std::vector data(COMMUNICATION_ID_SIZE + sizeof(u32) + count * (sizeof(s32) + sizeof(s64))), reply_data; + + rpcn_client::write_communication_id(communication_id, data); + reinterpret_cast&>(data[COMMUNICATION_ID_SIZE]) = count; + + usz offset = COMMUNICATION_ID_SIZE + sizeof(u32); + for (const auto& [tid, ts] : local_unlocked) + { + reinterpret_cast&>(data[offset]) = tid; + offset += sizeof(s32); + reinterpret_cast&>(data[offset]) = ts; + offset += sizeof(s64); + } + + if (!forge_send_reply(CommandType::SyncTrophies, rpcn_request_counter.fetch_add(1), data, reply_data)) + return {}; + + vec_stream reply(reply_data); + const auto error = static_cast(reply.get()); + if (error != rpcn::ErrorType::NoError) + { + rpcn_log.error("sync_trophies: server returned error %s", fmt::format("%s", error)); + return {}; + } + + const u32 server_count = reply.get(); + std::vector> result; + result.reserve(server_count); + for (u32 i = 0; i < server_count; i++) + { + const s32 tid = reply.get(); + const s64 ts = reply.get(); + result.emplace_back(tid, ts); + } + + if (reply.is_error()) + { + error_and_disconnect("Malformed reply to SyncTrophies command"); + return {}; + } + return result; + } + bool rpcn_client::createjoin_room_gui(u32 req_id, const SceNpCommunicationId& communication_id, const SceNpMatchingAttr* attr_list) { np2_structs::CreateRoomGUIRequest pb_req; diff --git a/rpcs3/Emu/NP/rpcn_client.h b/rpcs3/Emu/NP/rpcn_client.h index e1fdfdfdc..31dba0e9e 100644 --- a/rpcs3/Emu/NP/rpcn_client.h +++ b/rpcs3/Emu/NP/rpcn_client.h @@ -374,6 +374,8 @@ namespace rpcn bool tus_get_friends_data_status(u32 req_id, SceNpCommunicationId& communication_id, SceNpTusSlotId slotId, bool includeSelf, s32 sortType, s32 arrayNum); bool tus_delete_multislot_data(u32 req_id, SceNpCommunicationId& communication_id, const SceNpOnlineId& targetNpId, vm::cptr slotIdArray, s32 arrayNum, bool vuser); bool send_presence(const SceNpCommunicationId& pr_com_id, const std::string& pr_title, const std::string& pr_status, const std::string& pr_comment, const std::vector& pr_data); + bool unlock_trophy(const SceNpCommunicationId& communication_id, s32 trophy_id, s64 timestamp); + std::vector> sync_trophies(const SceNpCommunicationId& communication_id, const std::vector>& local_unlocked); bool createjoin_room_gui(u32 req_id, const SceNpCommunicationId& communication_id, const SceNpMatchingAttr* attr_list); bool join_room_gui(u32 req_id, const SceNpRoomId& room_id); bool leave_room_gui(u32 req_id, const SceNpRoomId& room_id); diff --git a/rpcs3/Emu/NP/rpcn_types.h b/rpcs3/Emu/NP/rpcn_types.h index 16d6b0b5b..0a4a281e3 100644 --- a/rpcs3/Emu/NP/rpcn_types.h +++ b/rpcs3/Emu/NP/rpcn_types.h @@ -69,9 +69,6 @@ namespace rpcn QuickMatchGUI, SearchJoinRoomGUI, GetRoomMemberDataExternalList, - // Appended by upstream cb175278b along with protocol 31. We do not send either -- - // trophy sync is not implemented here -- but the ordinals have to match the server's - // or every command after this point would be misnumbered. UnlockTrophy, SyncTrophies, }; diff --git a/rpcs3/Emu/RSX/Common/TextureUtils.cpp b/rpcs3/Emu/RSX/Common/TextureUtils.cpp index a2875607c..3fd0ed68c 100644 --- a/rpcs3/Emu/RSX/Common/TextureUtils.cpp +++ b/rpcs3/Emu/RSX/Common/TextureUtils.cpp @@ -1570,6 +1570,7 @@ namespace rsx const auto gcm_format = format & ~(CELL_GCM_TEXTURE_LN | CELL_GCM_TEXTURE_UN); const bool packed = !(format & CELL_GCM_TEXTURE_LN); const auto texel_rows_per_line = get_format_texel_rows_per_line(gcm_format); + const bool has_border = !!border; if (!pitch && !packed) { @@ -1580,7 +1581,7 @@ namespace rsx width, height, format, gcm_format); } - pitch = get_format_packed_pitch(gcm_format, width, !!border, packed); + pitch = get_format_packed_pitch(gcm_format, width, has_border, packed); } u32 size = 0; @@ -1591,10 +1592,14 @@ namespace rsx for (u32 layer = 0; layer < layers; ++layer) { u32 mip_height = internal_height; + u32 mip_depth = depth; + for (u32 mipmap = 0; mipmap < mipmaps && mip_height > 0; ++mipmap) { - size += pitch * mip_height * depth; + const auto padded_h = has_border ? (mip_height + 2u) : mip_height; + size += pitch * padded_h * mip_depth; mip_height = std::max(mip_height / 2u, 1u); + mip_depth = std::max(mip_depth / 2u, 1u); } } } @@ -1606,15 +1611,22 @@ namespace rsx const u32 internal_height = (height + texel_rows_per_line - 1) / texel_rows_per_line; // Convert texels to blocks const u32 internal_width = (width + texels_per_block - 1) / texels_per_block; // Convert texels to blocks + for (u32 layer = 0; layer < layers; ++layer) { u32 mip_height = internal_height; u32 mip_width = internal_width; + u32 mip_depth = depth; + for (u32 mipmap = 0; mipmap < mipmaps && mip_height > 0; ++mipmap) { - size += (mip_width * bytes_per_block * mip_height * depth); + const auto padded_w = has_border ? rsx::next_pow2(mip_width + 8u) : mip_width; + const auto padded_h = has_border ? rsx::next_pow2(mip_height + 8u) : mip_height; + size += (padded_w * bytes_per_block * padded_h * mip_depth); + mip_height = std::max(mip_height / 2u, 1u); mip_width = std::max(mip_width / 2u, 1u); + mip_depth = std::max(mip_depth / 2u, 1u); } } } @@ -1622,18 +1634,45 @@ namespace rsx return size; } - usz get_texture_size(const rsx::fragment_texture& texture) + usz get_texture_size_with_mipmaps(const RSXTexture auto& texture, u16 mipmaps) { return get_texture_size(texture.format(), texture.width(), texture.height(), texture.depth(), - texture.pitch(), texture.get_exact_mipmap_count(), texture.cubemap() ? 6 : 1, + texture.pitch(), mipmaps, texture.cubemap() ? 6 : 1, texture.border_type() ^ 1); } - usz get_texture_size(const rsx::vertex_texture& texture) + usz get_texture_size_impl(const RSXTexture auto& texture, u8 mip_level) { - return get_texture_size(texture.format(), texture.width(), texture.height(), texture.depth(), - texture.pitch(), texture.get_exact_mipmap_count(), texture.cubemap() ? 6 : 1, - texture.border_type() ^ 1); + const auto max_levels = texture.get_exact_mipmap_count(); + if (mip_level != RSX_GCM_MIP_LEVEL_IGNORED && + mip_level >= max_levels) + { + // Mip level out of bounds + return 0; + } + + const auto base_levels = (mip_level != RSX_GCM_MIP_LEVEL_IGNORED) + ? static_cast(mip_level + 1) + : max_levels; + + const auto base_size = get_texture_size_with_mipmaps(texture, base_levels); + if (mip_level == 0 || mip_level == RSX_GCM_MIP_LEVEL_IGNORED) + { + return base_size; + } + + const auto leading_size = get_texture_size_with_mipmaps(texture, base_levels - 1); + return base_size - leading_size; + } + + usz get_texture_size(const rsx::fragment_texture& texture, u8 mip_level) + { + return get_texture_size_impl(texture, mip_level); + } + + usz get_texture_size(const rsx::vertex_texture& texture, u8 mip_level) + { + return get_texture_size_impl(texture, mip_level); } u32 get_remap_encoding(const texture_channel_remap_t& remap) diff --git a/rpcs3/Emu/RSX/Common/TextureUtils.h b/rpcs3/Emu/RSX/Common/TextureUtils.h index 13ac5c0da..526b91816 100644 --- a/rpcs3/Emu/RSX/Common/TextureUtils.h +++ b/rpcs3/Emu/RSX/Common/TextureUtils.h @@ -6,11 +6,17 @@ #include "../RSXTexture.h" #include +#include namespace rsx { using flags32_t = u32; + template + concept RSXTexture = + std::same_as || + std::same_as; + enum texture_upload_context : u32 { shader_read = 1, @@ -299,6 +305,40 @@ namespace rsx bool edge_clamped; }; + struct image_copy_subresource_layers + { + u8 src_mip_level = 0; + u8 dst_mip_level = 0; + u8 mipmap_count = 1; + u8 src_layer = 0; + u8 dst_layer = 0; + u8 layer_count = 1; + + image_copy_subresource_layers without_src() const + { + return { + .src_mip_level = 0, + .dst_mip_level = dst_mip_level, + .mipmap_count = 1, + .src_layer = 0, + .dst_layer = dst_layer, + .layer_count = 1 + }; + } + + image_copy_subresource_layers without_dst() const + { + return { + .src_mip_level = src_mip_level, + .dst_mip_level = 0, + .mipmap_count = 1, + .src_layer = src_layer, + .dst_layer = 0, + .layer_count = 1 + }; + } + }; + /** * Get size to store texture in a linear fashion. * Storage is assumed to use a rowPitchAlignment boundary for every row of texture. @@ -345,10 +385,12 @@ namespace rsx u8 get_format_texel_rows_per_line(u32 format); /** - * Get number of bytes occupied by texture in RSX mem + * Get number of bytes occupied by texture in RSX mem. + * Specify mip_level to compute size for exactly one mipmap level. */ - usz get_texture_size(const rsx::fragment_texture &texture); - usz get_texture_size(const rsx::vertex_texture &texture); + constexpr u8 RSX_GCM_MIP_LEVEL_IGNORED = UINT8_MAX; + usz get_texture_size(const rsx::fragment_texture &texture, u8 mip_level = RSX_GCM_MIP_LEVEL_IGNORED); + usz get_texture_size(const rsx::vertex_texture &texture, u8 mip_level = RSX_GCM_MIP_LEVEL_IGNORED); /** * Get packed pitch diff --git a/rpcs3/Emu/RSX/Common/surface_store.h b/rpcs3/Emu/RSX/Common/surface_store.h index 5289b7b8d..47fcf6131 100644 --- a/rpcs3/Emu/RSX/Common/surface_store.h +++ b/rpcs3/Emu/RSX/Common/surface_store.h @@ -1098,17 +1098,17 @@ namespace rsx rsx::simple_array result; rsx::simple_array> dirty; - const auto surface_internal_pitch = (required_width * required_bpp); + const u32 required_width_in_bytes = (required_width * required_bpp); // Sanity check - if (surface_internal_pitch > required_pitch) [[unlikely]] + if (required_width_in_bytes > required_pitch) [[unlikely]] { rsx_log.warning("Invalid 2D region descriptor. w=%d, h=%d, bpp=%d, pitch=%d", required_width, required_height, required_bpp, required_pitch); return {}; } - const auto test_range = utils::address_range32::start_length(texaddr, (required_pitch * required_height) - (required_pitch - surface_internal_pitch)); + const auto test_range = utils::address_range32::start_length(texaddr, (required_pitch * required_height) - (required_pitch - required_width_in_bytes)); auto process_list_function = [&](surface_ranged_map& data, bool is_depth) { @@ -1128,52 +1128,61 @@ namespace rsx if (!rsx::pitch_compatible(surface, required_pitch, required_height)) continue; - surface_overlap_info info; - u32 width, height; - info.surface = surface; - info.base_address = range.start; - info.is_depth = is_depth; + // 2D tests are done in format-agnostic rectangles (1bpp) + const u32 surface_width_in_bytes = surface->template get_surface_width(); + const u32 surface_height = surface->template get_surface_height(); + const u32 surface_bpp = surface->get_bpp(); - const u32 normalized_surface_width = surface->template get_surface_width() / required_bpp; - const u32 normalized_surface_height = surface->template get_surface_height(); + u32 src_x, src_y, dst_x, dst_y, width_in_bytes, height; if (range.start >= texaddr) [[likely]] { + // The surface begins somewhere inside the requested region const auto offset = range.start - texaddr; - info.dst_area.y = (offset / required_pitch); - info.dst_area.x = (offset % required_pitch) / required_bpp; + dst_y = (offset / required_pitch); + dst_x = (offset % required_pitch); - if (info.dst_area.x >= required_width || info.dst_area.y >= required_height) [[unlikely]] + if (dst_x >= required_width_in_bytes || dst_y >= required_height) [[unlikely]] { // Out of bounds continue; } - info.src_area.x = 0; - info.src_area.y = 0; - width = std::min(normalized_surface_width, required_width - info.dst_area.x); - height = std::min(normalized_surface_height, required_height - info.dst_area.y); + src_x = 0; + src_y = 0; + width_in_bytes = std::min(surface_width_in_bytes, required_width_in_bytes - dst_x); + height = std::min(surface_height, required_height - dst_y); } else { - const auto pitch = surface->get_rsx_pitch(); + // The requested region begins somewhere inside the surface + const auto surface_pitch = surface->get_rsx_pitch(); const auto offset = texaddr - range.start; - info.src_area.y = (offset / pitch); - info.src_area.x = (offset % pitch) / required_bpp; + src_y = (offset / surface_pitch); + src_x = (offset % surface_pitch); - if (info.src_area.x >= normalized_surface_width || info.src_area.y >= normalized_surface_height) [[unlikely]] + if (src_x >= surface_width_in_bytes || src_y >= surface_height) [[unlikely]] { // Region lies outside the actual texture area, but inside the 'tile' // In this case, a small region lies to the top-left corner, partially occupying the target continue; } - info.dst_area.x = 0; - info.dst_area.y = 0; - width = std::min(required_width, normalized_surface_width - info.src_area.x); - height = std::min(required_height, normalized_surface_height - info.src_area.y); + dst_x = 0; + dst_y = 0; + width_in_bytes = std::min(required_width_in_bytes, surface_width_in_bytes - src_x); + height = std::min(required_height, surface_height - src_y); } + if (width_in_bytes < required_bpp) [[unlikely]] + { + // There is nothing transferable here; less than 1 pixel available. + continue; + } + + // Drop any excess subpixels on the requester side if any. Ensures division generates a perfect fitting rect without rounding bugs. + width_in_bytes -= (width_in_bytes % required_bpp); + // Delay this as much as possible to avoid side-effects of spamming barrier if (surface->memory_barrier(cmd, access); !surface->test()) { @@ -1181,20 +1190,23 @@ namespace rsx continue; } - info.is_clipped = (width < required_width || height < required_height); - info.src_area.height = info.dst_area.height = height; - info.dst_area.width = width; + surface_overlap_info info; + info.surface = surface; + info.base_address = range.start; + info.is_depth = is_depth; + info.is_clipped = (width_in_bytes < required_width_in_bytes || height < required_height); - if (auto surface_bpp = surface->get_bpp(); surface_bpp != required_bpp) [[unlikely]] - { - // Width is calculated in the coordinate-space of the requester; normalize - info.src_area.x = (info.src_area.x * required_bpp) / surface_bpp; - info.src_area.width = utils::align(width * required_bpp, surface_bpp) / surface_bpp; - } - else - { - info.src_area.width = width; - } + // Decode source + info.src_area.x = src_x / surface_bpp; + info.src_area.y = src_y; + info.src_area.width = utils::aligned_div(width_in_bytes, surface_bpp); + info.src_area.height = height; + + // Decode dest + info.dst_area.x = dst_x / required_bpp; + info.dst_area.y = dst_y; + info.dst_area.width = width_in_bytes / required_bpp; + info.dst_area.height = height; result.push_back(std::move(info)); } diff --git a/rpcs3/Emu/RSX/Common/surface_utils.h b/rpcs3/Emu/RSX/Common/surface_utils.h index 8ec97600b..0c4a1f635 100644 --- a/rpcs3/Emu/RSX/Common/surface_utils.h +++ b/rpcs3/Emu/RSX/Common/surface_utils.h @@ -39,8 +39,8 @@ namespace rsx bool is_depth = false; bool is_clipped = false; - coordu src_area; - coordu dst_area; + coordu src_area; //<- Always computed in source image coordinates + coordu dst_area; //<- Always computed in destination (requester) image coordinates }; template diff --git a/rpcs3/Emu/RSX/Common/texture_cache.h b/rpcs3/Emu/RSX/Common/texture_cache.h index 48a099310..aae37dee5 100644 --- a/rpcs3/Emu/RSX/Common/texture_cache.h +++ b/rpcs3/Emu/RSX/Common/texture_cache.h @@ -147,25 +147,157 @@ namespace rsx texture_channel_remap_t remap; deferred_request_command op = deferred_request_command::nop; u32 external_ref_addr = 0; - u16 x = 0; - u16 y = 0; utils::address_range32 cache_range; bool do_not_cache = false; bool force_bg_load = false; + using section_array_type = rsx::simple_array; + deferred_subresource() = default; - deferred_subresource(image_resource_type _res, deferred_request_command _op, - const image_section_attributes_t& attr, position2u offset, - texture_channel_remap_t _remap) - : external_handle(_res) - , remap(std::move(_remap)) - , op(_op) - , x(offset.x) - , y(offset.y) + // + // Named constructor wrappers - tighten the API contracts a bit. + // Replaces the giant ctor which was very error-prone and had many unused or conditionally used inputs. + // + + // Single-section transfer. src_rect is in source image space, dst_rect in destination image space. + static deferred_subresource create_copy( + image_resource_type src, + const image_section_attributes_t& attr, + const coord3u& src_rect, + const coord3u& dst_rect, + rsx::surface_transform xform, + const texture_channel_remap_t& remap, + bool cyclic_reference = false) { - static_cast(*this) = attr; + ensure(src); + + deferred_subresource result{}; + static_cast(result) = attr; + result.external_handle = src; + result.op = cyclic_reference + ? deferred_request_command::copy_image_dynamic + : deferred_request_command::copy_image_static; + result.remap = remap; + result.add_copy_region(src, src_rect, dst_rect, attr.address, xform); + return result; + } + + // Shorthand for transfers where the source and destination regions are equal. + // xform allows to declare both as native or request conversion at the consumer side. + static deferred_subresource create_copy( + image_resource_type src, + const image_section_attributes_t& attr, + const coord3u& rect, + rsx::surface_transform xform, + const texture_channel_remap_t& remap, + bool cyclic_reference = false) + { + return create_copy(src, attr, rect, rect, xform, remap, cyclic_reference); + } + + // One large surface to be partitioned into cubemap faces. + static deferred_subresource create_cubemap_unwrap( + image_resource_type src, + const position2u& src_offset, + const image_section_attributes_t& attr, + const texture_channel_remap_t& remap) + { + return make_unwrap(deferred_request_command::cubemap_unwrap, src, src_offset, attr, remap); + } + + // One large surface to be partitioned into a 3D array. + static deferred_subresource create_3d_unwrap( + image_resource_type src, + const position2u& src_offset, + const image_section_attributes_t& attr, + const texture_channel_remap_t& remap) + { + return make_unwrap(deferred_request_command::_3d_unwrap, src, src_offset, attr, remap); + } + + // 2D section splat + static deferred_subresource create_atlas_gather( + const image_section_attributes_t& attr, + section_array_type&& sections, + const texture_channel_remap_t& remap, + bool force_bg_load = false) + { + return make_gather(deferred_request_command::atlas_gather, attr, std::move(sections), remap, force_bg_load); + } + + // Cubemap builder from discrete images per face and/or mip + static deferred_subresource create_cubemap_gather( + const image_section_attributes_t& attr, + section_array_type&& sections, + const texture_channel_remap_t& remap, + bool force_bg_load = false) + { + return make_gather(deferred_request_command::cubemap_gather, attr, std::move(sections), remap, force_bg_load); + } + + // 3D builder from discrete images per face and/or mip + static deferred_subresource create_3d_gather( + const image_section_attributes_t& attr, + section_array_type&& sections, + const texture_channel_remap_t& remap, + bool force_bg_load = false) + { + return make_gather(deferred_request_command::_3d_gather, attr, std::move(sections), remap, force_bg_load); + } + + // 2D mipchain builder + static deferred_subresource create_mipmap_gather( + const image_section_attributes_t& attr, + section_array_type&& sections, + const texture_channel_remap_t& remap, + bool force_bg_load = false) + { + return make_gather(deferred_request_command::mipmap_gather, attr, std::move(sections), remap, force_bg_load); + } + + void add_copy_region( + image_resource_type src, + const coord3u& src_rect, + const coord3u& dst_rect, + u32 base_addr = 0, + flags32_t xform = rsx::surface_transform::identity, + u8 level = 0) + { + sections_to_copy.push_back({ + .src = src, + .xform = xform, + .base_addr = base_addr, + .level = level, + .src_x = static_cast(src_rect.x), + .src_y = static_cast(src_rect.y), + .dst_x = static_cast(dst_rect.x), + .dst_y = static_cast(dst_rect.y), + .dst_z = static_cast(dst_rect.z), + .src_w = static_cast(src_rect.width), + .src_h = static_cast(src_rect.height), + .dst_w = static_cast(dst_rect.width), + .dst_h = static_cast(dst_rect.height), + }); + } + + // Key layout: + // [00..15] width - 4096 native, but res scaling can get this up to 64k. + // [16..31] height - ditto + // [32..42] depth - 512 max on RSX. One spare bit in case we ever expand 3D host-side. + // [43..47] mipmaps - log2(4096) + 1 = 13 max. Allow upto 31. + // [48..55] gcm_format - only a few actual enumerants but the values are in the 0x80-0x9F range + // [56..60] op - deferred_request_command, 10 values defined, allow upto 31 + // [61..63] unused + u64 encoded_properties() const + { + return (static_cast(width)) | + (static_cast(height) << 16) | + ((static_cast(depth) & 0x7ff) << 32) | + ((static_cast(mipmaps) & 0x1f) << 43) | + ((static_cast(gcm_format) & 0xff) << 48) | + ((static_cast(op) & 0x1f) << 56); } viewable_image_type as_viewable() const @@ -188,18 +320,95 @@ namespace rsx // Return typed null return external_handle; } + + position2u unwrap_offset() const + { + ensure(op == deferred_request_command::cubemap_unwrap || op == deferred_request_command::_3d_unwrap); + ensure(sections_to_copy.size() == 1); + const auto& section = sections_to_copy.front(); + return { section.src_x, section.src_y }; + } + + u8 exact_mip_count() const + { + switch (op) + { + case rsx::deferred_request_command::cubemap_unwrap: + return static_cast(mipmaps); + case rsx::deferred_request_command::mipmap_gather: + return static_cast(sections_to_copy.size()); + default: + break; + } + + return 1 + sections_to_copy.reduce(0, FN(std::max(x, y.level))); + } + + private: + static void embed_src_offset(deferred_subresource& target, const position2u& offset) + { + ensure(target.sections_to_copy.empty()); + // Embed a single copy block with the src offsets. + target.sections_to_copy.push_back({}); + target.sections_to_copy.back().src_x = static_cast(offset.x); + target.sections_to_copy.back().src_y = static_cast(offset.y); + } + + static deferred_subresource make_unwrap( + deferred_request_command op, + image_resource_type src, + const position2u& src_offset, + const image_section_attributes_t& attr, + const texture_channel_remap_t& remap) + { + ensure(src); + + // Unwrap commands do not define sections by themselves. That data is autogenerated at the consumer site. + deferred_subresource result{}; + static_cast(result) = attr; + result.external_handle = src; + result.op = op; + result.remap = remap; + embed_src_offset(result, src_offset); + return result; + } + + static deferred_subresource make_gather( + deferred_request_command op, + const image_section_attributes_t& attr, + section_array_type&& sections, + const texture_channel_remap_t& remap, + bool force_bg_load) + { + deferred_subresource result{}; + static_cast(result) = attr; + result.op = op; + result.remap = remap; + result.sections_to_copy = std::move(sections); + result.force_bg_load = force_bg_load; + return result; + } }; struct sampled_image_descriptor : public sampled_image_descriptor_base { + // Lets the templated helpers in texture_cache_helpers.h name the deferred type. + using deferred_subresource_type = deferred_subresource; + image_view_type image_handle = 0; deferred_subresource external_subresource_desc = {}; bool flag = false; sampled_image_descriptor() = default; - sampled_image_descriptor(image_view_type handle, texture_upload_context ctx, rsx::format_class ftype, - size3f scale, rsx::texture_dimension_extended type, bool cyclic_reference = false, + sampled_image_descriptor( + image_view_type handle, + texture_upload_context ctx, + rsx::format_class ftype, + size3f scale, + rsx::texture_dimension_extended type, + u32 ref_address_, + bool cyclic_reference = false, u8 msaa_samples = 1) { image_handle = handle; @@ -208,6 +417,7 @@ namespace rsx is_cyclic_reference = cyclic_reference; image_type = type; samples = msaa_samples; + ref_address = ref_address_; texcoord_xform.scale[0] = scale.width; texcoord_xform.scale[1] = scale.height; @@ -218,17 +428,21 @@ namespace rsx texcoord_xform.clamp = false; } - sampled_image_descriptor(image_resource_type external_handle, deferred_request_command reason, - const image_section_attributes_t& attr, position2u src_offset, - texture_upload_context ctx, rsx::format_class ftype, size3f scale, - rsx::texture_dimension_extended type, const texture_channel_remap_t& remap) + sampled_image_descriptor( + deferred_subresource&& desc, + texture_upload_context ctx, + rsx::format_class ftype, + size3f scale, + rsx::texture_dimension_extended type, + u32 ref_address_) { - external_subresource_desc = { external_handle, reason, attr, src_offset, remap }; + external_subresource_desc = std::move(desc); image_handle = 0; upload_context = ctx; format_class = ftype; image_type = type; + ref_address = ref_address_; texcoord_xform.scale[0] = scale.width; texcoord_xform.scale[1] = scale.height; @@ -267,7 +481,7 @@ namespace rsx if (section_fills_target(sections[idx])) { const auto remaining = sections.size() - idx; - std::memcpy( + std::memmove( sections.data(), §ions[idx], remaining * sizeof(sections[0]) @@ -281,17 +495,13 @@ namespace rsx // Optimizations in the straightforward methods copy_image_static and copy_image_dynamic make them preferred over the atlas method if (sections.size() == 1 && section_fills_target(sections[0])) { - const auto cpy = sections[0]; + const auto& cpy = sections[0]; external_subresource_desc.external_ref_addr = cpy.base_addr; if (section_is_transfer_only(cpy)) { // Change the command to copy_image_static external_subresource_desc.external_handle = cpy.src; - external_subresource_desc.x = cpy.src_x; - external_subresource_desc.y = cpy.src_y; - external_subresource_desc.width = cpy.src_w; - external_subresource_desc.height = cpy.src_h; external_subresource_desc.op = deferred_request_command::copy_image_static; } else @@ -495,7 +705,7 @@ namespace rsx virtual image_view_type generate_3d_from_2d_images(commandbuffer_type&, const deferred_subresource& desc) = 0; virtual image_view_type generate_atlas_from_images(commandbuffer_type&, const deferred_subresource& desc) = 0; virtual image_view_type generate_2d_mipmaps_from_images(commandbuffer_type&, const deferred_subresource& desc) = 0; - virtual void update_image_contents(commandbuffer_type&, image_view_type dst, image_resource_type src, u16 width, u16 height) = 0; + virtual void update_image_contents(commandbuffer_type&, image_view_type dst, const deferred_subresource& desc) = 0; virtual bool render_target_format_is_compatible(image_storage_type* tex, u32 gcm_format) = 0; virtual void prepare_for_dma_transfers(commandbuffer_type&) = 0; virtual void cleanup_after_dma_transfers(commandbuffer_type&) = 0; @@ -1698,23 +1908,81 @@ namespace rsx return evicted_set.violation_handled; } + // Expands a _3d_unwrap descriptor into the explicit slice list used by _3d_gather. + static void expand_3d_unwrap_sections(rsx::simple_array& sections, const deferred_subresource& desc, u8 level) + { + sections.reserve(sections.size() + desc.depth); + const auto src_offset = desc.unwrap_offset(); + + for (u16 n = 0; n < desc.depth; ++n) + { + sections.push_back( + { + .src = desc.external_handle, + .xform = surface_transform::coordinate_transform, + .level = level, + .src_x = static_cast(src_offset.x), + .src_y = static_cast(src_offset.y + (desc.slice_h * n)), + .dst_x = 0, + .dst_y = 0, + .dst_z = n, + .src_w = desc.width, + .src_h = desc.height, + .dst_w = desc.width, + .dst_h = desc.height + }); + } + } + + // Expands a cube_unwrap descriptor into explicit slice list for use with cube_gather + static void expand_cube_unwrap_sections(rsx::simple_array& sections, const deferred_subresource& desc) + { + sections.resize(6u * desc.mipmaps); + const auto src_offset = desc.unwrap_offset(); + + for (u16 n = 0, section_id = 0; n < 6; ++n) + { + u16 mip_w = desc.width, mip_h = desc.height; + u16 y_offset = static_cast(src_offset.y + (desc.slice_h * n)); + + for (u8 mip = 0; mip < desc.mipmaps; ++mip) + { + sections[section_id++] = + { + .src = desc.external_handle, + .xform = surface_transform::coordinate_transform, + .level = mip, + .src_x = static_cast(src_offset.x), + .src_y = y_offset, + .dst_x = 0, + .dst_y = 0, + .dst_z = n, + .src_w = mip_w, + .src_h = mip_h, + .dst_w = mip_w, + .dst_h = mip_h + }; + + y_offset += mip_h; + mip_w = std::max(mip_w / 2, 1); + mip_h = std::max(mip_h / 2, 1); + } + } + } + image_view_type create_temporary_subresource(commandbuffer_type &cmd, deferred_subresource& desc) { if (!desc.do_not_cache) [[likely]] { + const auto desc_key = desc.encoded_properties(); const auto found = m_temporary_subresource_cache.equal_range(desc.address); for (auto It = found.first; It != found.second; ++It) { - const auto& found_desc = It->second.first; - if (found_desc.external_handle != desc.external_handle || - found_desc.op != desc.op || - found_desc.x != desc.x || found_desc.y != desc.y || - found_desc.width != desc.width || found_desc.height != desc.height || - found_desc.gcm_format != desc.gcm_format) + if (It->second.first.encoded_properties() != desc_key) continue; if (desc.op == deferred_request_command::copy_image_dynamic) - update_image_contents(cmd, It->second.second, desc.external_handle, desc.width, desc.height); + update_image_contents(cmd, It->second.second, desc); return It->second.second; } @@ -1732,37 +2000,11 @@ namespace rsx } case deferred_request_command::cubemap_unwrap: { - rsx::simple_array sections(6 * desc.mipmaps); - for (u16 n = 0, section_id = 0; n < 6; ++n) - { - u16 mip_w = desc.width, mip_h = desc.height; - u16 y_offset = static_cast(desc.slice_h * n); - - for (u8 mip = 0; mip < desc.mipmaps; ++mip) - { - sections[section_id++] = - { - .src = desc.external_handle, - .xform = surface_transform::coordinate_transform, - .level = mip, - .src_x = 0, - .src_y = y_offset, - .dst_x = 0, - .dst_y = 0, - .dst_z = n, - .src_w = mip_w, - .src_h = mip_h, - .dst_w = mip_w, - .dst_h = mip_h - }; - - y_offset += mip_h; - mip_w = std::max(mip_w / 2, 1); - mip_h = std::max(mip_h / 2, 1); - } - } + rsx::simple_array sections; + expand_cube_unwrap_sections(sections, desc); auto unwrap_desc = desc; + unwrap_desc.op = deferred_request_command::cubemap_gather; unwrap_desc.sections_to_copy = std::move(sections); result = generate_cubemap_from_images(cmd, unwrap_desc); @@ -1776,27 +2018,10 @@ namespace rsx case deferred_request_command::_3d_unwrap: { rsx::simple_array sections; - sections.resize(desc.depth); - for (u16 n = 0; n < desc.depth; ++n) - { - sections[n] = - { - .src = desc.external_handle, - .xform = surface_transform::coordinate_transform, - .level = 0, - .src_x = 0, - .src_y = static_cast(desc.slice_h * n), - .dst_x = 0, - .dst_y = 0, - .dst_z = n, - .src_w = desc.width, - .src_h = desc.height, - .dst_w = desc.width, - .dst_h = desc.height - }; - } + expand_3d_unwrap_sections(sections, desc, 0); auto unwrap_desc = desc; + unwrap_desc.op = deferred_request_command::_3d_gather; unwrap_desc.sections_to_copy = std::move(sections); result = generate_3d_from_2d_images(cmd, unwrap_desc); @@ -1884,7 +2109,15 @@ namespace rsx // Most mesh textures are stored as compressed to make the most of the limited memory if (auto cached_texture = find_texture_from_dimensions(attr.address, attr.gcm_format, attr.width, attr.height, attr.depth)) { - return{ cached_texture->get_view(remap), cached_texture->get_context(), cached_texture->get_format_class(), scale, cached_texture->get_image_type() }; + return + { + cached_texture->get_view(remap), + cached_texture->get_context(), + cached_texture->get_format_class(), + scale, + cached_texture->get_image_type(), + cached_texture->get_section_base() + }; } return {}; @@ -1899,7 +2132,7 @@ namespace rsx const bool force_convert = !render_target_format_is_compatible(texptr, attr.gcm_format); auto result = helpers::process_framebuffer_resource_fast( - cmd, texptr, attr, scale, extended_dimension, remap, true, force_convert); + cmd, texptr, attr, {}, scale, extended_dimension, remap, true, force_convert); if (!options.skip_texture_barriers && result.is_cyclic_reference) { @@ -1918,12 +2151,24 @@ namespace rsx auto fast_fbo_check = [&]() -> sampled_image_descriptor { const auto& last = overlapping_fbos.back(); - if (last.src_area.x == 0 && last.src_area.y == 0 && !last.is_clipped) + + if (!last.is_clipped) //<- A non-clipped hit fully contains the requested box. We're good to go with the framebuffer processing. { const bool force_convert = !render_target_format_is_compatible(last.surface, attr.gcm_format); - return helpers::process_framebuffer_resource_fast( - cmd, last.surface, attr, scale, extended_dimension, remap, false, force_convert); + // Need to check for cyclic ref since we now allow offsets. + const bool surface_is_rop_target = m_rtts.address_is_bound(last.base_address); + + auto result = helpers::process_framebuffer_resource_fast( + cmd, last.surface, attr, position2u(last.src_area.x, last.src_area.y), scale, extended_dimension, remap, surface_is_rop_target, force_convert); + + if (!options.skip_texture_barriers && result.is_cyclic_reference) + { + ensure(surface_is_rop_target); + insert_texture_barrier(cmd, last.surface); + } + + return result; } return {}; @@ -1932,7 +2177,7 @@ namespace rsx // Check surface cache early if the option is enabled if (options.prefer_surface_cache) { - const u16 block_h = (attr.depth * attr.slice_h); + const u32 block_h = (attr.depth * attr.slice_h); overlapping_fbos = m_rtts.get_merged_texture_memory_region(cmd, attr.address, attr.width, block_h, attr.pitch, attr.bpp, rsx::surface_access::shader_read); if (!overlapping_fbos.empty()) @@ -1977,7 +2222,15 @@ namespace rsx break; } - return{ cached_texture->get_view(remap), cached_texture->get_context(), cached_texture->get_format_class(), scale, cached_texture->get_image_type() }; + return + { + cached_texture->get_view(remap), + cached_texture->get_context(), + cached_texture->get_format_class(), + scale, + cached_texture->get_image_type(), + cached_texture->get_section_base() + }; } } @@ -1993,7 +2246,7 @@ namespace rsx if (!options.prefer_surface_cache) { // Now check for surface cache hits - const u16 block_h = (attr.depth * attr.slice_h); + const u32 block_h = (attr.depth * attr.slice_h); overlapping_fbos = m_rtts.get_merged_texture_memory_region(cmd, attr.address, attr.width, block_h, attr.pitch, attr.bpp, rsx::surface_access::shader_read); } @@ -2027,7 +2280,8 @@ namespace rsx else if (extended_dimension <= rsx::texture_dimension_extended::texture_dimension_2d) { const auto last = overlapping_locals.back(); - const auto normalized_width = u16(last->get_width() * get_format_block_size_in_bytes(last->get_gcm_format())) / attr.bpp; + const auto src_bpp = get_format_block_size_in_bytes(last->get_gcm_format()); + const auto normalized_width = u16((last->get_width() * src_bpp) / attr.bpp); if (last->get_section_base() == attr.address && normalized_width >= attr.width && last->get_height() >= attr.height) @@ -2048,15 +2302,38 @@ namespace rsx { // Clipped view auto viewed_image = last->get_raw_texture(); - sampled_image_descriptor result = { viewed_image->get_view(remap), last->get_context(), - viewed_image->format_class(), scale, extended_dimension, false, viewed_image->samples() }; + sampled_image_descriptor result = + { + viewed_image->get_view(remap), + last->get_context(), + viewed_image->format_class(), + scale, + extended_dimension, + attr.address, + false, + viewed_image->samples() + }; helpers::calculate_sample_clip_parameters(result, position2i(0, 0), size2i(attr.width, attr.height), size2i(normalized_width, last->get_height())); return result; } - return { last->get_raw_texture(), deferred_request_command::copy_image_static, new_attr, {}, - last->get_context(), classify_format(gcm_format), scale, extended_dimension, remap }; + // Declare transfer rect in dest space and request coordinate transform + const coord3u xfer_rect = { 0, 0, 0, attr.width, attr.height, 1 }; + return + { + deferred_subresource::create_copy( + last->get_raw_texture(), + new_attr, + xfer_rect, + surface_transform::coordinate_transform, + remap), + last->get_context(), + classify_format(gcm_format), + scale, + extended_dimension, + attr.address + }; } } @@ -2073,22 +2350,7 @@ namespace rsx is_simple_subresource_copy && render_target_format_is_compatible(result.external_subresource_desc.src0(), attr.gcm_format)) { - if (result.external_subresource_desc.op != deferred_request_command::blit_image_static) [[ likely ]] - { - helpers::convert_image_copy_to_clip_descriptor( - result, - position2i(result.external_subresource_desc.x, result.external_subresource_desc.y), - size2i(result.external_subresource_desc.width, result.external_subresource_desc.height), - size2i(result.external_subresource_desc.external_handle->width(), result.external_subresource_desc.external_handle->height()), - remap, false); - } - else - { - helpers::convert_image_blit_to_clip_descriptor( - result, - remap, - false); - } + helpers::convert_image_transfer_to_clip_descriptor(result, remap, false); if (!!result.ref_address && m_rtts.address_is_bound(result.ref_address)) { @@ -2219,11 +2481,14 @@ namespace rsx } else if (descriptor->image_handle) { + // Sanity check + ensure(descriptor->format_ex.format_bits == tex.format()); + // Rebuild duplicate surface auto src = descriptor->image_handle->image(); rsx::image_section_attributes_t attr; attr.address = descriptor->ref_address; - attr.gcm_format = tex.format() & ~(CELL_GCM_TEXTURE_LN | CELL_GCM_TEXTURE_UN); + attr.gcm_format = descriptor->format_ex.format(); attr.width = src->width(); attr.height = src->height(); attr.depth = 1; @@ -2243,14 +2508,9 @@ namespace rsx attr.gcm_format = helpers::get_compatible_depth_format(attr.gcm_format); } - descriptor->external_subresource_desc = - { - src, - rsx::deferred_request_command::copy_image_dynamic, - attr, - {}, - rsx::default_remap_vector - }; + const coord3u copy_rect = { 0, 0, 0, attr.width, attr.height, 1 }; + descriptor->external_subresource_desc = deferred_subresource::create_copy( + src, attr, copy_rect, rsx::surface_transform::identity, rsx::default_remap_vector, true); descriptor->external_subresource_desc.do_not_cache = true; descriptor->image_handle = nullptr; @@ -2265,6 +2525,133 @@ namespace rsx return result.first; } + // Combine multiple mip level scan results into one gather op + template + void gather_3d_mipmap_levels( + commandbuffer_type& cmd, + sampled_image_descriptor& base_level, + const RsxTextureType& tex, + const image_section_attributes_t& attributes, + const size3f& scale, + texture_cache_search_options options, + surface_store_type& m_rtts, + Args&&... extras) + { + auto& desc = base_level.external_subresource_desc; + rsx::simple_array sections; + auto attr2 = attributes; + u32 mipchain_length = static_cast(get_texture_size(tex, 0)); + bool force_bg_load = desc.force_bg_load; + u16 levels_found = 1; + + options.skip_texture_merge = true; //<- We expect all the data to live on either host or guest. Gaps here will be closed by the blit-engine surface cache integration work later (e.g mipchain gen using nv3089). + options.skip_texture_barriers = true; //<- We'll be copying the data out, ignore texture barriers. + options.prefer_surface_cache = (base_level.upload_context == rsx::texture_upload_context::framebuffer_storage); + + for (u8 level = 1; level < attributes.mipmaps; ++level) + { + attr2.address = attributes.address + mipchain_length; + attr2.width = std::max(attr2.width / 2, 1); + attr2.height = std::max(attr2.height / 2, 1); + attr2.depth = std::max(attr2.depth / 2, 1); + attr2.mipmaps = 1; + + // NOTE: Linear textures have the higher mip levels keep the same pitch as the base. This does not work for swizzled though. + if (attributes.swizzled) + { + attr2.pitch = get_format_packed_pitch(attr2.gcm_format, attr2.width, tex.border_type() == CELL_GCM_TEXTURE_BORDER_TEXTURE, true); + } + + const u32 level_size = static_cast(get_texture_size(tex, level)); + if (!level_size || !attr2.pitch) + { + break; + } + + attr2.slice_h = static_cast((level_size / attr2.pitch) / attr2.depth); + + const auto range = utils::address_range32::start_length(attr2.address, level_size); + auto ret = fast_texture_search(cmd, attr2, scale, tex.decoded_remap(), + options, range, rsx::texture_dimension_extended::texture_dimension_3d, + m_rtts, std::forward(extras)...); + + if (!ret.validate() || ret.image_handle) + { + // Level is unavailable, or resolved to a whole image which we cannot splice in as a 2D source. + break; + } + + if (sections.empty()) + { + // Expand the base sections here. One-shot. + if (desc.op == deferred_request_command::_3d_unwrap) + { + expand_3d_unwrap_sections(sections, desc, 0); + } + else + { + sections = std::move(desc.sections_to_copy); + } + + ensure(!sections.empty()); // Impossible situation + } + + auto& sub_desc = ret.external_subresource_desc; + const auto insert_pos = sections.size(); + + switch (sub_desc.op) + { + case deferred_request_command::_3d_unwrap: + expand_3d_unwrap_sections(sections, sub_desc, level); + break; + case deferred_request_command::copy_image_static: + case deferred_request_command::copy_image_dynamic: + case deferred_request_command::blit_image_static: + case deferred_request_command::atlas_gather: + if (attr2.depth > 1) break; + [[ fallthrough ]]; + case deferred_request_command::_3d_gather: + for (const auto& section : sub_desc.sections_to_copy) + { + sections.push_back(section); + sections.back().level = level; + } + break; + default: + break; + } + + if (sections.size() == insert_pos) + { + // Nothing was added. + break; + } + + force_bg_load |= sub_desc.force_bg_load; + mipchain_length += level_size; + levels_found++; + } + + if (levels_found == 1) + { + // No new mip levels found. Restore the original desc if it was modified. + if (desc.sections_to_copy.empty() && + desc.op != deferred_request_command::_3d_unwrap) + { + ensure(!sections.empty()); + desc.sections_to_copy = std::move(sections); + } + return; + } + + // Create the new descriptor for all our new data. + desc.op = deferred_request_command::_3d_gather; + desc.mipmaps = levels_found; + desc.force_bg_load = force_bg_load; + desc.sections_to_copy = std::move(sections); + desc.cache_range = utils::address_range32::start_length(attributes.address, mipchain_length); + } + template sampled_image_descriptor upload_texture(commandbuffer_type& cmd, const RsxTextureType& tex, surface_store_type& m_rtts, Args&&... extras) { @@ -2347,15 +2734,14 @@ namespace rsx attributes.depth = 6; subsurface_count = 1; tex_size = static_cast(get_texture_size(tex)); - required_surface_height = tex_size / attributes.pitch; - attributes.slice_h = required_surface_height / attributes.depth; + required_surface_height = tex_size / attributes.pitch; //<- Cubemap mipmaps are laid inline with their respective faces. + attributes.slice_h = required_surface_height / attributes.depth; //<- Slice height should match attr.height * 2 assuming full mipchain per face. break; case rsx::texture_dimension_extended::texture_dimension_3d: attributes.depth = tex.depth(); subsurface_count = 1; - tex_size = static_cast(get_texture_size(tex)); - required_surface_height = tex_size / attributes.pitch; - attributes.slice_h = required_surface_height / attributes.depth; + required_surface_height = static_cast(get_texture_size(tex, 0)) / attributes.pitch; //<- Mipmaps for 3D are laid out one at a time, we compute only level 0 size. + attributes.slice_h = required_surface_height / attributes.depth; //<- Should match attr.height for most cases unless block textures or borders are involved. break; default: fmt::throw_exception("Unsupported texture dimension %d", static_cast(extended_dimension)); @@ -2402,6 +2788,15 @@ namespace rsx result.surface_cache_tag = m_rtts.write_tag; + // A 3D texture keeps each mipmap level in its own group of depth slices separate as complete sub-textures. + // Scan for each of the mip levels individually. Best-effort impl, we cannot promise to capture all of them. + if (attributes.mipmaps > 1 && !result.image_handle && + (result.external_subresource_desc.op == deferred_request_command::_3d_gather || + result.external_subresource_desc.op == deferred_request_command::_3d_unwrap)) + { + gather_3d_mipmap_levels(cmd, result, tex, attributes, scale, options, m_rtts, std::forward(extras)...); + } + if (subsurface_count == 1) { return result; @@ -2475,10 +2870,14 @@ namespace rsx } else { + // Grab the correct image dimensions from the base mipmap level before the list is consumed + const auto mip0 = sections.front(); + // NOTE: Do not disable 'cyclic ref' since the texture_barrier may have already been issued! result.image_handle = 0; - result.external_subresource_desc = { 0, deferred_request_command::mipmap_gather, attributes, {}, tex.decoded_remap() }; result.format_class = rsx::classify_format(attributes.gcm_format); + result.external_subresource_desc = deferred_subresource::create_mipmap_gather( + attributes, std::move(sections), tex.decoded_remap()); if (result.texcoord_xform.clamp) { @@ -2488,8 +2887,6 @@ namespace rsx if (use_upscaling) { - // Grab the correct image dimensions from the base mipmap level - const auto& mip0 = sections.front(); result.external_subresource_desc.width = mip0.dst_w; result.external_subresource_desc.height = mip0.dst_h; } @@ -2497,7 +2894,6 @@ namespace rsx const u32 cache_end = attr2.address + (attr2.pitch * attr2.height); result.external_subresource_desc.cache_range = utils::address_range32::start_end(attributes.address, cache_end); - result.external_subresource_desc.sections_to_copy = std::move(sections); return result; } } @@ -2523,8 +2919,15 @@ namespace rsx auto uploaded = upload_image_from_cpu(cmd, tex_range, attributes.width, attributes.height, attributes.depth, tex.get_exact_mipmap_count(), attributes.pitch, attributes.gcm_format, texture_upload_context::shader_read, subresources_layout, extended_dimension, attributes.swizzled); - return{ uploaded->get_view(tex.decoded_remap()), - texture_upload_context::shader_read, format_class, scale, extended_dimension }; + return + { + uploaded->get_view(tex.decoded_remap()), + texture_upload_context::shader_read, + format_class, + scale, + extended_dimension, + attributes.address + }; } // FIXME: This function is way too large and needs an urgent refactor. diff --git a/rpcs3/Emu/RSX/Common/texture_cache_helpers.h b/rpcs3/Emu/RSX/Common/texture_cache_helpers.h index 3153f5b9a..81cd4c676 100644 --- a/rpcs3/Emu/RSX/Common/texture_cache_helpers.h +++ b/rpcs3/Emu/RSX/Common/texture_cache_helpers.h @@ -255,15 +255,12 @@ namespace rsx bool unordered_list = false; rsx::simple_array sort_list; - rsx::simple_array sort_ranges; sort_list.reserve(available_slices); - sort_ranges.reserve(available_slices); // Generate sorting tree if both resources are available and overlapping for (u32 index = 0; index < fbos.size(); ++index) { const auto range = fbos[index].surface->get_memory_range(); - sort_ranges.push_back(range); sort_list.push_back({ .tag = fbos[index].surface->last_use_tag, .list = 0, @@ -278,7 +275,6 @@ namespace rsx continue; const auto range = local[index]->get_section_range(); - sort_ranges.push_back(range); sort_list.push_back({ .tag = local[index]->last_write_tag, .list = 1, @@ -293,14 +289,9 @@ namespace rsx } // Check if ordered - for (u32 i = 0; i < sort_list.size(); ++i) + for (u32 i = 1; i < sort_list.size(); ++i) { - if (i == 0) - { - continue; - } - - if (sort_ranges[i].start < sort_ranges[i - 1].end) + if (sort_list[i].bounds.start <= sort_list[i - 1].bounds.end) { unordered_list = true; break; @@ -349,6 +340,7 @@ namespace rsx std::tie(dst_x, dst_y) = rsx::apply_resolution_scale(section.surface->resolution_scaling_config, dst_x, dst_y, attr.width, attr.height); section.surface->memory_barrier(cmd, rsx::surface_access::transfer_read); + const bool output_covered = (section.dst_area.width >= attr.width) && (section_end >= slice_end); out.push_back ({ @@ -367,7 +359,7 @@ namespace rsx .dst_h = dst_height }); - return { section_end <= slice_end, section_end >= slice_end }; + return { section_end <= slice_end, output_covered }; }; auto add_local_resource = [&](auto& section, u32 address, u16 slice, bool scaling) -> std::pair // [ input fully consumed, output fully covered ] @@ -395,35 +387,39 @@ namespace rsx const size2u dst_size = { dimensions.width / attr.bpp, dimensions.height }; const size2u src_size = { dimensions.width / section_bpp, dimensions.height }; - const u32 dst_slice_begin = slice * attr.slice_h; // Output slice low watermark - const u32 dst_slice_end = dst_slice_begin + attr.height; // Output slice high watermark - + // NOTE: Computed dst_y is slice-local, not 2d-local. Our slice_h becomes our height. + // FIXME: This approach does not support mipmap levels. + const u32 dst_slice_h = attr.height; const auto dst_y = dst_offset.y; const auto dst_h = dst_size.height; const auto write_section_end = dst_y + dst_h; - if (dst_y >= dst_slice_end || write_section_end <= dst_slice_begin) + const bool input_consumed = (write_section_end < attr.slice_h); + + if (dst_y >= dst_slice_h) { // Belongs to a different slice - return { write_section_end <= dst_slice_begin, false }; + return { input_consumed, false }; } const u16 dst_w = static_cast(dst_size.width); const u16 src_w = static_cast(src_size.width); - const u16 height = std::min(dst_slice_end, write_section_end) - dst_y; + const u16 height = std::min(dst_slice_h, write_section_end) - dst_y; + const bool output_covered = (dst_w >= attr.width) && (write_section_end >= dst_slice_h); if (scaling) { // Since output is upscaled, also upscale on dst const auto& scaling_config = rsx::get_current_renderer()->resolution_scaling_config; - const auto [_dst_x, _dst_y] = rsx::apply_resolution_scale(scaling_config, static_cast(dst_offset.x), static_cast(dst_y - dst_slice_begin), attr.width, attr.height); + const auto [_dst_x, _dst_y] = rsx::apply_resolution_scale(scaling_config, static_cast(dst_offset.x), static_cast(dst_y), attr.width, attr.height); const auto [_dst_w, _dst_h] = rsx::apply_resolution_scale(scaling_config, dst_w, height, attr.width, attr.height); out.push_back ({ .src = section->get_raw_texture(), .xform = surface_transform::identity, + .base_addr = section->get_section_base(), .level = 0, .src_x = static_cast(src_offset.x), // src.x .src_y = static_cast(src_offset.y), // src.y @@ -436,26 +432,27 @@ namespace rsx .dst_h = _dst_h }); - return { write_section_end <= dst_slice_end, write_section_end >= dst_slice_end }; + return { input_consumed, output_covered }; } out.push_back ({ .src = section->get_raw_texture(), .xform = surface_transform::identity, + .base_addr = section->get_section_base(), .level = 0, .src_x = static_cast(src_offset.x), // src.x .src_y = static_cast(src_offset.y), // src.y .dst_x = static_cast(dst_offset.x), // dst.x - .dst_y = static_cast(dst_y - dst_slice_begin), // dst.y - .dst_z = 0, + .dst_y = static_cast(dst_y), // dst.y + .dst_z = slice, .src_w = src_w, .src_h = height, .dst_w = dst_w, .dst_h = height }); - return { write_section_end <= dst_slice_end, write_section_end >= dst_slice_end }; + return { input_consumed, output_covered }; }; u32 current_address = attr.address; @@ -593,7 +590,7 @@ namespace rsx } template - void convert_image_blit_to_clip_descriptor( + void convert_image_transfer_to_clip_descriptor( sampled_image_descriptor& desc, const texture_channel_remap_t& decoded_remap, bool cyclic_reference) @@ -601,9 +598,9 @@ namespace rsx // Our "desired" output is the source window, and the "actual" output is the real size const auto& section = desc.external_subresource_desc.sections_to_copy[0]; - // Apply AA correct factor auto surface_width = section.src->width(); auto surface_height = section.src->height(); + switch (section.src->samples()) { case 1: @@ -637,25 +634,45 @@ namespace rsx sampled_image_descriptor process_framebuffer_resource_fast(commandbuffer_type& cmd, render_target_type texptr, const image_section_attributes_t& attr, + const position2u& offset, const size3f& scale, texture_dimension_extended extended_dimension, const texture_channel_remap_t& decoded_remap, bool surface_is_rop_target, bool force_convert) { + using deferred_subresource_type = typename sampled_image_descriptor::deferred_subresource_type; + const auto surface_width = texptr->template get_surface_width(); const auto surface_height = texptr->template get_surface_height(); bool is_depth = texptr->is_depth_surface(); auto attr2 = attr; + // Track the clipping offset. Typically it is {0, 0} which means exact match, but subregions can also use this function and we need to scale this up from guest to host. + auto scaled_offset = offset; + + // If src and dst are mismatched in bpp, scale the offset to match dest bpp. + // That way we can use coordinate transform to reverse the conversion, which we already need to do anyway. + // We do that before res scaling to avoid rounding errors. + if (scaled_offset.x) + { + if (const auto surface_bpp = texptr->get_bpp(); + surface_bpp != attr.bpp) + { + scaled_offset.x = (scaled_offset.x * surface_bpp) / attr.bpp; + } + } + if (texptr->resolution_scaling_config.scale_percent != 100) { const auto [scaled_w, scaled_h] = rsx::apply_resolution_scale(texptr->resolution_scaling_config, attr.width, attr.height, surface_width, surface_height); const auto [unused, scaled_slice_h] = rsx::apply_resolution_scale(texptr->resolution_scaling_config, RSX_SURFACE_DIMENSION_IGNORED, attr.slice_h, surface_width, surface_height); + const auto [scaled_off_x, scaled_off_y] = rsx::apply_resolution_scale(texptr->resolution_scaling_config, static_cast(scaled_offset.x), static_cast(scaled_offset.y), surface_width, surface_height); attr2.width = scaled_w; attr2.height = scaled_h; attr2.slice_h = scaled_slice_h; + scaled_offset = { scaled_off_x, scaled_off_y }; } if (const bool gcm_format_is_depth = is_gcm_depth_format(attr2.gcm_format); @@ -696,7 +713,7 @@ namespace rsx rsx::surface_access access_type = rsx::surface_access::shader_read; - if (attr.width != surface_width || attr.height != surface_height) + if (offset.x || offset.y || attr.width != surface_width || attr.height != surface_height) { // If we can get away with clip only, do it if (attr.edge_clamped) @@ -726,22 +743,40 @@ namespace rsx if (requires_processing) { const auto format_class = (force_convert) ? classify_format(attr2.gcm_format) : texptr->format_class(); - const auto command = surface_is_rop_target ? deferred_request_command::copy_image_dynamic : deferred_request_command::copy_image_static; + + // This path is only reachable when the source region fully covers the request. + const coord3u dst_rect = { 0, 0, 0, attr2.width, attr2.height, 1 }; + const coord3u src_rect = { scaled_offset.x, scaled_offset.y, 0, attr2.width, attr2.height, 1 }; texptr->memory_barrier(cmd, rsx::surface_access::transfer_read); - return { texptr->get_surface(rsx::surface_access::transfer_read), command, attr2, {}, - texture_upload_context::framebuffer_storage, format_class, scale, - extended_dimension, decoded_remap }; + return + { + deferred_subresource_type::create_copy( + texptr->get_surface(rsx::surface_access::transfer_read), + attr2, src_rect, dst_rect, rsx::surface_transform::coordinate_transform, decoded_remap, surface_is_rop_target), + texture_upload_context::framebuffer_storage, + format_class, scale, extended_dimension, + texptr->base_addr + }; } texptr->memory_barrier(cmd, access_type); auto viewed_surface = texptr->get_surface(access_type); - sampled_image_descriptor result = { viewed_surface->get_view(decoded_remap), texture_upload_context::framebuffer_storage, - texptr->format_class(), scale, rsx::texture_dimension_extended::texture_dimension_2d, surface_is_rop_target, viewed_surface->samples() }; + sampled_image_descriptor result = + { + viewed_surface->get_view(decoded_remap), + texture_upload_context::framebuffer_storage, + texptr->format_class(), + scale, + rsx::texture_dimension_extended::texture_dimension_2d, + texptr->base_addr, + surface_is_rop_target, + viewed_surface->samples() + }; if (requires_clip) { - calculate_sample_clip_parameters(result, position2i(0, 0), size2i(attr.width, attr.height), size2i(surface_width, surface_height)); + calculate_sample_clip_parameters(result, position2i(offset.x, offset.y), size2i(attr.width, attr.height), size2i(surface_width, surface_height)); } return result; @@ -752,18 +787,24 @@ namespace rsx if (extended_dimension == rsx::texture_dimension_extended::texture_dimension_3d) { - return{ texptr->get_surface(rsx::surface_access::transfer_read), deferred_request_command::_3d_unwrap, - attr2, {}, - texture_upload_context::framebuffer_storage, format_class, scale, - rsx::texture_dimension_extended::texture_dimension_3d, decoded_remap }; + return + { + deferred_subresource_type::create_3d_unwrap(texptr->get_surface(rsx::surface_access::transfer_read), scaled_offset, attr2, decoded_remap), + texture_upload_context::framebuffer_storage, format_class, scale, + rsx::texture_dimension_extended::texture_dimension_3d, + texptr->base_addr + }; } ensure(extended_dimension == rsx::texture_dimension_extended::texture_dimension_cubemap); - return{ texptr->get_surface(rsx::surface_access::transfer_read), deferred_request_command::cubemap_unwrap, - attr2, {}, - texture_upload_context::framebuffer_storage, format_class, scale, - rsx::texture_dimension_extended::texture_dimension_cubemap, decoded_remap }; + return + { + deferred_subresource_type::create_cubemap_unwrap(texptr->get_surface(rsx::surface_access::transfer_read), scaled_offset, attr2, decoded_remap), + texture_upload_context::framebuffer_storage, format_class, scale, + rsx::texture_dimension_extended::texture_dimension_cubemap, + texptr->base_addr + }; } template @@ -776,6 +817,9 @@ namespace rsx const texture_channel_remap_t& decoded_remap, int select_hint = -1) { + using deferred_subresource_type = typename sampled_image_descriptor::deferred_subresource_type; + using transfer_sections_list_t = typename deferred_subresource_type::section_array_type; + ensure((select_hint & 0x1) == select_hint); bool is_depth = (select_hint == 0) ? fbos.back().is_depth : local.back()->is_depth_texture(); @@ -841,26 +885,32 @@ namespace rsx attr2.width = scaled_w; attr2.height = scaled_h; - sampled_image_descriptor desc = { nullptr, deferred_request_command::cubemap_gather, - attr2, {}, - upload_context, format_class, scale, - rsx::texture_dimension_extended::texture_dimension_cubemap, decoded_remap }; + transfer_sections_list_t sections; + const bool complete = gather_texture_slices(cmd, sections, fbos, local, attr, 6, is_depth); - desc.external_subresource_desc.force_bg_load = !gather_texture_slices(cmd, desc.external_subresource_desc.sections_to_copy, fbos, local, attr, 6, is_depth); - return desc; + return + { + deferred_subresource_type::create_cubemap_gather(attr2, std::move(sections), decoded_remap, !complete), + upload_context, format_class, scale, + rsx::texture_dimension_extended::texture_dimension_cubemap, + attr.address + }; } else if (extended_dimension == rsx::texture_dimension_extended::texture_dimension_3d && attr.depth > 1) { attr2.width = scaled_w; attr2.height = scaled_h; - sampled_image_descriptor desc = { nullptr, deferred_request_command::_3d_gather, - attr2, {}, - upload_context, format_class, scale, - rsx::texture_dimension_extended::texture_dimension_3d, decoded_remap }; + transfer_sections_list_t sections; + const bool complete = gather_texture_slices(cmd, sections, fbos, local, attr, attr.depth, is_depth); - desc.external_subresource_desc.force_bg_load = !gather_texture_slices(cmd, desc.external_subresource_desc.sections_to_copy, fbos, local, attr, attr.depth, is_depth); - return desc; + return + { + deferred_subresource_type::create_3d_gather(attr2, std::move(sections), decoded_remap, !complete), + upload_context, format_class, scale, + rsx::texture_dimension_extended::texture_dimension_3d, + attr.address + }; } if (extended_dimension == rsx::texture_dimension_extended::texture_dimension_1d) @@ -874,11 +924,17 @@ namespace rsx attr2.height = scaled_h; } - sampled_image_descriptor result = { nullptr, deferred_request_command::atlas_gather, - attr2, {}, upload_context, format_class, - scale, rsx::texture_dimension_extended::texture_dimension_2d, decoded_remap }; + typename deferred_subresource_type::section_array_type sections; + const bool complete = gather_texture_slices(cmd, sections, fbos, local, attr, 1, is_depth); + + sampled_image_descriptor result = + { + deferred_subresource_type::create_atlas_gather(attr2, std::move(sections), decoded_remap, !complete), + upload_context, format_class, scale, + rsx::texture_dimension_extended::texture_dimension_2d, + attr.address + }; - result.external_subresource_desc.force_bg_load = !gather_texture_slices(cmd, result.external_subresource_desc.sections_to_copy, fbos, local, attr, 1, is_depth); result.simplify(); return result; } @@ -904,12 +960,48 @@ namespace rsx .dst_h = attr.height }; - // "Fast" framebuffer results are a perfect match for attr so we do not store transfer sizes - // Calculate transfer dimensions from attr - if (level.upload_context == rsx::texture_upload_context::framebuffer_storage) [[likely]] + // Stash the surface cache view and dimensions for later + using surface_type = std::invoke_result_t; + surface_type rtv = nullptr; + u16 surface_w, surface_h; + + if (level.upload_context == rsx::texture_upload_context::framebuffer_storage) { - auto rtv = as_surface_type(mip); - std::tie(mip.src_w, mip.src_h) = rsx::apply_resolution_scale(rtv->resolution_scaling_config, attr.width, attr.height); + rtv = as_surface_type(mip); + surface_w = rtv->template get_surface_width(); + surface_h = rtv->template get_surface_height(); + } + else + { + surface_w = static_cast(level.image_handle->image()->width()); + surface_h = static_cast(level.image_handle->image()->height()); + } + + // "Fast" framebuffer results are a perfect match for attr so we do not store transfer sizes + // We need to account for the offsets however. + if (level.texcoord_xform.clamp) + { + // What we got back was a 'window' of attr.w/attr.h inside a bigger texture region. + // Reverse construct the src_x and src_y parameters + mip.src_x = static_cast(level.texcoord_xform.bias[0] * surface_w + 0.5f); + mip.src_y = static_cast(level.texcoord_xform.bias[1] * surface_h + 0.5f); + + // We cannot get here if bpp is mismatched. The whole point of getting a window back is that we avoid a bitcast operation. + ensure(!rtv || rtv->get_bpp() == attr.bpp); + } + + // Calculate transfer dimensions from attr + if (rtv) [[likely]] + { + std::tie(mip.src_x, mip.src_y) = rsx::apply_resolution_scale( + rtv->resolution_scaling_config, + mip.src_x, mip.src_y, + surface_w, surface_h); + + std::tie(mip.src_w, mip.src_h) = rsx::apply_resolution_scale( + rtv->resolution_scaling_config, + attr.width, attr.height, + surface_w, surface_h); } else { @@ -926,17 +1018,18 @@ namespace rsx case deferred_request_command::copy_image_dynamic: case deferred_request_command::copy_image_static: { + const auto& base_xfer = level.external_subresource_desc.sections_to_copy.front(); copy_region_descriptor_type mip { - .src = level.external_subresource_desc.external_handle, - .xform = surface_transform::coordinate_transform, + .src = base_xfer.src, + .xform = base_xfer.xform, .level = mipmap_level, // NOTE: gather_texture_slices pre-applies resolution scaling - .src_x = level.external_subresource_desc.x, - .src_y = level.external_subresource_desc.y, - .src_w = level.external_subresource_desc.width, - .src_h = level.external_subresource_desc.height, + .src_x = base_xfer.src_x, + .src_y = base_xfer.src_y, + .src_w = base_xfer.src_w, + .src_h = base_xfer.src_h, .dst_w = attr.width, .dst_h = attr.height @@ -956,11 +1049,23 @@ namespace rsx // Check for upscaling if requested if (apply_upscaling) { + // NOTE: Due to how mipmaps clamp at the lower levels, we will not blindly allow upscaling for the mip dimensions. + // If we try to upscale we end up trying to write OOB - crash. const auto base_mip = as_surface_type(sections.front()); auto& mip = sections.back(); - std::tie(mip.dst_w, mip.dst_h) = rsx::apply_resolution_scale( - base_mip->resolution_scaling_config, - mip.dst_w, mip.dst_h, level0_attr.width, level0_attr.height); + + if (mipmap_level == 0) + { + std::tie(mip.dst_w, mip.dst_h) = rsx::apply_resolution_scale( + base_mip->resolution_scaling_config, + mip.dst_w, mip.dst_h, level0_attr.width, level0_attr.height); + } + else + { + const auto& mip0 = sections.front(); + mip.dst_w = std::max(mip0.dst_w >> mipmap_level, 1); + mip.dst_h = std::max(mip0.dst_h >> mipmap_level, 1); + } } return true; diff --git a/rpcs3/Emu/RSX/GL/GLDraw.cpp b/rpcs3/Emu/RSX/GL/GLDraw.cpp index 38d76223f..3c5076a21 100644 --- a/rpcs3/Emu/RSX/GL/GLDraw.cpp +++ b/rpcs3/Emu/RSX/GL/GLDraw.cpp @@ -391,13 +391,9 @@ void GLGSRender::load_texture_env() { actual_mipcount = tex.get_exact_mipmap_count(); } - else if (sampler_state->external_subresource_desc.op == rsx::deferred_request_command::mipmap_gather) + else if (sampler_state->external_subresource_desc.op != rsx::deferred_request_command::nop) { - actual_mipcount = sampler_state->external_subresource_desc.sections_to_copy.size(); - } - else if (sampler_state->external_subresource_desc.op == rsx::deferred_request_command::cubemap_unwrap) - { - actual_mipcount = sampler_state->external_subresource_desc.mipmaps; + actual_mipcount = sampler_state->external_subresource_desc.exact_mip_count(); } m_fs_sampler_states[i].apply(tex, fs_sampler_state[i].get(), actual_mipcount > 1); @@ -516,11 +512,12 @@ void GLGSRender::bind_texture_env() using deferred_subresource_t = gl::texture_cache::deferred_subresource; auto image = static_cast(base->image()); + auto rtt = gl::try_as_rtt(base->image()); if (is_msaa) { // MSAA resolve - auto rtt = gl::as_rtt(base->image()); + ensure(rtt); rtt->memory_barrier(cmd, rsx::surface_access::transfer_read); image = rtt->get_surface(rsx::surface_access::transfer_read); } @@ -528,18 +525,22 @@ void GLGSRender::bind_texture_env() if (is_redirected) { // Force bitcast - deferred_subresource_t flatten_op{}; - flatten_op.address = desc->ref_address; - flatten_op.external_handle = image; - flatten_op.op = desc->is_cyclic_reference - ? rsx::deferred_request_command::copy_image_dynamic - : rsx::deferred_request_command::copy_image_static; - flatten_op.width = flatten_op.external_handle->width(); - flatten_op.height = flatten_op.external_handle->height(); - flatten_op.depth = 1; - flatten_op.gcm_format = desc->format_ex.format(); - flatten_op.remap = decoded_remap; - flatten_op.cache_range = utils::address_range32::start_length(desc->ref_address, attr.pitch * attr.height); + rsx::image_section_attributes_t flatten_attrs{}; + flatten_attrs.address = desc->ref_address; + flatten_attrs.gcm_format = desc->format_ex.format(); + flatten_attrs.width = image->width(); + flatten_attrs.height = image->height(); + flatten_attrs.depth = 1; + + const coord3u flatten_rect = { 0, 0, 0, flatten_attrs.width, flatten_attrs.height, 1 }; + auto flatten_op = deferred_subresource_t::create_copy( + image, flatten_attrs, flatten_rect, rsx::surface_transform::identity, decoded_remap, desc->is_cyclic_reference); + + ensure(desc->ref_address); + flatten_op.cache_range = rtt + ? rtt->get_memory_range() + : utils::address_range32::start_length(desc->ref_address, attr.pitch * attr.height); + return m_gl_texture_cache.create_temporary_subresource(cmd, flatten_op); } diff --git a/rpcs3/Emu/RSX/GL/GLPresent.cpp b/rpcs3/Emu/RSX/GL/GLPresent.cpp index 230d16694..1332b6a7a 100644 --- a/rpcs3/Emu/RSX/GL/GLPresent.cpp +++ b/rpcs3/Emu/RSX/GL/GLPresent.cpp @@ -145,7 +145,7 @@ gl::texture* GLGSRender::get_present_source(gl::present_surface_info* info, cons if (gl::formats_are_bitcast_compatible(flip_image.get(), image)) { const position3u offset{}; - gl::g_hw_blitter->copy_image(cmd, image, flip_image.get(), 0, 0, offset, offset, { info->width, info->height, 1 }); + gl::g_hw_blitter->copy_image(cmd, image, flip_image.get(), offset, offset, { info->width, info->height, 1 }); } else { @@ -364,7 +364,7 @@ void GLGSRender::flip(const rsx::display_flip_info_t& info) tex = m_sshot_tex.get(); static const position3u offset{}; - gl::g_hw_blitter->copy_image(cmd, image_to_flip, tex, 0, 0, offset, offset, { tex->width(), tex->height(), 1 }); + gl::g_hw_blitter->copy_image(cmd, image_to_flip, tex, offset, offset, { tex->width(), tex->height(), 1 }); render_overlays(tex, areau(0, 0, image_to_flip->width(), image_to_flip->height()), true); m_sshot_fbo.remove(); diff --git a/rpcs3/Emu/RSX/GL/GLProcTable.h b/rpcs3/Emu/RSX/GL/GLProcTable.h index f39610012..65bfbb798 100644 --- a/rpcs3/Emu/RSX/GL/GLProcTable.h +++ b/rpcs3/Emu/RSX/GL/GLProcTable.h @@ -152,6 +152,8 @@ OPENGL_PROC(PFNGLNAMEDFRAMEBUFFERRENDERBUFFERPROC, NamedFramebufferRenderbuffer) OPENGL_PROC(PFNGLNAMEDFRAMEBUFFERRENDERBUFFEREXTPROC, NamedFramebufferRenderbufferEXT); OPENGL_PROC(PFNGLNAMEDFRAMEBUFFERTEXTUREPROC, NamedFramebufferTexture); OPENGL_PROC(PFNGLNAMEDFRAMEBUFFERTEXTUREEXTPROC, NamedFramebufferTextureEXT); +OPENGL_PROC(PFNGLNAMEDFRAMEBUFFERTEXTURELAYERPROC, NamedFramebufferTextureLayer); +OPENGL_PROC(PFNGLNAMEDFRAMEBUFFERTEXTURELAYEREXTPROC, NamedFramebufferTextureLayerEXT); OPENGL_PROC(PFNGLNAMEDFRAMEBUFFERDRAWBUFFERSPROC, NamedFramebufferDrawBuffers); OPENGL_PROC(PFNGLFRAMEBUFFERDRAWBUFFERSEXTPROC, FramebufferDrawBuffersEXT); OPENGL_PROC(PFNGLNAMEDFRAMEBUFFERREADBUFFERPROC, NamedFramebufferReadBuffer); diff --git a/rpcs3/Emu/RSX/GL/GLRenderTargets.h b/rpcs3/Emu/RSX/GL/GLRenderTargets.h index f5e2252b9..a8891dcc7 100644 --- a/rpcs3/Emu/RSX/GL/GLRenderTargets.h +++ b/rpcs3/Emu/RSX/GL/GLRenderTargets.h @@ -121,6 +121,21 @@ namespace gl { return ensure(dynamic_cast(t)); } + + static inline gl::render_target* try_as_rtt(gl::texture* t) + { + return dynamic_cast(t); + } + + static inline const gl::render_target* try_as_rtt(const gl::texture* t) + { + return dynamic_cast(t); + } + + static inline bool is_rtt(const gl::texture* t) + { + return dynamic_cast(t) != nullptr; + } } struct gl_render_target_traits diff --git a/rpcs3/Emu/RSX/GL/GLTexture.cpp b/rpcs3/Emu/RSX/GL/GLTexture.cpp index 1d85e3ce2..362b6c4d6 100644 --- a/rpcs3/Emu/RSX/GL/GLTexture.cpp +++ b/rpcs3/Emu/RSX/GL/GLTexture.cpp @@ -559,7 +559,8 @@ namespace gl case texture::target::texture1D: { const position3u transfer_offset = { dst_region.position.x, 0, 0 }; - g_hw_blitter->copy_image(cmd, scratch.get(), dst, 0, dst_level, transfer_offset, transfer_offset, { dst_region.width, 1, 1 }); + g_hw_blitter->copy_image(cmd, scratch.get(), dst, transfer_offset, transfer_offset, { dst_region.width, 1, 1 }, + { .dst_mip_level = static_cast(dst_level) }); break; } case texture::target::texture3D: @@ -569,7 +570,8 @@ namespace gl { const position3u src_offset = { dst_region.position.x, dst_region.position.y + (i * dst_region.height), 0 }; const position3u dst_offset = { dst_region.position.x, dst_region.position.y, layer }; - g_hw_blitter->copy_image(cmd, scratch.get(), dst, 0, dst_level, src_offset, dst_offset, { dst_region.width, dst_region.height, 1 }); + g_hw_blitter->copy_image(cmd, scratch.get(), dst, src_offset, dst_offset, { dst_region.width, dst_region.height, 1 }, + { .dst_mip_level = static_cast(dst_level) }); } break; } @@ -1041,7 +1043,11 @@ namespace gl return formats_are_bitcast_compatible(static_cast(texture1->get_internal_format()), static_cast(texture2->get_internal_format())); } - void copy_typeless(gl::command_context& cmd, texture * dst, const texture * src, const coord3u& dst_region, const coord3u& src_region) + static void copy_typeless_impl( + gl::command_context& cmd, + texture * dst, const texture * src, + const coord3u& dst_region, const coord3u& src_region, + int src_level, int dst_level) { const auto src_bpp = src->pitch() / src->width(); const auto dst_bpp = dst->pitch() / dst->width(); @@ -1095,8 +1101,8 @@ namespace gl g_typeless_transfer_buffer.create(gl::buffer::target::ssbo, new_size); } - void* data_ptr = copy_image_to_buffer(cmd, pack_info, src, &g_typeless_transfer_buffer.get(), scratch_offset, 0, src_region, &src_mem); - copy_buffer_to_image(cmd, unpack_info, &g_typeless_transfer_buffer.get(), dst, data_ptr, 0, dst_region, &dst_mem); + void* data_ptr = copy_image_to_buffer(cmd, pack_info, src, &g_typeless_transfer_buffer.get(), scratch_offset, src_level, src_region, &src_mem); + copy_buffer_to_image(cmd, unpack_info, &g_typeless_transfer_buffer.get(), dst, data_ptr, dst_level, dst_region, &dst_mem); // Not truly range-accurate, but should cover most of what we care about g_typeless_transfer_buffer.push_barrier(scratch_offset, static_cast(min_storage_requirement)); @@ -1170,7 +1176,7 @@ namespace gl // Start pack operation pixel_pack_settings pack_settings{}; pack_settings.swap_bytes(pack_info.swap_bytes); - src->copy_to(g_typeless_transfer_buffer.get(), 0, static_cast(pack_info.format), static_cast(pack_info.type), 0, src_region, pack_settings); + src->copy_to(g_typeless_transfer_buffer.get(), 0, static_cast(pack_info.format), static_cast(pack_info.type), src_level, src_region, pack_settings); glBindBuffer(GL_PIXEL_PACK_BUFFER, GL_NONE); @@ -1178,11 +1184,55 @@ namespace gl pixel_unpack_settings unpack_settings{}; unpack_settings.swap_bytes(unpack_info.swap_bytes); - dst->copy_from(g_typeless_transfer_buffer.get(), 0, static_cast(unpack_info.format), static_cast(unpack_info.type), 0, dst_region, unpack_settings); + dst->copy_from(g_typeless_transfer_buffer.get(), 0, static_cast(unpack_info.format), static_cast(unpack_info.type), dst_level, dst_region, unpack_settings); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, GL_NONE); } } + void copy_typeless( + gl::command_context& cmd, + texture* dst, const texture* src, + const coord3u& dst_region, const coord3u& src_region, + const rsx::image_copy_subresource_layers& mip_layers) + { + ensure(mip_layers.layer_count == 1); + + // Can either be volume (3D/Cube) or layered, not both. + ensure(!src_region.z || !mip_layers.src_layer); + ensure(!dst_region.z || !mip_layers.dst_layer); + + coord3u src_rgn = src_region; + coord3u dst_rgn = dst_region; + + // Encode properties into OpenGL's X/Y/Z layout + if (mip_layers.src_layer) src_rgn.z = mip_layers.src_layer; + if (mip_layers.dst_layer) dst_rgn.z = mip_layers.dst_layer; + + int src_level = mip_layers.src_mip_level; + int dst_level = mip_layers.dst_mip_level; + + for (u32 remaining_levels = mip_layers.mipmap_count; remaining_levels > 0; --remaining_levels) + { + copy_typeless_impl(cmd, dst, src, dst_rgn, src_rgn, src_level, dst_level); + + if (remaining_levels > 1) + { + src_rgn.x /= 2; + src_rgn.y /= 2; + src_rgn.width = std::max(src_rgn.width / 2, 1u); + src_rgn.height = std::max(src_rgn.height / 2, 1u); + + dst_rgn.x /= 2; + dst_rgn.y /= 2; + dst_rgn.width = std::max(dst_rgn.width / 2, 1u); + dst_rgn.height = std::max(dst_rgn.height / 2, 1u); + + src_level++; + dst_level++; + } + } + } + void copy_typeless(gl::command_context& cmd, texture* dst, const texture* src) { const coord3u src_area = { {}, src->size3D() }; diff --git a/rpcs3/Emu/RSX/GL/GLTexture.h b/rpcs3/Emu/RSX/GL/GLTexture.h index 6d0bbb18a..0f4c089d4 100644 --- a/rpcs3/Emu/RSX/GL/GLTexture.h +++ b/rpcs3/Emu/RSX/GL/GLTexture.h @@ -70,7 +70,13 @@ namespace gl viewable_image* create_texture(u32 gcm_format, u16 width, u16 height, u16 depth, u16 mipmaps, rsx::texture_dimension_extended type); bool formats_are_bitcast_compatible(const texture* texture1, const texture* texture2); - void copy_typeless(gl::command_context& cmd, texture* dst, const texture* src, const coord3u& dst_region, const coord3u& src_region); + + void copy_typeless( + gl::command_context& cmd, + texture* dst, const texture* src, + const coord3u& dst_region, const coord3u& src_region, + const rsx::image_copy_subresource_layers& mip_layers = {}); + void copy_typeless(gl::command_context& cmd, texture* dst, const texture* src); void* copy_image_to_buffer(gl::command_context& cmd, const pixel_buffer_layout& pack_info, const gl::texture* src, gl::buffer* dst, diff --git a/rpcs3/Emu/RSX/GL/GLTextureCache.cpp b/rpcs3/Emu/RSX/GL/GLTextureCache.cpp index a40b265bd..16deadd28 100644 --- a/rpcs3/Emu/RSX/GL/GLTextureCache.cpp +++ b/rpcs3/Emu/RSX/GL/GLTextureCache.cpp @@ -121,7 +121,8 @@ namespace gl } gl::texture_view* texture_cache::create_temporary_subresource_impl(gl::command_context& cmd, gl::texture* src, GLenum sized_internal_fmt, GLenum dst_target, - u32 gcm_format, u16 x, u16 y, u16 width, u16 height, u16 depth, u8 mipmaps, const rsx::texture_channel_remap_t& remap, bool copy) + u32 gcm_format, u16 width, u16 height, u16 depth, u8 mipmaps, const rsx::texture_channel_remap_t& remap, + const copy_region_descriptor* copy) { if (sized_internal_fmt == GL_NONE) { @@ -160,18 +161,7 @@ namespace gl if (copy) { - rsx::simple_array region = - {{ - .src = src, - .xform = rsx::surface_transform::coordinate_transform, - .src_x = x, - .src_y = y, - .src_w = width, - .src_h = height, - .dst_w = width, - .dst_h = height - }}; - + rsx::simple_array region = { *copy }; copy_transfer_regions_impl(cmd, dst, region); } @@ -200,6 +190,8 @@ namespace gl const auto dst_bpp = dst_image->pitch() / dst_image->width(); const auto dst_aspect = dst_image->aspect(); + std::unique_ptr tmp; + for (const auto &slice : sources) { if (!slice.src) @@ -210,7 +202,6 @@ namespace gl const bool typeless = !formats_are_bitcast_compatible(slice.src, dst_image); ensure(typeless || dst_aspect == slice.src->aspect()); - std::unique_ptr tmp; auto src_image = slice.src; auto src_x = slice.src_x; auto src_y = slice.src_y; @@ -234,12 +225,19 @@ namespace gl { const auto src_bpp = slice.src->pitch() / slice.src->width(); const u16 convert_w = u16(slice.src->width() * src_bpp) / dst_bpp; - tmp = std::make_unique( - GL_TEXTURE_2D, - convert_w, slice.src->height(), - 1, 1, 1, - static_cast(dst_image->get_internal_format()), - dst_image->format_class()); + + if (!tmp || + tmp->width() < convert_w || + tmp->height() < slice.src->height() || + tmp->get_internal_format() != dst_image->get_internal_format()) + { + tmp = std::make_unique( + GL_TEXTURE_2D, + convert_w, slice.src->height(), + 1, 1, 1, + static_cast(dst_image->get_internal_format()), + dst_image->format_class()); + } src_image = tmp.get(); @@ -268,10 +266,11 @@ namespace gl if (src_w == slice.dst_w && src_h == slice.dst_h) { - gl::g_hw_blitter->copy_image(cmd, src_image, dst_image, 0, slice.level, + gl::g_hw_blitter->copy_image(cmd, src_image, dst_image, position3i{ src_x, src_y, 0 }, position3i{ slice.dst_x, slice.dst_y, slice.dst_z }, - size3i{ src_w, src_h, 1 }); + size3i{ src_w, src_h, 1 }, + { .dst_mip_level = slice.level }); } else { @@ -280,29 +279,48 @@ namespace gl const areai dst_rect = { slice.dst_x, slice.dst_y, slice.dst_x + slice.dst_w, slice.dst_y + slice.dst_h }; gl::texture* _dst = dst_image; - if (src_image->get_internal_format() != dst_image->get_internal_format() || - slice.level != 0 || - slice.dst_z != 0) [[ unlikely ]] + rsx::image_copy_subresource_layers mip_layers{ .dst_mip_level = slice.level }; + + const coord3i src_vol = { src_rect.x1, src_rect.y1, 0, src_rect.x2 - src_rect.x1, src_rect.y2 - src_rect.y1, 1 }; + coord3i dst_vol = { dst_rect.x1, dst_rect.y1, 0, dst_rect.x2 - dst_rect.x1, dst_rect.y2 - dst_rect.y1, 1 }; + + if (src_image->get_internal_format() != dst_image->get_internal_format()) { - tmp = std::make_unique( - GL_TEXTURE_2D, - dst_rect.x2, dst_rect.y2, - 1, 1, 1, - static_cast(slice.src->get_internal_format()), - slice.src->format_class()); + ensure(src_image == slice.src); + + if (!tmp || + tmp->width() < static_cast(dst_rect.x2) || + tmp->height() < static_cast(dst_rect.y2) || + tmp->get_internal_format() != slice.src->get_internal_format()) [[ unlikely ]] + { + tmp = std::make_unique( + GL_TEXTURE_2D, + dst_rect.x2, dst_rect.y2, + 1, 1, 1, + static_cast(slice.src->get_internal_format()), + slice.src->format_class()); + } _dst = tmp.get(); + mip_layers = {}; + } + else + { + // We can set either the target layer or target Z. + // Set target Z due to integer limits with the mip_layers. + dst_vol.z = slice.dst_z; } - _blitter->scale_image(cmd, src_image, _dst, src_rect, dst_rect, false, {}); + _blitter->scale_image(cmd, src_image, _dst, src_vol, dst_vol, false, {}, mip_layers); if (_dst != dst_image) { // Data cast comes after scaling - gl::g_hw_blitter->copy_image(cmd, tmp.get(), dst_image, 0, slice.level, + gl::g_hw_blitter->copy_image(cmd, tmp.get(), dst_image, position3i{slice.dst_x, slice.dst_y, 0}, position3i{slice.dst_x, slice.dst_y, slice.dst_z}, - size3i{slice.dst_w, slice.dst_h, 1}); + size3i{slice.dst_w, slice.dst_h, 1}, + { .dst_mip_level = slice.level }); } } } diff --git a/rpcs3/Emu/RSX/GL/GLTextureCache.h b/rpcs3/Emu/RSX/GL/GLTextureCache.h index 90ca47b8d..5219c104d 100644 --- a/rpcs3/Emu/RSX/GL/GLTextureCache.h +++ b/rpcs3/Emu/RSX/GL/GLTextureCache.h @@ -497,7 +497,8 @@ namespace gl void initialize_subresource_from_memory(gl::command_context& cmd, gl::texture* dst, const deferred_subresource& desc, rsx::texture_dimension_extended type) const; gl::texture_view* create_temporary_subresource_impl(gl::command_context& cmd, gl::texture* src, GLenum sized_internal_fmt, GLenum dst_type, u32 gcm_format, - u16 x, u16 y, u16 width, u16 height, u16 depth, u8 mipmaps, const rsx::texture_channel_remap_t& remap, bool copy); + u16 width, u16 height, u16 depth, u8 mipmaps, const rsx::texture_channel_remap_t& remap, + const copy_region_descriptor* copy = nullptr); std::array get_component_mapping(u32 gcm_format, rsx::component_order flags) const { @@ -575,15 +576,16 @@ namespace gl gl::texture_view* create_temporary_subresource_view(gl::command_context& cmd, const deferred_subresource& desc) override { - return create_temporary_subresource_impl(cmd, desc.external_handle, static_cast(desc.external_handle->get_internal_format()), - GL_TEXTURE_2D, desc.gcm_format, desc.x, desc.y, desc.width, desc.height, 1, 1, desc.remap, true); + ensure(desc.sections_to_copy.size() == 1); + const auto& section = desc.sections_to_copy.front(); + return create_temporary_subresource_impl(cmd, section.src, static_cast(section.src->get_internal_format()), + GL_TEXTURE_2D, desc.gcm_format, desc.width, desc.height, 1, 1, desc.remap, §ion); } gl::texture_view* generate_cubemap_from_images(gl::command_context& cmd, const deferred_subresource& desc) override { auto _template = get_template_from_collection_impl(desc.sections_to_copy); - const u8 mip_count = 1 + desc.sections_to_copy.reduce(0, FN(std::max(x, y.level))); - auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_CUBE_MAP, desc.gcm_format, 0, 0, desc.width, desc.height, 1, mip_count, desc.remap, false); + auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_CUBE_MAP, desc.gcm_format, desc.width, desc.height, 1, desc.exact_mip_count(), desc.remap); if (desc.force_bg_load) { @@ -597,7 +599,7 @@ namespace gl gl::texture_view* generate_3d_from_2d_images(gl::command_context& cmd, const deferred_subresource& desc) override { auto _template = get_template_from_collection_impl(desc.sections_to_copy); - auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_3D, desc.gcm_format, 0, 0, desc.width, desc.height, desc.depth, 1, desc.remap, false); + auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_3D, desc.gcm_format, desc.width, desc.height, desc.depth, desc.exact_mip_count(), desc.remap); if (desc.force_bg_load) { @@ -611,7 +613,7 @@ namespace gl gl::texture_view* generate_atlas_from_images(gl::command_context& cmd, const deferred_subresource& desc) override { auto _template = get_template_from_collection_impl(desc.sections_to_copy); - auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_2D, desc.gcm_format, 0, 0, desc.width, desc.height, 1, 1, desc.remap, false); + auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_2D, desc.gcm_format, desc.width, desc.height, 1, 1, desc.remap); if (desc.force_bg_load) { @@ -626,7 +628,7 @@ namespace gl { const auto mipmaps = ::narrow(desc.sections_to_copy.size()); auto _template = get_template_from_collection_impl(desc.sections_to_copy); - auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_2D, desc.gcm_format, 0, 0, desc.width, desc.height, 1, mipmaps, desc.remap, false); + auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_2D, desc.gcm_format, desc.width, desc.height, 1, mipmaps, desc.remap); if (desc.force_bg_load) { @@ -649,19 +651,9 @@ namespace gl } } - void update_image_contents(gl::command_context& cmd, gl::texture_view* dst, gl::texture* src, u16 width, u16 height) override + void update_image_contents(gl::command_context& cmd, gl::texture_view* dst, const deferred_subresource& desc) override { - rsx::simple_array region = - {{ - .src = src, - .xform = rsx::surface_transform::identity, - .src_w = width, - .src_h = height, - .dst_w = width, - .dst_h = height - }}; - - copy_transfer_regions_impl(cmd, dst->image(), region); + copy_transfer_regions_impl(cmd, dst->image(), desc.sections_to_copy); } cached_texture_section* create_new_texture(gl::command_context& cmd, const utils::address_range32 &rsx_range, u16 width, u16 height, u16 depth, u16 mipmaps, u32 pitch, diff --git a/rpcs3/Emu/RSX/GL/glutils/blitter.cpp b/rpcs3/Emu/RSX/GL/glutils/blitter.cpp index 47c7d7b1c..1beecb731 100644 --- a/rpcs3/Emu/RSX/GL/glutils/blitter.cpp +++ b/rpcs3/Emu/RSX/GL/glutils/blitter.cpp @@ -8,6 +8,44 @@ namespace gl { blitter* g_hw_blitter = nullptr; + // Max operation on the magnitude of value while preserving the sign + // Clamp_val must be >= 0 + static int mag_max(int value, int clamp_val) + { + if (value >= 0) + { + return std::max(value, clamp_val); + } + + return -std::max(-value, clamp_val); + } + + // Shrink a blit rectangle to describe the same window one mipmap level down. + static void increment_mip_level(areai& rect) + { + const int mip_w = (rect.x2 - rect.x1) / 2; + const int mip_h = (rect.y2 - rect.y1) / 2; + + rect.x1 /= 2; + rect.y1 /= 2; + rect.x2 = rect.x1 + mag_max(mip_w, 1); + rect.y2 = rect.y1 + mag_max(mip_h, 1); + } + + // Targets whose slices have to be selected explicitly. A flat attach binds every slice of these at once. + static bool is_layered_target(const texture* tex) + { + switch (tex->get_target()) + { + case texture::target::texture3D: + case texture::target::texture2DArray: + case texture::target::textureCUBE: + return true; + default: + return false; + } + } + void blitter::init() { blit_src.create(); @@ -20,33 +58,86 @@ namespace gl blit_src.remove(); } - void blitter::copy_image(gl::command_context&, const texture* src, const texture* dst, int src_level, int dst_level, const position3i& src_offset, const position3i& dst_offset, const size3i& size) const + void blitter::copy_image( + gl::command_context& cmd, + const texture* src, const texture* dst, + const position3i& src_offset, + const position3i& dst_offset, + const size3i& size, + const rsx::image_copy_subresource_layers& mip_layers) const { - ensure(src_level == 0); + // Sanity check - disallow layered volumes. Either set the layer parameters or the Z parameters, not both. + ensure(!src_offset.z || !mip_layers.src_layer); + ensure(!dst_offset.z || !mip_layers.dst_layer); + ensure(size.depth == 1 || mip_layers.layer_count == 1); - // Typeless bypass for BGRA8 - std::unique_ptr temp_image; - const texture* real_src = src; + // Sanity check - 3D textures can only copy 1 mip level ata a time + if (src->get_target() == texture::target::texture3D || dst->get_target() == texture::target::texture3D ) + { + ensure(mip_layers.mipmap_count == 1); + } - glCopyImageSubData(real_src->id(), static_cast(real_src->get_target()), src_level, - src_offset.x, src_offset.y, src_offset.z, - dst->id(), static_cast(dst->get_target()), dst_level, - dst_offset.x, dst_offset.y, dst_offset.z, size.width, size.height, size.depth); + auto src_pos = src_offset; + auto dst_pos = dst_offset; + auto extents = size; + + // Wrap the mess of layers and Z to match OpenGL's dumb specification. OGL only provides X/Y/Z to mean both volume and layers. + if (mip_layers.src_layer) src_pos.z = mip_layers.src_layer; + if (mip_layers.dst_layer) dst_pos.z = mip_layers.dst_layer; + if (mip_layers.layer_count > 1) extents.depth = mip_layers.layer_count; + + int src_level = mip_layers.src_mip_level; + int dst_level = mip_layers.dst_mip_level; + + for (u32 remaining_levels = mip_layers.mipmap_count; remaining_levels > 0; --remaining_levels) + { + glCopyImageSubData(src->id(), static_cast(src->get_target()), src_level, + src_pos.x, src_pos.y, src_pos.z, + dst->id(), static_cast(dst->get_target()), dst_level, + dst_pos.x, dst_pos.y, dst_pos.z, extents.width, extents.height, extents.depth); + + if (remaining_levels > 1) + { + // NOTE: We don't touch Z here, it's a layer index not a depth slice. + src_pos.x /= 2; + src_pos.y /= 2; + dst_pos.x /= 2; + dst_pos.y /= 2; + + extents.width = std::max(extents.width / 2, 1); + extents.height = std::max(extents.height / 2, 1); + + src_level++; + dst_level++; + } + } } - void blitter::scale_image(gl::command_context& cmd, const texture* src, texture* dst, areai src_rect, areai dst_rect, - bool linear_interpolation, const rsx::typeless_xfer& xfer_info) + void blitter::scale_image(gl::command_context& cmd, + const texture* src, texture* dst, + const coord3i& src_rect, const coord3i& dst_rect, + bool linear_interpolation, const rsx::typeless_xfer& xfer_info, + const rsx::image_copy_subresource_layers& mip_layers) { std::unique_ptr typeless_src; std::unique_ptr typeless_dst; const gl::texture* real_src = src; const gl::texture* real_dst = dst; + const bool targets_subresource = + mip_layers.src_mip_level || mip_layers.dst_mip_level || mip_layers.mipmap_count > 1 || + mip_layers.src_layer || mip_layers.dst_layer || mip_layers.layer_count > 1; + + // Typeless scratch images are allocated as flat level-0 2D surfaces sized from the base mip, so they cannot carry a subresource selection. + ensure(!targets_subresource || (!xfer_info.src_is_typeless && !xfer_info.dst_is_typeless)); + // Optimization pass; check for pass-through data transfer - if (!xfer_info.flip_horizontal && !xfer_info.flip_vertical && src_rect.height() == dst_rect.height()) + if (!xfer_info.flip_horizontal && !xfer_info.flip_vertical && + !src_rect.is_flipped() && !dst_rect.is_flipped() && + src_rect.height == dst_rect.height && src_rect.depth == dst_rect.depth) { - auto src_w = src_rect.width(); - auto dst_w = dst_rect.width(); + auto src_w = src_rect.width; + auto dst_w = dst_rect.width; if (xfer_info.src_is_typeless) src_w = static_cast(src_w * xfer_info.src_scaling_hint); if (xfer_info.dst_is_typeless) dst_w = static_cast(dst_w * xfer_info.dst_scaling_hint); @@ -56,19 +147,20 @@ namespace gl // Final dimensions are a match if (xfer_info.src_is_typeless || xfer_info.dst_is_typeless) { - const coord3i src_region = { { src_rect.x1, src_rect.y1, 0 }, { src_rect.width(), src_rect.height(), 1 } }; - const coord3i dst_region = { { dst_rect.x1, dst_rect.y1, 0 }, { dst_rect.width(), dst_rect.height(), 1 } }; - gl::copy_typeless(cmd, dst, src, static_cast(dst_region), static_cast(src_region)); + gl::copy_typeless(cmd, dst, src, static_cast(dst_rect), static_cast(src_rect), mip_layers); } else { - copy_image(cmd, src, dst, 0, 0, position3i{ src_rect.x1, src_rect.y1, 0u }, position3i{ dst_rect.x1, dst_rect.y1, 0 }, size3i{ src_rect.width(), src_rect.height(), 1 }); + copy_image(cmd, src, dst, src_rect.position, dst_rect.position, src_rect.size, mip_layers); } return; } } + auto src_rect_ = src_rect; + auto dst_rect_ = dst_rect; + if (xfer_info.src_is_typeless) { const auto internal_fmt = xfer_info.src_native_format_override ? @@ -82,8 +174,8 @@ namespace gl copy_typeless(cmd, typeless_src.get(), src); real_src = typeless_src.get(); - src_rect.x1 = static_cast(src_rect.x1 * xfer_info.src_scaling_hint); - src_rect.x2 = static_cast(src_rect.x2 * xfer_info.src_scaling_hint); + src_rect_.x = static_cast(src_rect.x * xfer_info.src_scaling_hint); + src_rect_.width = static_cast(src_rect.width * xfer_info.src_scaling_hint); } } @@ -100,8 +192,8 @@ namespace gl copy_typeless(cmd, typeless_dst.get(), dst); real_dst = typeless_dst.get(); - dst_rect.x1 = static_cast(dst_rect.x1 * xfer_info.dst_scaling_hint); - dst_rect.x2 = static_cast(dst_rect.x2 * xfer_info.dst_scaling_hint); + dst_rect_.x = static_cast(dst_rect_.x * xfer_info.dst_scaling_hint); + dst_rect_.width = static_cast(dst_rect_.width * xfer_info.dst_scaling_hint); } } @@ -109,19 +201,20 @@ namespace gl if (xfer_info.flip_horizontal) { - src_rect.flip_horizontal(); + src_rect_.flip_horizontal(); } if (xfer_info.flip_vertical) { - src_rect.flip_vertical(); + src_rect_.flip_vertical(); } - if (src_rect.width() == dst_rect.width() && - src_rect.height() == dst_rect.height() && - !src_rect.is_flipped() && !dst_rect.is_flipped()) + if (src_rect_.width == dst_rect_.width && + src_rect_.height == dst_rect_.height && + src_rect_.depth == dst_rect_.depth && + !src_rect_.is_flipped() && !dst_rect_.is_flipped()) { - copy_image(cmd, real_src, real_dst, 0, 0, position3i{ src_rect.x1, src_rect.y1, 0 }, position3i{ dst_rect.x1, dst_rect.y1, 0 }, size3i{ src_rect.width(), src_rect.height(), 1 }); + copy_image(cmd, real_src, real_dst, src_rect_.position, dst_rect_.position, src_rect_.size, mip_layers); } else { @@ -149,20 +242,59 @@ namespace gl target = gl::buffers::color; } + // Bulk layer transfers are not doable in OpenGL. We can only process one layer at a time. + ensure(mip_layers.layer_count == 1); + cmd->disable(GL_SCISSOR_TEST); save_binding_state saved; gl::fbo::attachment src_att{ blit_src, static_cast(attachment) }; - src_att = *real_src; - gl::fbo::attachment dst_att{ blit_dst, static_cast(attachment) }; - dst_att = *real_dst; - blit_src.check(); - blit_dst.check(); + auto attach_slice = [](fbo::attachment& att, const texture* tex, int level, int layer) + { + if (is_layered_target(tex)) [[ unlikely ]] + { + att.bind_layer(*tex, level, layer); + return; + } - blit_src.blit(blit_dst, src_rect, dst_rect, target, interp); + ensure(!layer); + att.bind(*tex, level); + }; + + // Pick volume or layered transfer, not both. + ensure(!mip_layers.src_layer || !src_rect_.z); + ensure(!mip_layers.dst_layer || !dst_rect_.z); + const int src_layer = mip_layers.src_layer + src_rect_.z; + const int dst_layer = mip_layers.dst_layer + dst_rect_.z; + + areai src_area = src_rect_.to_area(); + areai dst_area = dst_rect_.to_area(); + + int src_level = mip_layers.src_mip_level; + int dst_level = mip_layers.dst_mip_level; + + for (u32 remaining_levels = mip_layers.mipmap_count; remaining_levels > 0; --remaining_levels) + { + attach_slice(src_att, real_src, src_level, src_layer); + attach_slice(dst_att, real_dst, dst_level, dst_layer); + + blit_src.check(); + blit_dst.check(); + + blit_src.blit(blit_dst, src_area, dst_area, target, interp); + + if (remaining_levels > 1) + { + increment_mip_level(src_area); + increment_mip_level(dst_area); + + src_level++; + dst_level++; + } + } // Release the attachments explicitly (not doing so causes glitches, e.g Journey Menu) src_att = GL_NONE; diff --git a/rpcs3/Emu/RSX/GL/glutils/blitter.h b/rpcs3/Emu/RSX/GL/glutils/blitter.h index d56754fca..b81602f5a 100644 --- a/rpcs3/Emu/RSX/GL/glutils/blitter.h +++ b/rpcs3/Emu/RSX/GL/glutils/blitter.h @@ -34,17 +34,53 @@ namespace gl void destroy(); - void scale_image(gl::command_context& cmd, const texture* src, texture* dst, areai src_rect, areai dst_rect, bool linear_interpolation, - const rsx::typeless_xfer& xfer_info); + void scale_image( + gl::command_context& cmd, + const texture* src, + texture* dst, + const coord3i& src_rect, //<- Note, negative w/h/d is allowed for flipping + const coord3i& dst_rect, //<- Note, negative w/h/d is allowed for flipping + bool linear_interpolation, + const rsx::typeless_xfer& xfer_info, + const rsx::image_copy_subresource_layers& mip_layers = {}); - void copy_image(gl::command_context& cmd, const texture* src, const texture* dst, int src_level, int dst_level, const position3i& src_offset, const position3i& dst_offset, const size3i& size) const; + void scale_image( + gl::command_context& cmd, + const texture* src, + texture* dst, + areai src_rect, + areai dst_rect, + bool linear_interpolation, + const rsx::typeless_xfer& xfer_info, + const rsx::image_copy_subresource_layers& mip_layers = {}) + { + const auto src_region = coord3i{ src_rect.x1, src_rect.y1, 0, src_rect.x2 - src_rect.x1, src_rect.y2 - src_rect.y1, 1 }; + const auto dst_region = coord3i{ dst_rect.x1, dst_rect.y1, 0, dst_rect.x2 - dst_rect.x1, dst_rect.y2 - dst_rect.y1, 1 }; + scale_image(cmd, src, dst, src_region, dst_region, linear_interpolation, xfer_info, mip_layers); + } + + void copy_image( + gl::command_context& cmd, + const texture* src, + const texture* dst, + const position3i& src_offset, + const position3i& dst_offset, + const size3i& size, + const rsx::image_copy_subresource_layers& mip_layers = {}) const; void fast_clear_image(gl::command_context& cmd, const texture* dst, const color4f& color); void fast_clear_image(gl::command_context& cmd, const texture* dst, float depth, u8 stencil); - void copy_image(gl::command_context& cmd, const texture* src, const texture* dst, int src_level, int dst_level, const position3u& src_offset, const position3u& dst_offset, const size3u& size) const + void copy_image( + gl::command_context& cmd, + const texture* src, + const texture* dst, + const position3u& src_offset, + const position3u& dst_offset, + const size3u& size, + const rsx::image_copy_subresource_layers& mip_layers = {}) const { - copy_image(cmd, src, dst, src_level, dst_level, static_cast(src_offset), static_cast(dst_offset), static_cast(size)); + copy_image(cmd, src, dst, static_cast(src_offset), static_cast(dst_offset), static_cast(size), mip_layers); } }; diff --git a/rpcs3/Emu/RSX/GL/glutils/fbo.h b/rpcs3/Emu/RSX/GL/glutils/fbo.h index ef71bfa46..e17e8a62e 100644 --- a/rpcs3/Emu/RSX/GL/glutils/fbo.h +++ b/rpcs3/Emu/RSX/GL/glutils/fbo.h @@ -123,13 +123,32 @@ namespace gl return 0; } - void operator = (const texture& rhs) + // Attach a specific mipmap level of a flat 2D image. + void bind(const texture& rhs, GLint level = 0) { ensure(rhs.get_target() == texture::target::texture2D || rhs.get_target() == texture::target::texture2DMS); m_parent.m_resource_bindings[m_id] = rhs.id(); - DSA_CALL2(NamedFramebufferTexture, m_parent.id(), m_id, rhs.id(), 0); + DSA_CALL2(NamedFramebufferTexture, m_parent.id(), m_id, rhs.id(), level); + } + + // Attach one slice of a layered image. Cubemap faces are addressed as array layers. + void bind_layer(const texture& rhs, GLint level, GLint layer) + { + ensure(rhs.get_target() == texture::target::texture3D || + rhs.get_target() == texture::target::texture2DArray || + rhs.get_target() == texture::target::textureCUBE); + + ensure(layer < 6 || rhs.get_target() != texture::target::textureCUBE); + + m_parent.m_resource_bindings[m_id] = rhs.id(); + DSA_CALL2(NamedFramebufferTextureLayer, m_parent.id(), m_id, rhs.id(), level, layer); + } + + void operator = (const texture& rhs) + { + bind(rhs, 0); } void operator = (const GLuint rhs) diff --git a/rpcs3/Emu/RSX/NV47/HW/nv0039.cpp b/rpcs3/Emu/RSX/NV47/HW/nv0039.cpp index 111747609..d9a0ffc14 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv0039.cpp +++ b/rpcs3/Emu/RSX/NV47/HW/nv0039.cpp @@ -15,13 +15,13 @@ namespace rsx namespace nv0039 { // Transfer with stride - inline void block2d_copy_with_stride(u8* dst, const u8* src, u32 width, u32 height, s32 src_pitch, s32 dst_pitch, u8 src_stride, u8 dst_stride) + inline void block2d_copy_with_stride(u8* dst, const u8* src, u32 column_count, u32 row_count, s32 src_pitch, s32 dst_pitch, u8 src_stride, u8 dst_stride) { - for (u32 row = 0; row < height; ++row) + for (u32 row = 0; row < row_count; ++row) { auto dst_ptr = dst; auto src_ptr = src; - while (src_ptr < src + width) + for (u32 column = 0; column < column_count; ++column) { *dst_ptr = *src_ptr; @@ -44,20 +44,41 @@ namespace rsx } } + inline bool validate_buffer_notify(s32 col_count, s32 row_count, s32 in_stride, s32 out_stride) + { + if (!col_count || !row_count) + { + rsx_log.warning("NV0039_BUFFER_NOTIFY NOPed out: 2D area is zero."); + return false; + } + + if (in_stride <= 0 || in_stride > 4) + { + rsx_log.error("NV0039_BUFFER_NOTIFY NOPed out: Invalid input stride (=%d)", in_stride); + return false; + } + + if (out_stride <= 0 || out_stride > 4) + { + rsx_log.error("NV0039_BUFFER_NOTIFY NOPed out: Invalid output stride (=%d)", out_stride); + return false; + } + + return true; + } + void buffer_notify(context* ctx, u32, u32 arg) { s32 in_pitch = REGS(ctx)->nv0039_input_pitch(); s32 out_pitch = REGS(ctx)->nv0039_output_pitch(); - const u32 line_length = REGS(ctx)->nv0039_line_length(); - const u32 line_count = REGS(ctx)->nv0039_line_count(); - const u8 out_format = REGS(ctx)->nv0039_output_format(); - const u8 in_format = REGS(ctx)->nv0039_input_format(); + const u32 line_length = REGS(ctx)->nv0039_line_length(); // Number of columns per row + const u32 line_count = REGS(ctx)->nv0039_line_count(); // Number of rows to copy + const u8 out_format = REGS(ctx)->nv0039_output_format(); // Column stride in bytes. Only the first byte is actually written to. + const u8 in_format = REGS(ctx)->nv0039_input_format(); // Column stride in bytes. Only the first byte is actually read from. const u32 notify = arg; - if (!line_count || !line_length) + if (!validate_buffer_notify(line_length, line_count, in_format, out_format)) { - rsx_log.warning("NV0039_BUFFER_NOTIFY NOPed out: pitch(in=0x%x, out=0x%x), line(len=0x%x, cnt=0x%x), fmt(in=0x%x, out=0x%x), notify=0x%x", - in_pitch, out_pitch, line_length, line_count, in_format, out_format, notify); return; } @@ -70,18 +91,19 @@ namespace rsx u32 dst_offset = REGS(ctx)->nv0039_output_offset(); u32 dst_dma = REGS(ctx)->nv0039_output_location(); - const bool is_block_transfer = (in_pitch == out_pitch && out_pitch + 0u == line_length); + const auto in_width_in_bytes = line_length * in_format; + const auto out_width_in_bytes = line_length * out_format; + const bool is_block_transfer = + in_format == 1 && out_format == 1 && + in_pitch + 0u == in_width_in_bytes && + out_pitch + 0u == out_width_in_bytes; const auto read_address = get_address(src_offset, src_dma); const auto write_address = get_address(dst_offset, dst_dma); - const auto read_length = in_pitch * (line_count - 1) + line_length; - const auto write_length = out_pitch * (line_count - 1) + line_length; + const auto read_length = in_pitch * (line_count - 1) + in_width_in_bytes; + const auto write_length = out_pitch * (line_count - 1) + out_width_in_bytes; RSX(ctx)->invalidate_fragment_program(dst_dma, dst_offset, write_length); - // Can force a readback of anything the GPU wrote into this range, which is the one - // thing here that is not a memory copy and the one that can stall. - rsx::prof::scope read_barrier_prof{ rsx::prof::bucket::rsx_barrier }; - if (const auto result = RSX(ctx)->read_barrier(read_address, read_length, !is_block_transfer); result == rsx::result_zcull_intr) { @@ -93,12 +115,9 @@ namespace rsx } } - read_barrier_prof.close(); - // Deferred write_barrier on RSX side utils::deferred_op deferred([&]() { - RSX_PROF_SCOPE(rsx_barrier); RSX(ctx)->write_barrier(write_address, write_length); // res->release(0); }); @@ -118,14 +137,11 @@ namespace rsx const bool is_overlapping = dst_dma == src_dma && [&]() -> bool { const u32 src_max = src_offset + read_length; - const u32 dst_max = dst_offset + (out_pitch * (line_count - 1) + line_length); + const u32 dst_max = dst_offset + write_length; return (src_offset >= dst_offset && src_offset < dst_max) || (dst_offset >= src_offset && dst_offset < src_max); }(); - // Everything below is the transfer this method exists to perform. - RSX_PROF_SCOPE(dma_copy); - if (in_format > 1 || out_format > 1) [[ unlikely ]] { // The formats are just input channel strides. You can use this to do cool tricks like gathering channels diff --git a/rpcs3/Emu/RSX/Overlays/Trophies/overlay_trophy_list_dialog.cpp b/rpcs3/Emu/RSX/Overlays/Trophies/overlay_trophy_list_dialog.cpp index 9fc90fcbe..32d8ae172 100644 --- a/rpcs3/Emu/RSX/Overlays/Trophies/overlay_trophy_list_dialog.cpp +++ b/rpcs3/Emu/RSX/Overlays/Trophies/overlay_trophy_list_dialog.cpp @@ -2,6 +2,7 @@ #include "../overlay_manager.h" #include "overlay_trophy_list_dialog.h" #include "Emu/Cell/Modules/sceNpTrophy.h" +#include "Emu/NP/rpcn_config.h" #include "Emu/System.h" #include "Emu/VFS.h" @@ -107,7 +108,7 @@ namespace rsx m_sort_button->set_text(localized_string_id::HOME_MENU_TROPHY_SORT_GAME_DEFAULT); m_sort_button->set_image_resource(resource_config::standard_image_resource::triangle); m_sort_button->set_size(120, 30); - m_sort_button->set_pos(560, trophy_list_y + trophy_list_h + 20); + m_sort_button->set_pos(460, trophy_list_y + trophy_list_h + 20); m_sort_button->set_font("Arial", 16); m_show_hidden_trophies_button = std::make_unique(); @@ -117,6 +118,13 @@ namespace rsx m_show_hidden_trophies_button->set_pos(180, trophy_list_y + trophy_list_h + 20); m_show_hidden_trophies_button->set_font("Arial", 16); + m_sync_trophies_button = std::make_unique(); + m_sync_trophies_button->set_text(localized_string_id::HOME_MENU_TROPHY_SYNC_TROPHIES); + m_sync_trophies_button->set_image_resource(resource_config::standard_image_resource::select); + m_sync_trophies_button->set_size(120, 30); + m_sync_trophies_button->set_pos(700, trophy_list_y + trophy_list_h + 20); + m_sync_trophies_button->set_font("Arial", 16); + fade_animation.duration_sec = 0.15f; return_code = selection_code::canceled; @@ -159,6 +167,13 @@ namespace rsx m_list_dirty = true; break; } + case pad_button::select: + // Only start a new sync if not already in progress + if (m_sync_status.load() != 1) + { + sync_trophies_async(); + } + break; case pad_button::dpad_up: case pad_button::ls_up: m_list->select_previous(); @@ -209,6 +224,15 @@ namespace rsx m_show_hidden_trophies_last = m_show_hidden_trophies; } + // Update sync button label based on current sync state + switch (m_sync_status.load()) + { + case 1: m_sync_trophies_button->set_text(localized_string_id::HOME_MENU_TROPHY_SYNCING_TROPHIES); break; + case 2: m_sync_trophies_button->set_text(localized_string_id::HOME_MENU_TROPHY_SYNC_SUCCESS); break; + case 3: m_sync_trophies_button->set_text(localized_string_id::HOME_MENU_TROPHY_SYNC_FAILED); break; + default: m_sync_trophies_button->set_text(localized_string_id::HOME_MENU_TROPHY_SYNC_TROPHIES); break; + } + if (m_sort_mode_last != m_sort_mode) { localized_string_id sort_label_id; @@ -236,6 +260,7 @@ namespace rsx result.add(m_description->get_compiled()); result.add(m_sort_button->get_compiled()); result.add(m_show_hidden_trophies_button->get_compiled()); + result.add(m_sync_trophies_button->get_compiled()); fade_animation.apply(result); @@ -245,7 +270,8 @@ namespace rsx void trophy_list_dialog::show(const std::string& trop_name) { visible = false; - + + m_trop_name = trop_name; m_trophy_data = load_trophies(trop_name); ensure(m_trophy_data && m_trophy_data->trop_usr); @@ -269,6 +295,126 @@ namespace rsx } } + bool trophy_list_dialog::rpcn_configured() + { + cfg_rpcn cfg; + cfg.load(); + return !cfg.get_npid().empty() && !cfg.get_password().empty(); + } + + void trophy_list_dialog::sync_trophies_async() + { + if (!rpcn_configured()) + { + rsx_log.warning("Trophy sync requested but RPCN is not configured."); + m_sync_status = 3; // error + return; + } + + if (!m_trophy_data || !m_trophy_data->trop_usr) + { + rsx_log.error("Trophy sync requested but trophy data is not loaded."); + m_sync_status = 3; + return; + } + + m_sync_status = 1; // syncing + + const std::string trop_name = m_trop_name; + atomic_t* sync_status = &m_sync_status; + atomic_t* list_dirty = &m_list_dirty; + + SceNpCommunicationId comm_id{}; + { + if (trop_name.size() >= COMMUNICATION_ID_SIZE) + { + const auto& n = trop_name; + std::memcpy(comm_id.data, n.c_str(), COMMUNICATION_ID_COMID_COMPONENT_SIZE); + comm_id.data[COMMUNICATION_ID_COMID_COMPONENT_SIZE] = '\0'; + comm_id.num = static_cast(std::atoi(n.c_str() + COMMUNICATION_ID_COMID_COMPONENT_SIZE + 1)); + } + else + { + rsx_log.error("Trophy sync: unexpected trop_name format: %s", trop_name); + m_sync_status = 3; + return; + } + } + + const u32 trophy_count = m_trophy_data->trop_usr->GetTrophiesCount(); + std::vector> local_unlocked; + local_unlocked.reserve(trophy_count); + for (u32 i = 0; i < trophy_count; i++) + { + if (m_trophy_data->trop_usr->GetTrophyUnlockState(static_cast(i))) + { + local_unlocked.emplace_back( + static_cast(i), + static_cast(m_trophy_data->trop_usr->GetTrophyTimestamp(static_cast(i)))); + } + } + + const std::string tropusr_vfs_path = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + trop_name + "/TROPUSR.DAT"; + const std::string tropconf_vfs_path = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + trop_name + "/TROPCONF.SFM"; + + std::thread([=, local_unlocked = std::move(local_unlocked)]() mutable + { + g_cfg_rpcn.load(); + + auto rpcn = rpcn::rpcn_client::get_instance(0); + + if (auto res = rpcn->wait_for_connection(); res != rpcn::rpcn_state::failure_no_failure) + { + rsx_log.error("Trophy sync: failed to connect to RPCN: %s", rpcn::rpcn_state_to_string(res)); + *sync_status = 3; + return; + } + + if (auto res = rpcn->wait_for_authentified(); res != rpcn::rpcn_state::failure_no_failure) + { + rsx_log.error("Trophy sync: failed to authenticate with RPCN: %s", rpcn::rpcn_state_to_string(res)); + *sync_status = 3; + return; + } + + std::vector> srv_trophies = rpcn->sync_trophies(comm_id, local_unlocked); + + if (!srv_trophies.empty()) + { + auto tropusr = std::make_unique(); + if (tropusr->Load(tropusr_vfs_path, tropconf_vfs_path).success) + { + const u32 count = tropusr->GetTrophiesCount(); + bool changed = false; + for (const auto& [tid, ts] : srv_trophies) + { + if (tid >= 0 && tid < static_cast(count) && ts >= 0 && !tropusr->GetTrophyUnlockState(tid)) + { + (void)tropusr->UnlockTrophy(tid, static_cast(ts), static_cast(ts)); + changed = true; + } + } + + if (changed) + { + if (!tropusr->Save(tropusr_vfs_path)) + rsx_log.error("Trophy sync: failed to save TROPUSR after sync for %s", trop_name); + + *list_dirty = true; + } + } + else + { + rsx_log.error("Trophy sync: failed to reload TROPUSR for %s", trop_name); + *sync_status = 3; + return; + } + } + + *sync_status = 2; // success + }).detach(); + } + std::unique_ptr trophy_list_dialog::load_trophies(const std::string& trop_name) const { // Populate GameTrophiesData @@ -344,6 +490,15 @@ namespace rsx { ensure(m_trophy_data); + if (!m_trop_name.empty()) + { + const std::string tropusr_path = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + m_trop_name + "/TROPUSR.DAT"; + const std::string tropconf_path = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + m_trop_name + "/TROPCONF.SFM"; + auto fresh_usr = std::make_unique(); + if (fresh_usr->Load(tropusr_path, tropconf_path).success) + m_trophy_data->trop_usr = std::move(fresh_usr); + } + rsx_log.trace("Reloading Trophy List Overlay with %s %s", m_trophy_data->game_name, m_trophy_data->path); s32 selected_index = m_list ? m_list->get_selected_index() : 0; diff --git a/rpcs3/Emu/RSX/Overlays/Trophies/overlay_trophy_list_dialog.h b/rpcs3/Emu/RSX/Overlays/Trophies/overlay_trophy_list_dialog.h index c6ec34750..a5b3b61d7 100644 --- a/rpcs3/Emu/RSX/Overlays/Trophies/overlay_trophy_list_dialog.h +++ b/rpcs3/Emu/RSX/Overlays/Trophies/overlay_trophy_list_dialog.h @@ -4,6 +4,7 @@ #include "../overlay_list_view.hpp" #include "Loader/TROPUSR.h" +#include "Emu/NP/rpcn_client.h" class TROPUSRLoader; @@ -33,22 +34,28 @@ namespace rsx private: std::unique_ptr load_trophies(const std::string& trop_name) const; void reload(); + void sync_trophies_async(); std::unique_ptr m_dim_background; std::unique_ptr m_list; std::unique_ptr