diff --git a/.ci/build-windows-clang.sh b/.ci/build-windows-clang.sh index 06482dd63a..4f07688ee5 100644 --- a/.ci/build-windows-clang.sh +++ b/.ci/build-windows-clang.sh @@ -2,6 +2,9 @@ git config --global --add safe.directory '*' +CPU_ARCH="${1:-x86_64}" +MSYS2="${2:-clang64}" + # Pull all the submodules except some # Note: Tried to use git submodule status, but it takes over 20 seconds # shellcheck disable=SC2046 @@ -23,7 +26,7 @@ else fi cmake .. \ - -DCMAKE_PREFIX_PATH=/clang64 \ + -DCMAKE_PREFIX_PATH=/"${MSYS2}" \ -DCMAKE_INSTALL_PREFIX=/usr \ -DUSE_NATIVE_INSTRUCTIONS=OFF \ -DUSE_PRECOMPILED_HEADERS=OFF \ @@ -44,8 +47,8 @@ cmake .. \ -DUSE_DISCORD_RPC=ON \ -DOpenGL_GL_PREFERENCE=LEGACY \ -DWITH_LLVM=ON \ - -DLLVM_DIR=/clang64/lib/cmake/llvm \ - -DVulkan_LIBRARY=/clang64/lib/libvulkan-1.dll.a \ + -DLLVM_DIR=/"${MSYS2}"/lib/cmake/llvm \ + -DVulkan_LIBRARY=/"${MSYS2}"/lib/libvulkan-1.dll.a \ -DSTATIC_LINK_LLVM=ON \ -DBUILD_RPCS3_TESTS=OFF \ -DRUN_RPCS3_TESTS=OFF \ @@ -57,5 +60,5 @@ cd .. # If it compiled succesfully let's deploy. if [ "$build_status" -eq 0 ]; then - echo "Build succeeded" + .ci/deploy-windows-clang.sh "${CPU_ARCH}" "${MSYS2}" fi diff --git a/.ci/deploy-windows-clang.sh b/.ci/deploy-windows-clang.sh index 0bf731e7c8..32c6f24808 100644 --- a/.ci/deploy-windows-clang.sh +++ b/.ci/deploy-windows-clang.sh @@ -7,12 +7,13 @@ cd build || exit 1 CPU_ARCH="${1:-x86_64}" +MSYS2="${2:-clang64}" echo "Deploying rpcs3 windows clang $CPU_ARCH" # BUILD_blablabla is CI specific, so we wrap it for portability ARTIFACT_DIR=$(cygpath -u "$BUILD_ARTIFACTSTAGINGDIRECTORY") -MSYS2_CLANG_BIN=$(cygpath -w /clang64/bin) +MSYS2_CLANG_BIN=$(cygpath -w /"${MSYS2}"/bin) MSYS2_USR_BIN=$(cygpath -w /usr/bin) echo "Installing dependencies of: ./bin/rpcs3.exe (MSYS2 dir is '$MSYS2_CLANG_BIN', usr dir is '$MSYS2_USR_BIN')" diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml index 108ef432f2..4e078a3732 100644 --- a/.github/workflows/rpcs3.yml +++ b/.github/workflows/rpcs3.yml @@ -333,16 +333,24 @@ jobs: Windows_Build_MSYS2: # Only run push event on master branch of main repo, but run all PRs if: github.event_name != 'push' || (github.repository == 'RPCS3/rpcs3' && github.ref_name == 'master') - runs-on: windows-2025 strategy: + fail-fast: false matrix: include: - msys2: clang64 compiler: clang - arch: win64 + arch: x86_64 + os: windows-2025 + name: X64 + - msys2: clangarm64 + compiler: clang + arch: aarch64 + os: windows-11-arm + name: ARM64 env: CCACHE_DIR: 'C:\ccache' - name: RPCS3 Windows Clang (MSYS2) + name: RPCS3 Windows Clang ${{ matrix.arch }} + runs-on: ${{ matrix.os }} steps: - name: Checkout repository uses: actions/checkout@main @@ -356,23 +364,25 @@ jobs: update: true cache: true install: | - mingw-w64-clang-x86_64-clang - mingw-w64-clang-x86_64-ccache - mingw-w64-clang-x86_64-cmake - mingw-w64-clang-x86_64-lld - mingw-w64-clang-x86_64-ninja - mingw-w64-clang-x86_64-llvm - mingw-w64-clang-x86_64-ffmpeg - mingw-w64-clang-x86_64-opencv - mingw-w64-clang-x86_64-glew - mingw-w64-clang-x86_64-vulkan - mingw-w64-clang-x86_64-vulkan-headers - mingw-w64-clang-x86_64-vulkan-loader - mingw-w64-clang-x86_64-gtest - mingw-w64-clang-x86_64-qt6-base - mingw-w64-clang-x86_64-qt6-declarative - mingw-w64-clang-x86_64-qt6-multimedia - mingw-w64-clang-x86_64-qt6-svg + mingw-w64-clang-${{ matrix.arch }}-clang + mingw-w64-clang-${{ matrix.arch }}-ccache + mingw-w64-clang-${{ matrix.arch }}-cmake + mingw-w64-clang-${{ matrix.arch }}-lld + mingw-w64-clang-${{ matrix.arch }}-ninja + mingw-w64-clang-${{ matrix.arch }}-llvm + mingw-w64-clang-${{ matrix.arch }}-ffmpeg + mingw-w64-clang-${{ matrix.arch }}-opencv + mingw-w64-clang-${{ matrix.arch }}-glew + mingw-w64-clang-${{ matrix.arch }}-vulkan + mingw-w64-clang-${{ matrix.arch }}-vulkan-headers + mingw-w64-clang-${{ matrix.arch }}-vulkan-loader + mingw-w64-clang-${{ matrix.arch }}-gtest + mingw-w64-clang-${{ matrix.arch }}-qt6-base + mingw-w64-clang-${{ matrix.arch }}-qt6-declarative + mingw-w64-clang-${{ matrix.arch }}-qt6-multimedia + mingw-w64-clang-${{ matrix.arch }}-qt6-svg + mingw-w64-clang-${{ matrix.arch }}-qt6-tools + mingw-w64-clang-${{ matrix.arch }}-qt6-translations base-devel curl git @@ -383,17 +393,16 @@ jobs: id: restore-build-ccache with: path: ${{ env.CCACHE_DIR }} - key: ${{ runner.os }}-ccache-${{ matrix.compiler }}-${{ runner.arch }}-${{ github.run_id }} - restore-keys: ${{ runner.os }}-ccache-${{ matrix.compiler }}-${{ runner.arch }}- + key: ${{ runner.os }}-ccache-${{ matrix.compiler }}-${{ matrix.arch }}-${{ github.run_id }} + restore-keys: ${{ runner.os }}-ccache-${{ matrix.compiler }}-${{ matrix.arch }}- - name: Build RPCS3 shell: msys2 {0} run: | export CCACHE_DIR=$(cygpath -u "$CCACHE_DIR") echo "CCACHE_DIR=$CCACHE_DIR" - .ci/build-windows-clang.sh .ci/setup-windows-ci-vars.sh ${{ matrix.arch }} ${{ matrix.compiler }} - .ci/deploy-windows-${{ matrix.compiler }}.sh + .ci/build-windows-clang.sh ${{ matrix.arch }} ${{ matrix.msys2 }} - name: Save build Ccache if: github.ref == 'refs/heads/master' @@ -405,7 +414,7 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@main with: - name: RPCS3 for Windows (${{ runner.arch }}, ${{ matrix.compiler }}) + name: RPCS3 for Windows (${{ matrix.name }}, clang) path: ${{ env.BUILD_ARTIFACTSTAGINGDIRECTORY }} compression-level: 0 if-no-files-found: error diff --git a/3rdparty/FAudio b/3rdparty/FAudio index 4ea8afea6b..633bdb772a 160000 --- a/3rdparty/FAudio +++ b/3rdparty/FAudio @@ -1 +1 @@ -Subproject commit 4ea8afea6ba857c24e40877f487d000d559b196d +Subproject commit 633bdb772a593104414b4b103ec752567d57c3c1 diff --git a/3rdparty/ffmpeg b/3rdparty/ffmpeg index ec6367d3ba..ce81114ed9 160000 --- a/3rdparty/ffmpeg +++ b/3rdparty/ffmpeg @@ -1 +1 @@ -Subproject commit ec6367d3ba9d0d57b9d22d4b87da8144acaf428f +Subproject commit ce81114ed99e5510f6cd983f59a1eac9f33bb73c diff --git a/3rdparty/libsdl-org/SDL b/3rdparty/libsdl-org/SDL index f5e5f65889..a962f40bbb 160000 --- a/3rdparty/libsdl-org/SDL +++ b/3rdparty/libsdl-org/SDL @@ -1 +1 @@ -Subproject commit f5e5f6588921eed3d7d048ce43d9eb1ff0da0ffc +Subproject commit a962f40bbba175e9716557a25d5d7965f134a3d3 diff --git a/3rdparty/libsdl-org/SDL.vcxproj b/3rdparty/libsdl-org/SDL.vcxproj index f0b38ca09f..fd2bcf2f03 100644 --- a/3rdparty/libsdl-org/SDL.vcxproj +++ b/3rdparty/libsdl-org/SDL.vcxproj @@ -23,6 +23,7 @@ + @@ -102,6 +103,7 @@ + @@ -130,6 +132,8 @@ + + @@ -140,7 +144,11 @@ + + + + @@ -156,6 +164,7 @@ + @@ -175,7 +184,6 @@ - @@ -184,20 +192,35 @@ + + + + + + + + + + + + + + + @@ -241,6 +264,7 @@ + @@ -256,13 +280,14 @@ + + - @@ -303,7 +328,6 @@ - @@ -333,7 +357,6 @@ - @@ -393,7 +416,6 @@ - @@ -464,6 +486,7 @@ + @@ -471,12 +494,11 @@ - + - diff --git a/3rdparty/libsdl-org/SDL.vcxproj.filters b/3rdparty/libsdl-org/SDL.vcxproj.filters index 5839899c0d..8b7f293ef3 100644 --- a/3rdparty/libsdl-org/SDL.vcxproj.filters +++ b/3rdparty/libsdl-org/SDL.vcxproj.filters @@ -214,6 +214,9 @@ {000028b2ea36d7190d13777a4dc70000} + + {695ffc61-5497-4227-b415-15e9bdd5b6bf} + @@ -699,9 +702,6 @@ video\yuv2rgb - - video\windows - video\windows @@ -831,9 +831,6 @@ render\software - - render\software - render\software @@ -911,12 +908,6 @@ - - - - - - render\vulkan @@ -950,6 +941,60 @@ + + video\yuv2rgb + + + video\yuv2rgb + + + video\yuv2rgb + + + video\yuv2rgb + + + video\yuv2rgb + + + video\yuv2rgb + + + video + + + video + + + video + + + misc + + + haptic\hidapi + + + haptic\hidapi + + + core + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + + + API Headers + @@ -1037,9 +1082,6 @@ core - - core\windows - core\windows @@ -1166,9 +1208,6 @@ joystick\dummy - - joystick\gdk - joystick\hidapi @@ -1328,9 +1367,6 @@ video\dummy - - video\windows - video\windows @@ -1343,9 +1379,6 @@ video\windows - - video\windows - video\windows @@ -1508,9 +1541,6 @@ render\software - - render\software - render\software @@ -1535,9 +1565,6 @@ - - - render\vulkan @@ -1579,6 +1606,66 @@ + + video\windows + + + video\yuv2rgb + + + video\yuv2rgb + + + video\yuv2rgb + + + video + + + misc + + + haptic\hidapi + + + haptic\hidapi + + + core\windows + + + core\windows + + + joystick\gdk + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + + + joystick\hidapi + diff --git a/Utilities/File.h b/Utilities/File.h index 90453f16f0..7e6356da7b 100644 --- a/Utilities/File.h +++ b/Utilities/File.h @@ -155,7 +155,7 @@ namespace fs // Virtual device struct device_base { - const std::string fs_prefix; + std::string fs_prefix; device_base(); virtual ~device_base(); @@ -257,6 +257,8 @@ namespace fs // Open file with specified mode explicit file(const std::string& path, bs_t mode = ::fs::read); + file(std::unique_ptr&& ptr) : m_file(std::move(ptr)) {} + static file from_native_handle(native_handle handle); // Open memory for read diff --git a/buildfiles/msvc/rpcs3_default.props b/buildfiles/msvc/rpcs3_default.props index d8df69d503..3cd3d25a94 100644 --- a/buildfiles/msvc/rpcs3_default.props +++ b/buildfiles/msvc/rpcs3_default.props @@ -7,7 +7,7 @@ $(SolutionDir)build\lib\$(Configuration)-$(Platform)\ $(SolutionDir)build\lib\$(Configuration)-$(Platform)\;$(UniversalCRT_LibraryPath_x64);$(LibraryPath) $(SolutionDir)build\tmp\$(ProjectName)-$(Configuration)-$(Platform)\ - $(SolutionDir)packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.7\build\native\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.targets + $(SolutionDir)packages\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.1.8.1.8\build\native\Microsoft.googletest.v140.windesktop.msvcstl.static.rt-static.targets true diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 8775144360..0c9b6d7f1e 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -126,6 +126,7 @@ target_sources(rpcs3_emu PRIVATE ../Loader/PSF.cpp ../Loader/PUP.cpp ../Loader/TAR.cpp + ../Loader/ISO.cpp ../Loader/TROPUSR.cpp ../Loader/TRP.cpp ) diff --git a/rpcs3/Emu/Cell/Modules/cellVdec.cpp b/rpcs3/Emu/Cell/Modules/cellVdec.cpp index 84bb74ad28..df638670ba 100644 --- a/rpcs3/Emu/Cell/Modules/cellVdec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellVdec.cpp @@ -211,6 +211,7 @@ struct vdec_context final lf_queue in_cmd; AVRational log_time_base{}; // Used to reduce log spam + AVRational log_framerate{}; // Used to reduce log spam vdec_context(s32 type, u32 /*profile*/, u32 addr, u32 size, vm::ptr func, u32 arg) : type(type) @@ -231,6 +232,7 @@ struct vdec_context final codec = avcodec_find_decoder(AV_CODEC_ID_H264); break; } + case CELL_VDEC_CODEC_TYPE_MPEG4: case CELL_VDEC_CODEC_TYPE_DIVX: { codec = avcodec_find_decoder(AV_CODEC_ID_MPEG4); @@ -292,6 +294,19 @@ struct vdec_context final sws_freeContext(sws); } + static u32 freq_to_framerate_code(f64 freq) + { + if (std::abs(freq - 23.976) < 0.002) return CELL_VDEC_FRC_24000DIV1001; + if (std::abs(freq - 24.000) < 0.001) return CELL_VDEC_FRC_24; + if (std::abs(freq - 25.000) < 0.001) return CELL_VDEC_FRC_25; + if (std::abs(freq - 29.970) < 0.002) return CELL_VDEC_FRC_30000DIV1001; + if (std::abs(freq - 30.000) < 0.001) return CELL_VDEC_FRC_30; + if (std::abs(freq - 50.000) < 0.001) return CELL_VDEC_FRC_50; + if (std::abs(freq - 59.940) < 0.002) return CELL_VDEC_FRC_60000DIV1001; + if (std::abs(freq - 60.000) < 0.001) return CELL_VDEC_FRC_60; + return 0; + } + void exec(ppu_thread& ppu, u32 vid) { perf_meter<"VDEC"_u32> perf0; @@ -341,6 +356,7 @@ struct vdec_context final out_queue.clear(); // Flush image queue log_time_base = {}; + log_framerate = {}; frc_set = 0; // TODO: ??? next_pts = 0; @@ -471,10 +487,10 @@ struct vdec_context final frame.userdata = au_usrd; frame.attr = attr; + u64 amend = 0; + if (frc_set) { - u64 amend = 0; - switch (frc_set) { case CELL_VDEC_FRC_24000DIV1001: amend = 1001 * 90000 / 24000; break; @@ -491,62 +507,45 @@ struct vdec_context final } } - next_pts += amend; - next_dts += amend; frame.frc = frc_set; } - else if (ctx->time_base.num == 0) + else if (ctx->time_base.den && ctx->time_base.num) { - if (log_time_base.den != ctx->time_base.den || log_time_base.num != ctx->time_base.num) + const auto freq = 1. * ctx->time_base.den / ctx->time_base.num / ticks_per_frame; + + frame.frc = freq_to_framerate_code(freq); + if (frame.frc) { - cellVdec.error("time_base.num is 0 (handle=0x%x, seq_id=%d, cmd_id=%d, %d/%d, tpf=%d framerate=%d/%d)", handle, cmd->seq_id, cmd->id, ctx->time_base.num, ctx->time_base.den, ticks_per_frame, ctx->framerate.num, ctx->framerate.den); + amend = u64{90000} * ctx->time_base.num * ticks_per_frame / ctx->time_base.den; + } + } + else if (ctx->framerate.den && ctx->framerate.num) + { + const auto freq = ctx->framerate.num / static_cast(ctx->framerate.den); + + frame.frc = freq_to_framerate_code(freq); + if (frame.frc) + { + amend = u64{90000} * ctx->framerate.den / ctx->framerate.num; + } + } + + if (amend == 0 || frame.frc == 0) + { + if (log_time_base.den != ctx->time_base.den || log_time_base.num != ctx->time_base.num || log_framerate.den != ctx->framerate.den || log_framerate.num != ctx->framerate.num) + { + cellVdec.error("Invalid frequency (handle=0x%x, seq_id=%d, cmd_id=%d, timebase=%d/%d, tpf=%d framerate=%d/%d)", handle, cmd->seq_id, cmd->id, ctx->time_base.num, ctx->time_base.den, ticks_per_frame, ctx->framerate.num, ctx->framerate.den); log_time_base = ctx->time_base; + log_framerate = ctx->framerate; } // Hack - const u64 amend = u64{90000} / 30; + amend = u64{90000} / 30; frame.frc = CELL_VDEC_FRC_30; - next_pts += amend; - next_dts += amend; } - else - { - u64 amend = u64{90000} * ctx->time_base.num * ticks_per_frame / ctx->time_base.den; - const auto freq = 1. * ctx->time_base.den / ctx->time_base.num / ticks_per_frame; - if (std::abs(freq - 23.976) < 0.002) - frame.frc = CELL_VDEC_FRC_24000DIV1001; - else if (std::abs(freq - 24.000) < 0.001) - frame.frc = CELL_VDEC_FRC_24; - else if (std::abs(freq - 25.000) < 0.001) - frame.frc = CELL_VDEC_FRC_25; - else if (std::abs(freq - 29.970) < 0.002) - frame.frc = CELL_VDEC_FRC_30000DIV1001; - else if (std::abs(freq - 30.000) < 0.001) - frame.frc = CELL_VDEC_FRC_30; - else if (std::abs(freq - 50.000) < 0.001) - frame.frc = CELL_VDEC_FRC_50; - else if (std::abs(freq - 59.940) < 0.002) - frame.frc = CELL_VDEC_FRC_60000DIV1001; - else if (std::abs(freq - 60.000) < 0.001) - frame.frc = CELL_VDEC_FRC_60; - else - { - if (log_time_base.den != ctx->time_base.den || log_time_base.num != ctx->time_base.num) - { - // 1/1000 usually means that the time stamps are written in 1ms units and that the frame rate may vary. - cellVdec.error("Unsupported time_base (handle=0x%x, seq_id=%d, cmd_id=%d, %d/%d, tpf=%d framerate=%d/%d)", handle, cmd->seq_id, cmd->id, ctx->time_base.num, ctx->time_base.den, ticks_per_frame, ctx->framerate.num, ctx->framerate.den); - log_time_base = ctx->time_base; - } - - // Hack - amend = u64{90000} / 30; - frame.frc = CELL_VDEC_FRC_30; - } - - next_pts += amend; - next_dts += amend; - } + next_pts += amend; + next_dts += amend; cellVdec.trace("Got picture (handle=0x%x, seq_id=%d, cmd_id=%d, pts=0x%llx[0x%llx], dts=0x%llx[0x%llx])", handle, cmd->seq_id, cmd->id, frame.pts, frame->pts, frame.dts, frame->pkt_dts); @@ -680,7 +679,15 @@ static error_code vdecQueryAttr(s32 type, u32 profile, u32 spec_addr /* may be 0 { cellVdec.warning("cellVdecQueryAttr: AVC (profile=%d)", profile); - //const vm::ptr sinfo = vm::cast(spec_addr); + const vm::ptr sinfo = vm::cast(spec_addr); + + if (sinfo) + { + if (sinfo->thisSize != sizeof(CellVdecAvcSpecificInfo)) + { + return { CELL_VDEC_ERROR_ARG, "Invalid AVC specific info size %d", sinfo->thisSize }; + } + } // TODO: sinfo @@ -699,7 +706,7 @@ static error_code vdecQueryAttr(s32 type, u32 profile, u32 spec_addr /* may be 0 case CELL_VDEC_AVC_LEVEL_4P0: memSize = new_sdk ? 0x33A5FFD : 0x36A527D; break; case CELL_VDEC_AVC_LEVEL_4P1: memSize = new_sdk ? 0x33A5FFD : 0x36A527D; break; case CELL_VDEC_AVC_LEVEL_4P2: memSize = new_sdk ? 0x33A5FFD : 0x36A527D; break; - default: return CELL_VDEC_ERROR_ARG; + default: return { CELL_VDEC_ERROR_ARG, "Invalid AVC profile level %d", profile }; } decoderVerLower = 0x11300; @@ -715,7 +722,7 @@ static error_code vdecQueryAttr(s32 type, u32 profile, u32 spec_addr /* may be 0 { if (sinfo->thisSize != sizeof(CellVdecMpeg2SpecificInfo)) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "Invalid MPEG2 specific info size %d", sinfo->thisSize }; } } @@ -730,7 +737,7 @@ static error_code vdecQueryAttr(s32 type, u32 profile, u32 spec_addr /* may be 0 { if (maxDecW > 352 || maxDecH > 288) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "Invalid max decoded frame size %dx%d for profile %d", maxDecH, maxDecW, profile }; } memSize = new_sdk ? 0x11290B : 0x2A610B; @@ -740,7 +747,7 @@ static error_code vdecQueryAttr(s32 type, u32 profile, u32 spec_addr /* may be 0 { if (maxDecW > 720 || maxDecH > 576) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "Invalid max decoded frame size %dx%d for profile %d", maxDecH, maxDecW, profile }; } memSize = new_sdk ? 0x2DFB8B : 0x47110B; @@ -750,7 +757,7 @@ static error_code vdecQueryAttr(s32 type, u32 profile, u32 spec_addr /* may be 0 { if (maxDecW > 1440 || maxDecH > 1152) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "Invalid max decoded frame size %dx%d for profile %d", maxDecH, maxDecW, profile }; } memSize = new_sdk ? 0xA0270B : 0xB8F90B; @@ -760,18 +767,19 @@ static error_code vdecQueryAttr(s32 type, u32 profile, u32 spec_addr /* may be 0 { if (maxDecW > 1920 || maxDecH > 1152) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "Invalid max decoded frame size %dx%d for profile %d", maxDecH, maxDecW, profile }; } memSize = new_sdk ? 0xD2F40B : 0xEB990B; break; } - default: return CELL_VDEC_ERROR_ARG; + default: return { CELL_VDEC_ERROR_ARG, "Invalid MPEG2 profile %d", profile }; } decoderVerLower = 0x1030000; break; } + case CELL_VDEC_CODEC_TYPE_MPEG4: case CELL_VDEC_CODEC_TYPE_DIVX: { cellVdec.warning("cellVdecQueryAttr: DivX (profile=%d)", profile); @@ -780,9 +788,9 @@ static error_code vdecQueryAttr(s32 type, u32 profile, u32 spec_addr /* may be 0 if (sinfo) { - if (sinfo->thisSize != sizeof(CellVdecDivxSpecificInfo2)) + if (sinfo->thisSize != sizeof(CellVdecDivxSpecificInfo) && sinfo->thisSize != sizeof(CellVdecDivxSpecificInfo2)) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "Invalid DIVX specific info size %d", sinfo->thisSize }; } } @@ -790,7 +798,7 @@ static error_code vdecQueryAttr(s32 type, u32 profile, u32 spec_addr /* may be 0 //const u32 maxDecH = sinfo ? +sinfo->maxDecodedFrameHeight : 0; //const u32 maxDecW = sinfo ? +sinfo->maxDecodedFrameWidth : 0; - u32 nrOfBuf = sinfo ? +sinfo->numberOfDecodedFrameBuffer : 0; + u32 nrOfBuf = sinfo && sinfo->thisSize == sizeof(CellVdecDivxSpecificInfo2) ? +sinfo->numberOfDecodedFrameBuffer : 0; if (nrOfBuf == 0) { @@ -800,12 +808,12 @@ static error_code vdecQueryAttr(s32 type, u32 profile, u32 spec_addr /* may be 0 { if (profile != CELL_VDEC_DIVX_QMOBILE && profile != CELL_VDEC_DIVX_MOBILE) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "Invalid number of decoded frame buffers %d for DIVX profile %d", nrOfBuf, profile }; } } else if (nrOfBuf != 4 && nrOfBuf != 3) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "Invalid number of decoded frame buffers %d for DIVX", nrOfBuf }; } // TODO: change memSize based on buffercount. @@ -814,16 +822,17 @@ static error_code vdecQueryAttr(s32 type, u32 profile, u32 spec_addr /* may be 0 { case CELL_VDEC_DIVX_QMOBILE : memSize = new_sdk ? 0x11B720 : 0x1DEF30; break; case CELL_VDEC_DIVX_MOBILE : memSize = new_sdk ? 0x19A740 : 0x26DED0; break; + case CELL_VDEC_MPEG4_SIMPLE_PROFILE: // just a guess based on the profile used by singstar before and after update case CELL_VDEC_DIVX_HOME_THEATER: memSize = new_sdk ? 0x386A60 : 0x498060; break; case CELL_VDEC_DIVX_HD_720 : memSize = new_sdk ? 0x692070 : 0x805690; break; case CELL_VDEC_DIVX_HD_1080 : memSize = new_sdk ? 0xD78100 : 0xFC9870; break; - default: return CELL_VDEC_ERROR_ARG; + default: return { CELL_VDEC_ERROR_ARG, "Invalid DIVX profile %d", profile }; } decoderVerLower = 0x30806; break; } - default: return CELL_VDEC_ERROR_ARG; + default: return { CELL_VDEC_ERROR_ARG, "Invalid codec type %d", type }; } attr->decoderVerLower = decoderVerLower; @@ -839,7 +848,7 @@ error_code cellVdecQueryAttr(vm::cptr type, vm::ptr if (!type || !attr) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "type=%d, attr=%d", !!type, !!attr }; } return vdecQueryAttr(type->codecType, type->profileLevel, 0, attr.get_ptr()); @@ -851,7 +860,7 @@ error_code cellVdecQueryAttrEx(vm::cptr type, vm::ptrcodecType, type->profileLevel, type->codecSpecificInfo_addr, attr.get_ptr()); @@ -862,13 +871,14 @@ static error_code vdecOpen(ppu_thread& ppu, T type, U res, vm::cptr { if (!type || !res || !cb || !handle || !cb->cbFunc) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "type=%d, res=%d, cb=%d, handle=%d, cbFunc=%d", !!type, !!res, !!cb, !!handle, cb && cb->cbFunc }; } if (!res->memAddr || res->ppuThreadPriority + 0u >= 3072 || res->spuThreadPriority + 0u >= 256 || res->ppuThreadStackSize < 4096 || type->codecType + 0u >= 0xe) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "memAddr=%d, ppuThreadPriority=%d, spuThreadPriority=%d, ppuThreadStackSize=%d, codecType=%d", + res->memAddr, res->ppuThreadPriority, res->spuThreadPriority, res->ppuThreadStackSize, type->codecType }; } u32 spec_addr = 0; @@ -878,11 +888,16 @@ static error_code vdecOpen(ppu_thread& ppu, T type, U res, vm::cptr spec_addr = type->codecSpecificInfo_addr; } - if (CellVdecAttr attr{}; - vdecQueryAttr(type->codecType, type->profileLevel, spec_addr, &attr) != CELL_OK || - attr.memSize > res->memSize) + CellVdecAttr attr{}; + const error_code err = vdecQueryAttr(type->codecType, type->profileLevel, spec_addr, &attr); + if (err != CELL_OK) { - return CELL_VDEC_ERROR_ARG; + return err; + } + + if (attr.memSize > res->memSize) + { + return { CELL_VDEC_ERROR_ARG, "attr.memSize=%d, res->memSize=%d", attr.memSize, res->memSize }; } // Create decoder context @@ -953,7 +968,7 @@ error_code cellVdecClose(ppu_thread& ppu, u32 handle) if (!vdec) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "vdec is nullptr" }; } { @@ -991,7 +1006,7 @@ error_code cellVdecClose(ppu_thread& ppu, u32 handle) if (!idm::remove_verify(handle, std::move(vdec))) { // Other thread removed it beforehead - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "remove_verify failed" }; } return CELL_OK; @@ -1007,7 +1022,7 @@ error_code cellVdecStartSeq(ppu_thread& ppu, u32 handle) if (!vdec) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "vdec is nullptr" }; } sequence_state old_state{}; @@ -1059,7 +1074,7 @@ error_code cellVdecEndSeq(ppu_thread& ppu, u32 handle) if (!vdec) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "vdec is nullptr" }; } { @@ -1196,7 +1211,7 @@ error_code cellVdecGetPictureExt(ppu_thread& ppu, u32 handle, vm::cptrformatType > 4 || (format->formatType <= CELL_VDEC_PICFMT_RGBA32_ILV && format->colorMatrixType > CELL_VDEC_COLOR_MATRIX_TYPE_BT709)) { - return CELL_VDEC_ERROR_ARG; + return {CELL_VDEC_ERROR_ARG, "formatType=%d, colorMatrixType=%d", +format->formatType, +format->colorMatrixType}; } if (arg4 && arg4 != 8 && arg4 != 0xc) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "arg4=0x%x", arg4 }; } if (arg4 || format->unk0 || format->unk1) @@ -1335,7 +1350,7 @@ error_code cellVdecGetPicture(ppu_thread& ppu, u32 handle, vm::cptr format2; @@ -1358,7 +1373,7 @@ error_code cellVdecGetPicItem(ppu_thread& ppu, u32 handle, vm::pptrreserved[0] = 0; avc->reserved[1] = 0; } - else if (vdec->type == CELL_VDEC_CODEC_TYPE_DIVX) + else if (vdec->type == CELL_VDEC_CODEC_TYPE_MPEG4 || vdec->type == CELL_VDEC_CODEC_TYPE_DIVX) { const vm::ptr dvx = picinfo_addr; @@ -1601,7 +1616,7 @@ error_code cellVdecSetFrameRate(u32 handle, CellVdecFrameRate frameRateCode) // 0x80 seems like a common prefix if (!vdec || (frameRateCode & 0xf8) != 0x80) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "vdec=%d, frameRateCode=0x%x", !!vdec, +frameRateCode }; } std::lock_guard lock{vdec->mutex}; @@ -1625,7 +1640,7 @@ error_code cellVdecOpenExt(ppu_thread& ppu, vm::cptr type, vm::cpt if (!res) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "res is nullptr" }; } vm::var tmp = vm::make_var({}); @@ -1678,7 +1693,7 @@ error_code cellVdecSetPts(u32 handle, vm::ptr unk) if (!vdec || !unk) { - return CELL_VDEC_ERROR_ARG; + return { CELL_VDEC_ERROR_ARG, "vdec=%d, unk=%d", !!vdec, !!unk }; } std::lock_guard lock{vdec->mutex}; diff --git a/rpcs3/Emu/Cell/Modules/cellVdec.h b/rpcs3/Emu/Cell/Modules/cellVdec.h index 9f30b7d1b0..8cf9a0b1de 100644 --- a/rpcs3/Emu/Cell/Modules/cellVdec.h +++ b/rpcs3/Emu/Cell/Modules/cellVdec.h @@ -16,6 +16,7 @@ enum CellVdecCodecType : s32 { CELL_VDEC_CODEC_TYPE_MPEG2 = 0, CELL_VDEC_CODEC_TYPE_AVC = 1, + CELL_VDEC_CODEC_TYPE_MPEG4 = 2, CELL_VDEC_CODEC_TYPE_DIVX = 5, CELL_VDEC_CODEC_TYPE_MAX }; @@ -388,6 +389,7 @@ struct CellVdecAvcInfo // DIVX Profile enum DIVX_level : u8 { + CELL_VDEC_MPEG4_SIMPLE_PROFILE = 4, CELL_VDEC_DIVX_QMOBILE = 10, CELL_VDEC_DIVX_MOBILE = 11, CELL_VDEC_DIVX_HOME_THEATER = 12, diff --git a/rpcs3/Emu/Cell/Modules/sceNp.cpp b/rpcs3/Emu/Cell/Modules/sceNp.cpp index beb6c2da91..4b4c3cfd01 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNp.cpp @@ -745,7 +745,7 @@ error_code sceNpDrmIsAvailable(ppu_thread& ppu, vm::cptr k_licensee_addr, vm lv2_obj::sleep(ppu); const auto ret = npDrmIsAvailable(k_licensee_addr, drm_path); - lv2_sleep(25'000, &ppu); + lv2_sleep(5'000, &ppu); return ret; } diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index 92fc21b747..961d0d6ad3 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -1035,7 +1035,7 @@ lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mo error_code sys_fs_open(ppu_thread& ppu, vm::cptr path, s32 flags, vm::ptr fd, s32 mode, vm::cptr arg, u64 size) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_open(path=%s, flags=%#o, fd=*0x%x, mode=%#o, arg=*0x%x, size=0x%llx)", path, flags, fd, mode, arg, size); @@ -1083,7 +1083,7 @@ error_code sys_fs_open(ppu_thread& ppu, vm::cptr path, s32 flags, vm::ptr< error_code sys_fs_read(ppu_thread& ppu, u32 fd, vm::ptr buf, u64 nbytes, vm::ptr nread) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.trace("sys_fs_read(fd=%d, buf=*0x%x, nbytes=0x%llx, nread=*0x%x)", fd, buf, nbytes, nread); @@ -1156,7 +1156,7 @@ error_code sys_fs_read(ppu_thread& ppu, u32 fd, vm::ptr buf, u64 nbytes, v error_code sys_fs_write(ppu_thread& ppu, u32 fd, vm::cptr buf, u64 nbytes, vm::ptr nwrite) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.trace("sys_fs_write(fd=%d, buf=*0x%x, nbytes=0x%llx, nwrite=*0x%x)", fd, buf, nbytes, nwrite); @@ -1238,7 +1238,7 @@ error_code sys_fs_write(ppu_thread& ppu, u32 fd, vm::cptr buf, u64 nbytes, error_code sys_fs_close(ppu_thread& ppu, u32 fd) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); const auto file = idm::get_unlocked(fd); @@ -1314,7 +1314,7 @@ error_code sys_fs_close(ppu_thread& ppu, u32 fd) error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr path, vm::ptr fd) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_opendir(path=%s, fd=*0x%x)", path, fd); @@ -1433,7 +1433,7 @@ error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr path, vm::ptr fd) error_code sys_fs_readdir(ppu_thread& ppu, u32 fd, vm::ptr dir, vm::ptr nread) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_readdir(fd=%d, dir=*0x%x, nread=*0x%x)", fd, dir, nread); @@ -1490,7 +1490,7 @@ error_code sys_fs_readdir(ppu_thread& ppu, u32 fd, vm::ptr dir, vm error_code sys_fs_closedir(ppu_thread& ppu, u32 fd) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_closedir(fd=%d)", fd); @@ -1504,7 +1504,6 @@ error_code sys_fs_closedir(ppu_thread& ppu, u32 fd) error_code sys_fs_stat(ppu_thread& ppu, vm::cptr path, vm::ptr sb) { - ppu.state += cpu_flag::wait; lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_stat(path=%s, sb=*0x%x)", path, sb); @@ -1618,7 +1617,7 @@ error_code sys_fs_stat(ppu_thread& ppu, vm::cptr path, vm::ptr error_code sys_fs_fstat(ppu_thread& ppu, u32 fd, vm::ptr sb) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_fstat(fd=%d, sb=*0x%x)", fd, sb); @@ -1673,7 +1672,7 @@ error_code sys_fs_link(ppu_thread&, vm::cptr from, vm::cptr to) error_code sys_fs_mkdir(ppu_thread& ppu, vm::cptr path, s32 mode) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_mkdir(path=%s, mode=%#o)", path, mode); @@ -1734,7 +1733,7 @@ error_code sys_fs_mkdir(ppu_thread& ppu, vm::cptr path, s32 mode) error_code sys_fs_rename(ppu_thread& ppu, vm::cptr from, vm::cptr to) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_rename(from=%s, to=%s)", from, to); @@ -1799,7 +1798,7 @@ error_code sys_fs_rename(ppu_thread& ppu, vm::cptr from, vm::cptr to error_code sys_fs_rmdir(ppu_thread& ppu, vm::cptr path) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_rmdir(path=%s)", path); @@ -1854,7 +1853,7 @@ error_code sys_fs_rmdir(ppu_thread& ppu, vm::cptr path) error_code sys_fs_unlink(ppu_thread& ppu, vm::cptr path) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_unlink(path=%s)", path); @@ -1919,7 +1918,7 @@ error_code sys_fs_access(ppu_thread&, vm::cptr path, s32 mode) error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr _arg, u32 _size) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.trace("sys_fs_fcntl(fd=%d, op=0x%x, arg=*0x%x, size=0x%x)", fd, op, _arg, _size); @@ -2591,7 +2590,7 @@ error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr _arg, u32 error_code sys_fs_lseek(ppu_thread& ppu, u32 fd, s64 offset, s32 whence, vm::ptr pos) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.trace("sys_fs_lseek(fd=%d, offset=0x%llx, whence=0x%x, pos=*0x%x)", fd, offset, whence, pos); @@ -2636,7 +2635,7 @@ error_code sys_fs_lseek(ppu_thread& ppu, u32 fd, s64 offset, s32 whence, vm::ptr error_code sys_fs_fdatasync(ppu_thread& ppu, u32 fd) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.trace("sys_fs_fdadasync(fd=%d)", fd); @@ -2662,7 +2661,7 @@ error_code sys_fs_fdatasync(ppu_thread& ppu, u32 fd) error_code sys_fs_fsync(ppu_thread& ppu, u32 fd) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.trace("sys_fs_fsync(fd=%d)", fd); @@ -2688,7 +2687,7 @@ error_code sys_fs_fsync(ppu_thread& ppu, u32 fd) error_code sys_fs_fget_block_size(ppu_thread& ppu, u32 fd, vm::ptr sector_size, vm::ptr block_size, vm::ptr arg4, vm::ptr out_flags) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_fget_block_size(fd=%d, sector_size=*0x%x, block_size=*0x%x, arg4=*0x%x, out_flags=*0x%x)", fd, sector_size, block_size, arg4, out_flags); @@ -2712,7 +2711,7 @@ error_code sys_fs_fget_block_size(ppu_thread& ppu, u32 fd, vm::ptr sector_s error_code sys_fs_get_block_size(ppu_thread& ppu, vm::cptr path, vm::ptr sector_size, vm::ptr block_size, vm::ptr arg4) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_get_block_size(path=%s, sector_size=*0x%x, block_size=*0x%x, arg4=*0x%x)", path, sector_size, block_size, arg4); @@ -2762,7 +2761,7 @@ error_code sys_fs_get_block_size(ppu_thread& ppu, vm::cptr path, vm::ptr path, u64 size) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_truncate(path=%s, size=0x%llx)", path, size); @@ -2813,7 +2812,7 @@ error_code sys_fs_truncate(ppu_thread& ppu, vm::cptr path, u64 size) error_code sys_fs_ftruncate(ppu_thread& ppu, u32 fd, u64 size) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_ftruncate(fd=%d, size=0x%llx)", fd, size); @@ -2939,7 +2938,6 @@ error_code sys_fs_chown(ppu_thread&, vm::cptr path, s32 uid, s32 gid) error_code sys_fs_disk_free(ppu_thread& ppu, vm::cptr path, vm::ptr total_free, vm::ptr avail_free) { - ppu.state += cpu_flag::wait; lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_disk_free(path=%s total_free=*0x%x avail_free=*0x%x)", path, total_free, avail_free); @@ -3018,7 +3016,7 @@ error_code sys_fs_disk_free(ppu_thread& ppu, vm::cptr path, vm::ptr t error_code sys_fs_utime(ppu_thread& ppu, vm::cptr path, vm::cptr timep) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_utime(path=%s, timep=*0x%x)", path, timep); sys_fs.warning("** actime=%u, modtime=%u", timep->actime, timep->modtime); @@ -3215,7 +3213,7 @@ error_code sys_fs_get_mount_info(ppu_thread&, vm::ptr info, u64 error_code sys_fs_newfs(ppu_thread& ppu, vm::cptr dev_name, vm::cptr file_system, s32 unk1, vm::cptr str1) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_newfs(dev_name=%s, file_system=%s, unk1=0x%x, str1=%s)", dev_name, file_system, unk1, str1); @@ -3263,7 +3261,7 @@ error_code sys_fs_newfs(ppu_thread& ppu, vm::cptr dev_name, vm::cptr error_code sys_fs_mount(ppu_thread& ppu, vm::cptr dev_name, vm::cptr file_system, vm::cptr path, s32 unk1, s32 prot, s32 unk2, vm::cptr str1, u32 str_len) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_mount(dev_name=%s, file_system=%s, path=%s, unk1=0x%x, prot=%d, unk3=0x%x, str1=%s, str_len=%d)", dev_name, file_system, path, unk1, prot, unk2, str1, str_len); @@ -3366,7 +3364,7 @@ error_code sys_fs_mount(ppu_thread& ppu, vm::cptr dev_name, vm::cptr error_code sys_fs_unmount(ppu_thread& ppu, vm::cptr path, s32 unk1, s32 force) { - ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_unmount(path=%s, unk1=0x%x, force=%d)", path, unk1, force); diff --git a/rpcs3/Emu/Io/LogitechG27.cpp b/rpcs3/Emu/Io/LogitechG27.cpp index 2eb62581bb..1f02c92d59 100644 --- a/rpcs3/Emu/Io/LogitechG27.cpp +++ b/rpcs3/Emu/Io/LogitechG27.cpp @@ -267,7 +267,7 @@ static const std::map; diff --git a/rpcs3/Emu/RSX/Common/texture_cache_utils.h b/rpcs3/Emu/RSX/Common/texture_cache_utils.h index c34b73f7ba..a180a1a8d1 100644 --- a/rpcs3/Emu/RSX/Common/texture_cache_utils.h +++ b/rpcs3/Emu/RSX/Common/texture_cache_utils.h @@ -79,7 +79,7 @@ namespace rsx private: // Members - block_list *block; + block_list* block = nullptr; list_iterator list_it = {}; size_type idx = u32{umax}; size_type array_idx = 0; @@ -705,9 +705,9 @@ namespace rsx private: // Members address_range32 range; - section_bounds bounds; + section_bounds bounds {}; - block_type *block = nullptr; + block_type* block = nullptr; bool needs_overlap_check = true; bool unowned_remaining = false; unowned_iterator unowned_it = {}; diff --git a/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp b/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp index 4e5e61dcba..18058842aa 100644 --- a/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp +++ b/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp @@ -218,7 +218,7 @@ void GLGSRender::init_buffers(rsx::framebuffer_creation_context context, bool /* static_cast(m_draw_fbo)->release(); } - for (auto &fbo : m_framebuffer_cache) + for (auto& fbo : m_framebuffer_cache) { if (fbo.matches(color_targets, depth_stencil_target)) { @@ -264,6 +264,8 @@ void GLGSRender::init_buffers(rsx::framebuffer_creation_context context, bool /* } } + ensure(m_draw_fbo); + switch (rsx::method_registers.surface_color_target()) { case rsx::surface_target::none: break; diff --git a/rpcs3/Emu/RSX/GL/GLTexture.cpp b/rpcs3/Emu/RSX/GL/GLTexture.cpp index 7b43cfc0a7..9a439177f4 100644 --- a/rpcs3/Emu/RSX/GL/GLTexture.cpp +++ b/rpcs3/Emu/RSX/GL/GLTexture.cpp @@ -289,6 +289,8 @@ namespace gl void* copy_image_to_buffer(gl::command_context& cmd, const pixel_buffer_layout& pack_info, const gl::texture* src, gl::buffer* dst, u32 dst_offset, const int src_level, const coord3u& src_region, image_memory_requirements* mem_info) { + ensure(src && dst); + auto initialize_scratch_mem = [&]() -> bool // skip_transform { const u64 max_mem = (mem_info->memory_required) ? mem_info->memory_required : mem_info->image_size_in_bytes; diff --git a/rpcs3/Emu/RSX/GL/GLTextureCache.h b/rpcs3/Emu/RSX/GL/GLTextureCache.h index 486087d56a..8dc2c27664 100644 --- a/rpcs3/Emu/RSX/GL/GLTextureCache.h +++ b/rpcs3/Emu/RSX/GL/GLTextureCache.h @@ -669,8 +669,8 @@ namespace gl } else { - //TODO: More tests on byte order - //ARGB8+native+unswizzled is confirmed with Dark Souls II character preview + // TODO: More tests on byte order + // ARGB8+native+unswizzled is confirmed with Dark Souls II character preview switch (gcm_format) { case CELL_GCM_TEXTURE_A8R8G8B8: @@ -697,8 +697,7 @@ namespace gl fmt::throw_exception("Unexpected gcm format 0x%X", gcm_format); } - //NOTE: Protection is handled by the caller - cached.set_dimensions(width, height, depth, (rsx_range.length() / height)); + // NOTE: Protection is handled by the caller no_access_range = cached.get_min_max(no_access_range, rsx::section_bounds::locked_range); } diff --git a/rpcs3/Emu/RSX/GL/glutils/program.h b/rpcs3/Emu/RSX/GL/glutils/program.h index 72daef2523..5caca0ed98 100644 --- a/rpcs3/Emu/RSX/GL/glutils/program.h +++ b/rpcs3/Emu/RSX/GL/glutils/program.h @@ -14,7 +14,7 @@ namespace gl class shader { std::string source; - ::glsl::program_domain type; + ::glsl::program_domain type {}; GLuint m_id = GL_NONE; fence m_compiled_fence; diff --git a/rpcs3/Emu/RSX/GL/glutils/state_tracker.hpp b/rpcs3/Emu/RSX/GL/glutils/state_tracker.hpp index 9835a5891f..694ed28cac 100644 --- a/rpcs3/Emu/RSX/GL/glutils/state_tracker.hpp +++ b/rpcs3/Emu/RSX/GL/glutils/state_tracker.hpp @@ -376,15 +376,12 @@ namespace gl GLuint get_bound_texture(GLuint layer, GLenum target) { - ensure(layer < 48); - return bound_textures[layer][target]; + return ::at32(bound_textures, layer)[target]; } void bind_texture(GLuint layer, GLenum target, GLuint name, GLboolean force = GL_FALSE) { - ensure(layer < 48); - - auto& bound = bound_textures[layer][target]; + auto& bound = ::at32(bound_textures, layer)[target]; if (bound != name || force) { glActiveTexture(GL_TEXTURE0 + layer); diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 651c220b60..7c0fa19c93 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -34,6 +34,7 @@ #include "Loader/PSF.h" #include "Loader/TAR.h" +#include "Loader/ISO.h" #include "Loader/ELF.h" #include "Loader/disc.h" @@ -782,7 +783,7 @@ bool Emulator::BootRsxCapture(const std::string& path) std::unique_ptr frame = std::make_unique(); utils::serial load; load.set_reading_state(); - + const std::string lower = fmt::to_lower(path); if (lower.ends_with(".gz") || lower.ends_with(".zst")) @@ -931,6 +932,7 @@ game_boot_result Emulator::BootGame(const std::string& path, const std::string& if (result != game_boot_result::no_errors) { + unload_iso(); GetCallbacks().close_gs_frame(); } } @@ -1078,6 +1080,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, sys_log.notice("Path: %s", m_path); std::string inherited_ps3_game_path; + bool launching_from_disc_archive = false; { Init(); @@ -1169,12 +1172,16 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, std::string disc_info; m_ar->serialize(argv.emplace_back(), disc_info, klic.emplace_back(), m_game_dir, hdd1); + launching_from_disc_archive = is_file_iso(disc_info); + + sys_log.notice("Savestate: is iso archive = %d ('%s')", launching_from_disc_archive, disc_info); + if (!klic[0]) { klic.clear(); } - if (!disc_info.empty() && disc_info[0] != '/') + if (!launching_from_disc_archive && !disc_info.empty() && disc_info[0] != '/') { // Restore disc path for disc games (must exist in games.yml i.e. your game library) m_title_id = disc_info; @@ -1182,6 +1189,11 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, // Load /dev_bdvd/ from game list if available if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty()) { + if (is_file_iso(game_path)) + { + game_path = iso_device::virtual_device_name + "/PS3_GAME/./"; + } + if (game_path.ends_with("/./")) { // Marked as PS3_GAME directory @@ -1286,6 +1298,16 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, m_path_old = m_path; resolve_path_as_vfs_path = true; + + if (launching_from_disc_archive) + { + sys_log.notice("Savestate: Loading iso archive"); + + load_iso(disc_info); + m_path = iso_device::virtual_device_name + "/" + argv[0]; + + resolve_path_as_vfs_path = false; + } } else if (m_path.starts_with(vfs_boot_prefix)) { @@ -1421,6 +1443,33 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, } const std::string resolved_path = GetCallbacks().resolve_path(m_path); + if (!launching_from_disc_archive && is_file_iso(m_path)) + { + sys_log.notice("Loading iso archive '%s'", m_path); + + load_iso(m_path); + + launching_from_disc_archive = true; + + std::string path = iso_device::virtual_device_name + "/"; + + // ISOs that are install discs will error if set to EBOOT.BIN + // so this should cover both of them + if (fs::exists(path + "PS3_GAME/USRDIR/EBOOT.BIN")) + { + path = path + "PS3_GAME/USRDIR/EBOOT.BIN"; + } + + m_path = path; + } + + sys_log.notice("Load: is iso archive = %d (m_path='%s')", launching_from_disc_archive, m_path); + + if (launching_from_disc_archive) + { + m_dir = "/dev_bdvd/PS3_GAME/"; + m_cat = "DG"sv; + } const std::string elf_dir = fs::get_parent_dir(m_path); @@ -1595,8 +1644,14 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, } } + // ISO PKG INSTALL HACK! + if (launching_from_disc_archive && fs::is_dir(m_path)) + { + bdvd_dir = m_path; + } + // Special boot mode (directory scan) - if (fs::is_dir(m_path)) + if (!launching_from_disc_archive && fs::is_dir(m_path)) { m_state = system_state::ready; GetCallbacks().on_ready(); @@ -1788,6 +1843,10 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, sys_log.error("Failed to move disc game %s to '%s' (%s)", m_title_id, dst_dir, fs::g_tls_error); return game_boot_result::wrong_disc_location; } + else if (launching_from_disc_archive) + { + bdvd_dir = iso_device::virtual_device_name + "/"; + } } if (bdvd_dir.empty() && disc.empty() && !is_disc_patch) @@ -1802,6 +1861,15 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, // Load /dev_bdvd/ from game list if available if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty()) { + if (is_file_iso(game_path)) + { + sys_log.notice("Loading iso archive for patch ('%s')", game_path); + + load_iso(game_path); + launching_from_disc_archive = true; + game_path = iso_device::virtual_device_name + "/PS3_GAME/./"; + } + if (game_path.ends_with("/./")) { // Marked as PS3_GAME directory @@ -1929,21 +1997,28 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, } // TODO: Verify timestamps and error codes with sys_fs - vfs::mount("/dev_bdvd", bdvd_dir); - vfs::mount("/dev_bdvd/PS3_GAME", inherited_ps3_game_path.empty() ? hdd0_game + m_path.substr(hdd0_game.size(), 10) : inherited_ps3_game_path); const std::string new_ps3_game = vfs::get("/dev_bdvd/PS3_GAME"); sys_log.notice("Game: %s", new_ps3_game); - // Store /dev_bdvd/PS3_GAME location - if (games_config::result res = m_games_config.add_game(m_title_id, new_ps3_game + "/./"); res == games_config::result::success) + if (!new_ps3_game.starts_with(iso_device::virtual_device_name)) { - sys_log.notice("Registered BDVD/PS3_GAME game directory for title '%s': %s", m_title_id, new_ps3_game); + // Store /dev_bdvd/PS3_GAME location + if (games_config::result res = m_games_config.add_game(m_title_id, new_ps3_game + "/./"); res == games_config::result::success) + { + sys_log.notice("Registered BDVD/PS3_GAME game directory for title '%s': %s", m_title_id, new_ps3_game); + } + else if (res == games_config::result::failure) + { + sys_log.error("Failed to save BDVD/PS3_GAME location of title '%s' (error=%s)", m_title_id, fs::g_tls_error); + } + + vfs::mount("/dev_bdvd", bdvd_dir); } - else if (res == games_config::result::failure) + else { - sys_log.error("Failed to save BDVD/PS3_GAME location of title '%s' (error=%s)", m_title_id, fs::g_tls_error); + vfs::mount("/dev_bdvd", iso_device::virtual_device_name + "/"); } } else if (disc.empty()) @@ -2076,6 +2151,13 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, } } + // ISO has no USRDIR/EBOOT.BIN, and we've examined its PKGDIR and extras. + // time to wrap up + if (launching_from_disc_archive && fs::is_dir(m_path)) + { + return game_boot_result::nothing_to_boot; + } + // Check firmware version if (const std::string_view game_fw_version = psf::get_string(_psf, "PS3_SYSTEM_VER", ""); !game_fw_version.empty()) { @@ -2184,7 +2266,8 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, // Check game updates if (const std::string hdd0_boot = hdd0_game + m_title_id + "/USRDIR/EBOOT.BIN"; !m_ar && recursion_count == 0 && disc.empty() && !bdvd_dir.empty() && !m_title_id.empty() - && resolved_path == GetCallbacks().resolve_path(vfs::get("/dev_bdvd/PS3_GAME/USRDIR/EBOOT.BIN")) + && (launching_from_disc_archive || + resolved_path == GetCallbacks().resolve_path(vfs::get("/dev_bdvd/PS3_GAME/USRDIR/EBOOT.BIN"))) && resolved_path != GetCallbacks().resolve_path(hdd0_boot) && fs::is_file(hdd0_boot) && ppu_exec == elf_error::ok) { @@ -2295,8 +2378,9 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, else if (!bdvd_dir.empty() && fs::is_dir(bdvd_dir)) { // Disc games are on /dev_bdvd/ - const usz pos = resolved_path.rfind(m_game_dir); - argv[0] = "/dev_bdvd/PS3_GAME/" + unescape(resolved_path.substr(pos + m_game_dir.size() + 1)); + const std::string& disc_path = launching_from_disc_archive ? m_path : resolved_path; + const usz pos = disc_path.rfind(m_game_dir); + argv[0] = "/dev_bdvd/PS3_GAME/" + unescape(disc_path.substr(pos + m_game_dir.size() + 1)); m_dir = "/dev_bdvd/PS3_GAME/"; } else if (from_dev_flash) @@ -3368,7 +3452,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s { if (spu.first->pc != spu.second || spu.first->unsavable) { - std::string dump; + std::string dump; spu.first->dump_all(dump); sys_log.error("SPU thread continued after being paused. (old_pc=0x%x, pc=0x%x, unsavable=%d)", spu.second, spu.first->pc, spu.first->unsavable); @@ -3546,7 +3630,18 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s ar(std::array{}); // Reserved for future use - if (auto dir = vfs::get("/dev_bdvd/PS3_GAME"); fs::is_dir(dir) && !fs::is_file(fs::get_parent_dir(dir) + "/PS3_DISC.SFB")) + // Game mounted from archive + if (m_path.starts_with(iso_device::virtual_device_name + "/")) + { + const auto device = fs::get_virtual_device(iso_device::virtual_device_name + "/"); + ensure(device); + + const auto iso_dev = dynamic_cast(device.get()); + + ar(m_path.substr(iso_device::virtual_device_name.size() + 1)); + ar(iso_dev->get_loaded_iso()); + } + else if (auto dir = vfs::get("/dev_bdvd/PS3_GAME"); fs::is_dir(dir) && !fs::is_file(fs::get_parent_dir(dir) + "/PS3_DISC.SFB")) { // Fake /dev_bdvd/PS3_GAME detected, use HDD0 for m_path restoration ensure(vfs::unmount("/dev_bdvd/PS3_GAME")); @@ -3840,6 +3935,11 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s m_emu_state_close_pending = false; m_precompilation_option = {}; + if (!m_continuous_mode) + { + unload_iso(); + } + initialize_timebased_time(0, true); // Complete the operation @@ -4039,13 +4139,18 @@ u32 Emulator::AddGamesFromDir(const std::string& path) { auto dir_entry = std::move(*path_it); - if (!dir_entry.is_directory || dir_entry.name == "." || dir_entry.name == "..") + if (dir_entry.name == "." || dir_entry.name == "..") { continue; } const std::string dir_path = path + '/' + dir_entry.name; + if (!dir_entry.is_directory && !is_file_iso(dir_path)) + { + continue; + } + if (const game_boot_result error = AddGame(dir_path); error == game_boot_result::no_errors) { games_added++; @@ -4143,10 +4248,17 @@ game_boot_result Emulator::AddGameToYml(const std::string& path) return error; } + std::unique_ptr archive; + if (is_file_iso(path)) + { + archive = std::make_unique(path); + } + // Load PARAM.SFO const std::string elf_dir = fs::get_parent_dir(path); - std::string sfo_dir = rpcs3::utils::get_sfo_dir_from_game_path(fs::get_parent_dir(elf_dir)); - const psf::registry _psf = psf::load_object(sfo_dir + "/PARAM.SFO"); + std::string sfo_dir = archive ? "PS3_GAME" : rpcs3::utils::get_sfo_dir_from_game_path(fs::get_parent_dir(elf_dir)); + const std::string sfo_path = sfo_dir + "/PARAM.SFO"; + const psf::registry _psf = archive ? archive->open_psf(sfo_path) : psf::load_object(sfo_path); const std::string title_id = std::string(psf::get_string(_psf, "TITLE_ID")); const std::string cat = std::string(psf::get_string(_psf, "CATEGORY")); @@ -4169,6 +4281,23 @@ game_boot_result Emulator::AddGameToYml(const std::string& path) return game_boot_result::invalid_file_or_folder; } + // Add ISO game + if (archive) + { + if (cat == "DG") + { + std::string iso_path = path; + switch (m_games_config.add_external_hdd_game(title_id, iso_path)) + { + case games_config::result::failure: return game_boot_result::generic_error; + case games_config::result::success: return game_boot_result::no_errors; + case games_config::result::exists: return game_boot_result::already_added; + } + + return game_boot_result::generic_error; + } + } + // Set bdvd_dir std::string bdvd_dir; std::string game_dir; @@ -4331,7 +4460,7 @@ const std::string& Emulator::GetFakeCat() const { const std::string mount_point = vfs::get("/dev_bdvd"); - if (mount_point.empty() || !IsPathInsideDir(m_path, mount_point)) + if (mount_point.empty() || (!IsPathInsideDir(m_path, mount_point) && !m_path.starts_with(iso_device::virtual_device_name))) { static const std::string s_hg = "HG"; return s_hg; diff --git a/rpcs3/Emu/games_config.cpp b/rpcs3/Emu/games_config.cpp index cfa43f6e15..ae0fad8084 100644 --- a/rpcs3/Emu/games_config.cpp +++ b/rpcs3/Emu/games_config.cpp @@ -4,6 +4,8 @@ #include "util/yaml.hpp" #include "Utilities/File.h" +#include "Loader/ISO.h" + LOG_CHANNEL(cfg_log, "CFG"); games_config::games_config() @@ -44,6 +46,15 @@ std::string games_config::get_path(const std::string& title_id) const games_config::result games_config::add_game(const std::string& key, const std::string& path) { + if (path == iso_device::virtual_device_name + "/") + { + const auto device = fs::get_virtual_device(iso_device::virtual_device_name + "/"); + if (!device) return result::failure; + + const auto iso_dev = dynamic_cast(device.get()); + return add_game(key, iso_dev->get_loaded_iso()); + } + std::lock_guard lock(m_mutex); // Access or create node if does not exist diff --git a/rpcs3/Input/sdl_instance.cpp b/rpcs3/Input/sdl_instance.cpp index 9850974492..d3296f44ba 100644 --- a/rpcs3/Input/sdl_instance.cpp +++ b/rpcs3/Input/sdl_instance.cpp @@ -102,6 +102,11 @@ bool sdl_instance::initialize_impl() set_hint(SDL_HINT_JOYSTICK_HIDAPI_PS3, "1"); #endif + // Disable LG4FF driver on windows (only needed for SDL 3.4.0) +#if _WIN32 && SDL_VERSION_ATLEAST(3, 4, 0) + set_hint(SDL_HINT_JOYSTICK_HIDAPI_LG4FF, "0"); +#endif + if (!SDL_Init(SDL_INIT_GAMEPAD | SDL_INIT_HAPTIC | SDL_INIT_CAMERA)) { sdl_log.error("Could not initialize! SDL Error: %s", SDL_GetError()); diff --git a/rpcs3/Input/sdl_pad_handler.cpp b/rpcs3/Input/sdl_pad_handler.cpp index 1b6ddbc40c..c47514f6ae 100644 --- a/rpcs3/Input/sdl_pad_handler.cpp +++ b/rpcs3/Input/sdl_pad_handler.cpp @@ -738,7 +738,7 @@ pad_capabilities sdl_pad_handler::get_capabilities(const std::string& pad_id) capabilities.has_rumble &= dev->sdl.has_rumble; capabilities.has_accel &= dev->sdl.has_accel; capabilities.has_gyro &= dev->sdl.has_gyro; - capabilities.has_pressure_sensitivity &= dev->sdl.is_ds3_with_pressure_buttons; + capabilities.has_pressure_intensity_button &= !dev->sdl.is_ds3_with_pressure_buttons; // Only allow if there's not pressure sensitivity return capabilities; } diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp new file mode 100644 index 0000000000..e46a1a6b82 --- /dev/null +++ b/rpcs3/Loader/ISO.cpp @@ -0,0 +1,617 @@ +#include "stdafx.h" + +#include "ISO.h" +#include "Emu/VFS.h" + +#include +#include +#include +#include +#include + +LOG_CHANNEL(sys_log, "SYS"); + +bool is_file_iso(const std::string& path) +{ + if (path.empty()) return false; + if (fs::is_dir(path)) return false; + + return is_file_iso(fs::file(path)); +} + +bool is_file_iso(const fs::file& file) +{ + if (!file) return false; + if (file.size() < 32768 + 6) return false; + + file.seek(32768); + + char magic[5]; + file.read_at(32768 + 1, magic, 5); + + return magic[0] == 'C' && magic[1] == 'D' + && magic[2] == '0' && magic[3] == '0' + && magic[4] == '1'; +} + +const int ISO_BLOCK_SIZE = 2048; + +template +inline T read_both_endian_int(fs::file& file) +{ + T out; + + if (std::endian::little == std::endian::native) + { + out = file.read(); + file.seek(sizeof(T), fs::seek_cur); + } + else + { + file.seek(sizeof(T), fs::seek_cur); + out = file.read(); + } + + return out; +} + +// assumed that directory_entry is at file head +std::optional iso_read_directory_entry(fs::file& file, bool names_in_ucs2 = false) +{ + const auto start_pos = file.pos(); + const u8 entry_length = file.read(); + + if (entry_length == 0) return std::nullopt; + + file.seek(1, fs::seek_cur); + const u32 start_sector = read_both_endian_int(file); + const u32 file_size = read_both_endian_int(file); + + std::tm file_date = {}; + file_date.tm_year = file.read(); + file_date.tm_mon = file.read() - 1; + file_date.tm_mday = file.read(); + file_date.tm_hour = file.read(); + file_date.tm_min = file.read(); + file_date.tm_sec = file.read(); + const s16 timezone_value = file.read(); + const s16 timezone_offset = (timezone_value - 50) * 15 * 60; + + const std::time_t date_time = std::mktime(&file_date) + timezone_offset; + + const u8 flags = file.read(); + + // 2nd flag bit indicates whether a given fs node is a directory + const bool is_directory = flags & 0b00000010; + const bool has_more_extents = flags & 0b10000000; + + file.seek(6, fs::seek_cur); + + const u8 file_name_length = file.read(); + + std::string file_name; + file.read(file_name, file_name_length); + + if (file_name_length == 1 && file_name[0] == 0) + { + file_name = "."; + } + else if (file_name == "\1") + { + file_name = ".."; + } + else if (names_in_ucs2) // for strings in joliet descriptor + { + // characters are stored in big endian format. + std::u16string utf16; + utf16.resize(file_name_length / 2); + + const u16* raw = reinterpret_cast(file_name.data()); + for (size_t i = 0; i < utf16.size(); ++i, raw++) + { + utf16[i] = *reinterpret_cast*>(raw); + } + + file_name = utf16_to_utf8(utf16); + } + + if (file_name.ends_with(";1")) + { + file_name.erase(file_name.end() - 2, file_name.end()); + } + + if (file_name_length > 1 && file_name.ends_with(".")) + { + file_name.pop_back(); + } + + // skip the rest of the entry. + file.seek(entry_length + start_pos); + + return iso_fs_metadata + { + .name = std::move(file_name), + .time = date_time, + .is_directory = is_directory, + .has_multiple_extents = has_more_extents, + .extents = + { + iso_extent_info + { + .start = start_sector, + .size = file_size + } + } + }; +} + +void iso_form_hierarchy(fs::file& file, iso_fs_node& node, bool use_ucs2_decoding = false, const std::string& parent_path = "") +{ + if (!node.metadata.is_directory) return; + + std::vector multi_extent_node_indices; + + // assuming the directory spans a single extent + const auto& directory_extent = node.metadata.extents[0]; + + file.seek(directory_extent.start * ISO_BLOCK_SIZE); + + const u64 end_pos = directory_extent.size + (directory_extent.start * ISO_BLOCK_SIZE); + + while(file.pos() < end_pos) + { + auto entry = iso_read_directory_entry(file, use_ucs2_decoding); + if (!entry) + { + const u64 new_sector = (file.pos() / ISO_BLOCK_SIZE) + 1; + file.seek(new_sector * ISO_BLOCK_SIZE); + continue; + } + + bool extent_added = false; + + // find previous extent and merge into it, otherwise we push this node's index + for (usz index : multi_extent_node_indices) + { + auto& selected_node = ::at32(node.children, index); + if (selected_node->metadata.name == entry->name) + { + // merge into selected_node + selected_node->metadata.extents.push_back(entry->extents[0]); + + extent_added = true; + } + } + + if (extent_added) continue; + + if (entry->has_multiple_extents) + { + // haven't pushed entry to node.children yet so node.children::size() == entry_index + multi_extent_node_indices.push_back(node.children.size()); + } + + node.children.push_back(std::make_unique(iso_fs_node{ + .metadata = std::move(*entry) + })); + } + + for (auto& child_node : node.children) + { + if (child_node->metadata.name != "." && child_node->metadata.name != "..") + { + iso_form_hierarchy(file, *child_node, use_ucs2_decoding, parent_path + "/" + node.metadata.name); + } + } +} + +u64 iso_fs_metadata::size() const +{ + u64 total_size = 0; + for (const auto& extent : extents) + { + total_size += extent.size; + } + + return total_size; +} + +iso_archive::iso_archive(const std::string& path) +{ + m_path = path; + m_file = fs::file(path); + + if (!is_file_iso(m_file)) + { + // not iso... TODO: throw something?? + return; + } + + u8 descriptor_type = -2; + bool use_ucs2_decoding = false; + + do + { + const auto descriptor_start = m_file.pos(); + + descriptor_type = m_file.read(); + + // 1 = primary vol descriptor, 2 = joliet SVD + if (descriptor_type == 1 || descriptor_type == 2) + { + use_ucs2_decoding = descriptor_type == 2; + + // skip the rest of descriptor's data + m_file.seek(155, fs::seek_cur); + + m_root = iso_fs_node + { + .metadata = iso_read_directory_entry(m_file, use_ucs2_decoding).value(), + }; + } + + m_file.seek(descriptor_start + ISO_BLOCK_SIZE); + } + while (descriptor_type != 255); + + iso_form_hierarchy(m_file, m_root, use_ucs2_decoding); +} + +iso_fs_node* iso_archive::retrieve(const std::string& passed_path) +{ + if (passed_path.empty()) return nullptr; + + const std::string path = std::filesystem::path(passed_path).string(); + const std::string_view path_sv = path; + + size_t start = 0; + size_t end = path_sv.find_first_of(fs::delim); + + std::stack search_stack; + search_stack.push(&m_root); + + do + { + if (search_stack.empty()) return nullptr; + const auto* top_entry = search_stack.top(); + + if (end == umax) + { + end = path.size(); + } + + const std::string_view path_component = path_sv.substr(start, end-start); + + bool found = false; + + if (path_component == ".") + { + found = true; + } + else if (path_component == "..") + { + search_stack.pop(); + found = true; + } + else + { + for (const auto& entry : top_entry->children) + { + if (entry->metadata.name == path_component) + { + search_stack.push(entry.get()); + + found = true; + break; + } + } + } + + if (!found) return nullptr; + + start = end + 1; + end = path_sv.find_first_of(fs::delim, start); + } + while (start < path.size()); + + if (search_stack.empty()) return nullptr; + + return search_stack.top(); +} + +bool iso_archive::exists(const std::string& path) +{ + return retrieve(path) != nullptr; +} + +bool iso_archive::is_file(const std::string& path) +{ + const auto file_node = retrieve(path); + if (!file_node) return false; + + return !file_node->metadata.is_directory; +} + +iso_file iso_archive::open(const std::string& path) +{ + return iso_file(fs::file(m_path), *ensure(retrieve(path))); +} + +psf::registry iso_archive::open_psf(const std::string& path) +{ + auto* archive_file = retrieve(path); + if (!archive_file) return psf::registry(); + + const fs::file psf_file(std::make_unique(fs::file(m_path), *archive_file)); + + return psf::load_object(psf_file, path); +} + +iso_file::iso_file(fs::file&& iso_handle, const iso_fs_node& node) + : m_file(std::move(iso_handle)), m_meta(node.metadata) +{ + m_file.seek(ISO_BLOCK_SIZE * node.metadata.extents[0].start); +} + +fs::stat_t iso_file::get_stat() +{ + return fs::stat_t + { + .is_directory = false, + .is_symlink = false, + .is_writable = false, + .size = size(), + .atime = m_meta.time, + .mtime = m_meta.time, + .ctime = m_meta.time + }; +} + +bool iso_file::trunc(u64 /*length*/) +{ + fs::g_tls_error = fs::error::readonly; + return false; +} + +std::pair iso_file::get_extent_pos(u64 pos) const +{ + ensure(!m_meta.extents.empty()); + + auto it = m_meta.extents.begin(); + + while (pos >= it->size && it != m_meta.extents.end() - 1) + { + pos -= it->size; + + it++; + } + + return {pos, *it}; +} + +// assumed valid and in bounds. +u64 iso_file::file_offset(u64 pos) const +{ + const auto [local_pos, extent] = get_extent_pos(pos); + + return (extent.start * ISO_BLOCK_SIZE) + local_pos; +} + +u64 iso_file::local_extent_remaining(u64 pos) const +{ + const auto [local_pos, extent] = get_extent_pos(pos); + + return extent.size - local_pos; +} + +u64 iso_file::local_extent_size(u64 pos) const +{ + return get_extent_pos(pos).second.size; +} + +u64 iso_file::read(void* buffer, u64 size) +{ + const auto r = read_at(m_pos, buffer, size); + m_pos += r; + return r; +} + +u64 iso_file::read_at(u64 offset, void* buffer, u64 size) +{ + const u64 local_remaining = local_extent_remaining(offset); + + const u64 total_read = m_file.read_at(file_offset(offset), buffer, std::min(size, local_remaining)); + + const auto total_size = this->size(); + + if (size > total_read && (offset + total_read) < total_size) + { + const u64 second_total_read = read_at(offset + total_read, reinterpret_cast(buffer) + total_read, size - total_read); + + return total_read + second_total_read; + } + + return total_read; +} + +u64 iso_file::write(const void* /*buffer*/, u64 /*size*/) +{ + fs::g_tls_error = fs::error::readonly; + return 0; +} + +u64 iso_file::seek(s64 offset, fs::seek_mode whence) +{ + const s64 total_size = size(); + const s64 new_pos = + whence == fs::seek_set ? offset : + whence == fs::seek_cur ? offset + m_pos : + whence == fs::seek_end ? offset + total_size : -1; + + if (new_pos < 0) + { + fs::g_tls_error = fs::error::inval; + return -1; + } + + const u64 result = m_file.seek(file_offset(m_pos)); + if (result == umax) return umax; + + m_pos = new_pos; + return m_pos; +} + +u64 iso_file::size() +{ + u64 extent_sizes = 0; + for (const auto& extent : m_meta.extents) + { + extent_sizes += extent.size; + } + + return extent_sizes; +} + +void iso_file::release() +{ + m_file.release(); +} + +bool iso_dir::read(fs::dir_entry& entry) +{ + if (m_pos < m_node.children.size()) + { + const auto& selected = m_node.children[m_pos].get()->metadata; + + entry.name = selected.name; + entry.atime = selected.time; + entry.mtime = selected.time; + entry.ctime = selected.time; + entry.is_directory = selected.is_directory; + entry.is_symlink = false; + entry.is_writable = false; + entry.size = selected.size(); + + m_pos++; + + return true; + } + + return false; +} + +bool iso_device::stat(const std::string& path, fs::stat_t& info) +{ + const auto relative_path = std::filesystem::relative(std::filesystem::path(path), std::filesystem::path(fs_prefix)).string(); + + const auto node = m_archive.retrieve(relative_path); + if (!node) + { + fs::g_tls_error = fs::error::noent; + return false; + } + + const auto& meta = node->metadata; + + info = fs::stat_t + { + .is_directory = meta.is_directory, + .is_symlink = false, + .is_writable = false, + .size = meta.size(), + .atime = meta.time, + .mtime = meta.time, + .ctime = meta.time + }; + + return true; +} + +bool iso_device::statfs(const std::string& path, fs::device_stat& info) +{ + const auto relative_path = std::filesystem::relative(std::filesystem::path(path), std::filesystem::path(fs_prefix)).string(); + + const auto node = m_archive.retrieve(relative_path); + if (!node) + { + fs::g_tls_error = fs::error::noent; + return false; + } + + const auto& meta = node->metadata; + const u64 size = meta.size(); + + info = fs::device_stat + { + .block_size = size, + .total_size = size, + .total_free = 0, + .avail_free = 0 + }; + + return false; +} + +std::unique_ptr iso_device::open(const std::string& path, bs_t mode) +{ + const auto relative_path = std::filesystem::relative(std::filesystem::path(path), std::filesystem::path(fs_prefix)).string(); + + const auto node = m_archive.retrieve(relative_path); + if (!node) + { + fs::g_tls_error = fs::error::noent; + return nullptr; + } + + if (node->metadata.is_directory) + { + fs::g_tls_error = fs::error::isdir; + return nullptr; + } + + return std::make_unique(fs::file(iso_path), *node); +} + +std::unique_ptr iso_device::open_dir(const std::string& path) +{ + const auto relative_path = std::filesystem::relative(std::filesystem::path(path), std::filesystem::path(fs_prefix)).string(); + + const auto node = m_archive.retrieve(relative_path); + if (!node) + { + fs::g_tls_error = fs::error::noent; + return nullptr; + } + + if (!node->metadata.is_directory) + { + // fs::dir::open -> ::readdir should return ENOTDIR when path is + // pointing to a file instead of a folder, which translates to error::unknown. + // doing the same here. + fs::g_tls_error = fs::error::unknown; + return nullptr; + } + + return std::make_unique(*node); +} + +void iso_dir::rewind() +{ + m_pos = 0; +} + +void load_iso(const std::string& path) +{ + sys_log.notice("Loading iso '%s'", path); + + fs::set_virtual_device("iso_overlay_fs_dev", stx::make_shared(path)); + + vfs::mount("/dev_bdvd/"sv, iso_device::virtual_device_name + "/"); +} + +void unload_iso() +{ + sys_log.notice("Unoading iso"); + + fs::set_virtual_device("iso_overlay_fs_dev", stx::shared_ptr()); +} diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h new file mode 100644 index 0000000000..3af4732aad --- /dev/null +++ b/rpcs3/Loader/ISO.h @@ -0,0 +1,121 @@ +#pragma once + +#include "Loader/PSF.h" + +#include "Utilities/File.h" +#include "util/types.hpp" + +bool is_file_iso(const std::string& path); +bool is_file_iso(const fs::file& path); + +void load_iso(const std::string& path); +void unload_iso(); + +struct iso_extent_info +{ + u64 start; + u64 size; +}; + +struct iso_fs_metadata +{ + std::string name; + s64 time; + bool is_directory; + bool has_multiple_extents; + std::vector extents; + + u64 size() const; +}; + +struct iso_fs_node +{ + iso_fs_metadata metadata; + std::vector> children; +}; + +class iso_file : public fs::file_base +{ +private: + fs::file m_file; + iso_fs_metadata m_meta; + u64 m_pos = 0; + + std::pair get_extent_pos(u64 pos) const; + u64 file_offset(u64 pos) const; + u64 local_extent_remaining(u64 pos) const; + u64 local_extent_size(u64 pos) const; + +public: + iso_file(fs::file&& iso_handle, const iso_fs_node& node); + + fs::stat_t get_stat() override; + bool trunc(u64 length) override; + u64 read(void* buffer, u64 size) override; + u64 read_at(u64 offset, void* buffer, u64 size) override; + u64 write(const void* buffer, u64 size) override; + u64 seek(s64 offset, fs::seek_mode whence) override; + u64 size() override; + + void release() override; +}; + +class iso_dir : public fs::dir_base +{ +private: + const iso_fs_node& m_node; + u64 m_pos = 0; + +public: + iso_dir(const iso_fs_node& node) + : m_node(node) + {} + + bool read(fs::dir_entry&) override; + void rewind() override; +}; + +// represents the .iso file itself. +class iso_archive +{ +private: + std::string m_path; + iso_fs_node m_root; + fs::file m_file; + +public: + iso_archive(const std::string& path); + + iso_fs_node* retrieve(const std::string& path); + bool exists(const std::string& path); + bool is_file(const std::string& path); + + iso_file open(const std::string& path); + + psf::registry open_psf(const std::string& path); +}; + +class iso_device : public fs::device_base +{ +private: + iso_archive m_archive; + std::string iso_path; + +public: + inline static std::string virtual_device_name = "/vfsv0_virtual_iso_overlay_fs_dev"; + + iso_device(const std::string& iso_path, const std::string& device_name = virtual_device_name) + : m_archive(iso_path), iso_path(iso_path) + { + fs_prefix = device_name; + } + ~iso_device() override = default; + + const std::string& get_loaded_iso() const { return iso_path; } + + bool stat(const std::string& path, fs::stat_t& info) override; + bool statfs(const std::string& path, fs::device_stat& info) override; + + std::unique_ptr open(const std::string& path, bs_t mode) override; + std::unique_ptr open_dir(const std::string& path) override; +}; diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 357dfe238d..7f3be85a13 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -540,6 +540,7 @@ + @@ -1021,6 +1022,7 @@ + @@ -1096,4 +1098,4 @@ - \ No newline at end of file + diff --git a/rpcs3/rpcs3qt/flow_layout.h b/rpcs3/rpcs3qt/flow_layout.h index c04fa8ab6d..ba0ec201f6 100644 --- a/rpcs3/rpcs3qt/flow_layout.h +++ b/rpcs3/rpcs3qt/flow_layout.h @@ -61,6 +61,8 @@ public: { int row{}; int col{}; + + operator bool() const { return row >= 0 && col >= 0; } }; explicit flow_layout(QWidget* parent, int margin = -1, bool dynamic_spacing = false, int hSpacing = -1, int vSpacing = -1); diff --git a/rpcs3/rpcs3qt/flow_widget.cpp b/rpcs3/rpcs3qt/flow_widget.cpp index 13754f6c66..bf00b3fa4e 100644 --- a/rpcs3/rpcs3qt/flow_widget.cpp +++ b/rpcs3/rpcs3qt/flow_widget.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "flow_widget.h" +#include #include #include #include @@ -48,14 +49,24 @@ void flow_widget::clear() m_flow_layout->clear(); } -flow_widget_item* flow_widget::selected_item() const +std::set flow_widget::selected_items() const { - if (m_selected_index >= 0 && static_cast(m_selected_index) < m_widgets.size()) + std::set items; + + for (s64 index : m_selected_items) { - return ::at32(m_widgets, m_selected_index); + if (index >= 0 && static_cast(index) < m_widgets.size()) + { + items.insert(::at32(m_widgets, index)); + + if (!m_allow_multi_selection) + { + return items; + } + } } - return nullptr; + return items; } void flow_widget::paintEvent(QPaintEvent* /*event*/) @@ -69,7 +80,7 @@ void flow_widget::paintEvent(QPaintEvent* /*event*/) s64 flow_widget::find_item(const flow_layout::position& pos) { - if (pos.row < 0 || pos.col < 0) + if (!pos) { return -1; } @@ -109,7 +120,7 @@ flow_layout::position flow_widget::find_item(flow_widget_item* item) flow_layout::position flow_widget::find_next_item(flow_layout::position current_pos, flow_navigation value) { - if (current_pos.row >= 0 && current_pos.col >= 0 && m_flow_layout->rows() > 0 && m_flow_layout->cols() > 0) + if (current_pos && m_flow_layout->rows() > 0 && m_flow_layout->cols() > 0) { switch (value) { @@ -183,54 +194,160 @@ flow_layout::position flow_widget::find_next_item(flow_layout::position current_ return current_pos; } -void flow_widget::select_item(flow_widget_item* item) +void flow_widget::select_items(const std::set& selected_items, flow_widget_item* current_item) { - const flow_layout::position selected_pos = find_item(item); - const s64 selected_index = find_item(selected_pos); + m_selected_items.clear(); - if (selected_index < 0 || static_cast(selected_index) >= items().size()) + for (flow_widget_item* item : selected_items) { - m_selected_index = -1; - return; - } + const flow_layout::position selected_pos = find_item(item); + const s64 selected_index = find_item(selected_pos); - m_selected_index = selected_index; - Q_EMIT ItemSelectionChanged(m_selected_index); + if (selected_index < 0 || static_cast(selected_index) >= items().size()) + { + continue; + } + + m_selected_items.insert(selected_index); + + if (!m_allow_multi_selection) + { + break; + } + } for (usz i = 0; i < items().size(); i++) { if (flow_widget_item* item = items().at(i)) { // We need to polish the widgets in order to re-apply any stylesheet changes for the selected property. - item->selected = m_selected_index >= 0 && i == static_cast(m_selected_index); + item->selected = m_selected_items.contains(i); item->polish_style(); } } - // Make sure we see the focused widget - m_scroll_area->ensureWidgetVisible(::at32(items(), m_selected_index)); + if (m_selected_items.empty()) + { + m_last_selected_item = -1; + } + else + { + if (!m_selected_items.contains(m_last_selected_item)) + { + m_last_selected_item = *m_selected_items.cbegin(); + } + + s64 selected_item = m_last_selected_item; + s64 focused_item = selected_item; + + if (current_item) + { + if (const flow_layout::position selected_pos = find_item(current_item)) + { + focused_item = find_item(selected_pos); + + if (current_item->selected) + { + selected_item = focused_item; + } + } + } + + // Make sure we see the focused widget + m_scroll_area->ensureWidgetVisible(::at32(items(), focused_item)); + + Q_EMIT ItemSelectionChanged(selected_item); + } +} + +void flow_widget::update_selection(flow_widget_item* current_item) +{ + std::set selected_items; + const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); + + if (current_item) + { + if (!m_allow_multi_selection || !current_item->selected || modifiers != Qt::ControlModifier) + { + selected_items.insert(current_item); + } + } + + if (m_allow_multi_selection) + { + flow_layout::position selected_pos; + flow_layout::position last_selected_pos; + + if (modifiers == Qt::ShiftModifier && m_last_selected_item >= 0 && static_cast(m_last_selected_item) < items().size()) + { + selected_pos = find_item(current_item); + last_selected_pos = find_item(::at32(m_widgets, m_last_selected_item)); + } + + flow_layout::position pos_min = last_selected_pos; + flow_layout::position pos_max = selected_pos; + + if (pos_min.row > pos_max.row) + { + std::swap(pos_min, pos_max); + } + + // Check if the item is between the last and the current selection + const auto item_between = [this, &pos_min, &pos_max](flow_widget_item* item) + { + if (!pos_min || !pos_max) return false; + + const flow_layout::position pos = find_item(item); + if (!pos) return false; // pos invalid + if (pos.row < pos_min.row || pos.row > pos_max.row) return false; // not in any relevant row + if (pos.row > pos_min.row && pos.row < pos_max.row) return true; // in a row between the items -> match + + const bool in_min_row = pos.row == pos_min.row && pos.col >= pos_min.col; + const bool in_max_row = pos.row == pos_max.row && pos.col <= pos_max.col; + + if (pos_min.row == pos_max.row) return in_min_row && in_max_row; // in the only row at a relevant col -> match + + return in_min_row || in_max_row; // in either min or max row at a relevant col -> match + }; + + for (usz i = 0; i < items().size(); i++) + { + if (flow_widget_item* item = items().at(i)) + { + if (item == current_item) continue; + + if (modifiers == Qt::ControlModifier && item->selected) + { + selected_items.insert(item); + } + else if (modifiers == Qt::ShiftModifier && item_between(item)) + { + selected_items.insert(item); + } + } + } + } + + select_items(selected_items, current_item); } void flow_widget::on_item_focus() { - select_item(static_cast(QObject::sender())); + update_selection(static_cast(QObject::sender())); } void flow_widget::on_navigate(flow_navigation value) { const flow_layout::position selected_pos = find_next_item(find_item(static_cast(QObject::sender())), value); const s64 selected_index = find_item(selected_pos); - if (selected_index < 0 || static_cast(selected_index) >= items().size()) - { - return; - } - if (flow_widget_item* item = items().at(selected_index)) + if (selected_index >= 0 && static_cast(selected_index) < items().size()) { - item->setFocus(); + if (flow_widget_item* item = items().at(selected_index)) + { + item->setFocus(); + } } - - m_selected_index = selected_index; } void flow_widget::mouseDoubleClickEvent(QMouseEvent* ev) diff --git a/rpcs3/rpcs3qt/flow_widget.h b/rpcs3/rpcs3qt/flow_widget.h index 433d05ad0e..1a4bcfb90d 100644 --- a/rpcs3/rpcs3qt/flow_widget.h +++ b/rpcs3/rpcs3qt/flow_widget.h @@ -4,6 +4,7 @@ #include "flow_widget_item.h" #include "flow_layout.h" +#include #include #include #include @@ -19,8 +20,10 @@ public: void add_widget(flow_widget_item* widget); void clear(); + void set_multi_selection_enabled(bool enabled) { m_allow_multi_selection = enabled; } + std::vector& items() { return m_widgets; } - flow_widget_item* selected_item() const; + std::set selected_items() const; QScrollArea* scroll_area() const { return m_scroll_area; } void paintEvent(QPaintEvent* event) override; @@ -33,7 +36,7 @@ private Q_SLOTS: void on_navigate(flow_navigation value); protected: - void select_item(flow_widget_item* item); + void select_items(const std::set& selected_items, flow_widget_item* current_item = nullptr); void mouseDoubleClickEvent(QMouseEvent* event) override; private: @@ -41,8 +44,12 @@ private: flow_layout::position find_item(flow_widget_item* item); flow_layout::position find_next_item(flow_layout::position current_pos, flow_navigation value); + void update_selection(flow_widget_item* current_item); + flow_layout* m_flow_layout{}; QScrollArea* m_scroll_area{}; std::vector m_widgets; - s64 m_selected_index = -1; + std::set m_selected_items; + s64 m_last_selected_item = -1; + bool m_allow_multi_selection = false; }; diff --git a/rpcs3/rpcs3qt/game_list_actions.cpp b/rpcs3/rpcs3qt/game_list_actions.cpp index 3e8a57d703..8cc4f767ef 100644 --- a/rpcs3/rpcs3qt/game_list_actions.cpp +++ b/rpcs3/rpcs3qt/game_list_actions.cpp @@ -25,6 +25,7 @@ #include LOG_CHANNEL(game_list_log, "GameList"); +LOG_CHANNEL(sys_log, "SYS"); extern atomic_t g_system_progress_canceled; @@ -340,7 +341,7 @@ void game_list_actions::ShowRemoveGameDialog(const std::vector& games if (content_info.is_single_selection) // Single selection { - if (!RemoveContentList(games[0]->info.serial, true)) + if (!RemoveContentList(games[0]->info.serial)) { QMessageBox::critical(m_game_list_frame, tr("Failure!"), caches->isChecked() ? tr("Failed to remove %0 from drive!\nCaches and custom configs have been left intact.").arg(QString::fromStdString(games[0]->info.name)) @@ -351,7 +352,7 @@ void game_list_actions::ShowRemoveGameDialog(const std::vector& games } else // Multi selection { - BatchRemoveContentLists(games, true); + BatchRemoveContentLists(games); } } @@ -363,6 +364,41 @@ void game_list_actions::ShowGameInfoDialog(const std::vector& games) QMessageBox::information(m_game_list_frame, tr("Game Info"), GetContentInfo(games).info); } +void game_list_actions::ShowDiskUsageDialog() +{ + if (m_disk_usage_future.isRunning()) // Still running the last request + return; + + // Disk usage calculation can take a while (in particular on non ssd/m.2 disks) + // so run it on a concurrent thread avoiding to block the entire GUI + m_disk_usage_future = QtConcurrent::run([this]() + { + const std::vector> vfs_disk_usage = rpcs3::utils::get_vfs_disk_usage(); + const u64 cache_disk_usage = rpcs3::utils::get_cache_disk_usage(); + + QString text; + u64 tot_data_size = 0; + + for (const auto& [dev, data_size] : vfs_disk_usage) + { + text += tr("\n %0: %1").arg(QString::fromStdString(dev)).arg(gui::utils::format_byte_size(data_size)); + tot_data_size += data_size; + } + + if (!text.isEmpty()) + text = tr("\n VFS disk usage: %0%1").arg(gui::utils::format_byte_size(tot_data_size)).arg(text); + + text += tr("\n Cache disk usage: %0").arg(gui::utils::format_byte_size(cache_disk_usage)); + + sys_log.success("%s", text); + + Emu.CallFromMainThread([this, text]() + { + QMessageBox::information(m_game_list_frame, tr("Disk usage"), text); + }, nullptr, false); + }); +} + bool game_list_actions::IsGameRunning(const std::string& serial) { return !Emu.IsStopped(true) && (serial == Emu.GetTitleID() || (serial == "vsh.self" && Emu.IsVsh())); @@ -1063,6 +1099,128 @@ void game_list_actions::BatchCreateCPUCaches(const std::vector& games }); } +void game_list_actions::BatchRemoveCustomConfigurations(const std::vector& games, bool is_interactive) +{ + if (is_interactive && QMessageBox::question(m_game_list_frame, tr("Confirm Removal"), tr("Remove custom configuration?")) != QMessageBox::Yes) + { + return; + } + + std::set serials; + + for (const auto& game : (games.empty() ? m_game_list_frame->GetGameInfo() : games)) + { + if (game->has_custom_config && !serials.count(game->info.serial)) + { + serials.emplace(game->info.serial); + } + } + + const u32 total = ::size32(serials); + + if (total == 0) + { + QMessageBox::information(m_game_list_frame, tr("Custom Configuration Batch Removal"), tr("No files found"), QMessageBox::Ok); + return; + } + + progress_dialog* pdlg = new progress_dialog(tr("Custom Configuration Batch Removal"), tr("Removing all custom configurations"), tr("Cancel"), 0, total, false, m_game_list_frame); + pdlg->setAutoClose(false); + pdlg->setAutoReset(false); + pdlg->open(); + + BatchActionBySerials(pdlg, serials, tr("%0/%1 custom configurations cleared"), [this](const std::string& serial) + { + return !serial.empty() && Emu.IsStopped(true) && RemoveCustomConfiguration(serial); + }, + [](u32 removed, u32 total) + { + game_list_log.notice("Custom Configuration Batch Removal was canceled. %d/%d custom configurations cleared", removed, total); + }, nullptr, true); +} + +void game_list_actions::BatchRemoveCustomPadConfigurations(const std::vector& games, bool is_interactive) +{ + if (is_interactive && QMessageBox::question(m_game_list_frame, tr("Confirm Removal"), tr("Remove custom gamepad configuration?")) != QMessageBox::Yes) + { + return; + } + + std::set serials; + + for (const auto& game : (games.empty() ? m_game_list_frame->GetGameInfo() : games)) + { + if (game->has_custom_pad_config && !serials.count(game->info.serial)) + { + serials.emplace(game->info.serial); + } + } + + const u32 total = ::size32(serials); + + if (total == 0) + { + QMessageBox::information(m_game_list_frame, tr("Custom Gamepad Configuration Batch Removal"), tr("No files found"), QMessageBox::Ok); + return; + } + + progress_dialog* pdlg = new progress_dialog(tr("Custom Gamepad Configuration Batch Removal"), tr("Removing all custom gamepad configurations"), tr("Cancel"), 0, total, false, m_game_list_frame); + pdlg->setAutoClose(false); + pdlg->setAutoReset(false); + pdlg->open(); + + BatchActionBySerials(pdlg, serials, tr("%0/%1 custom gamepad configurations cleared"), [this](const std::string& serial) + { + return !serial.empty() && Emu.IsStopped(true) && RemoveCustomPadConfiguration(serial); + }, + [](u32 removed, u32 total) + { + game_list_log.notice("Custom Gamepad Configuration Batch Removal was canceled. %d/%d custom gamepad configurations cleared", removed, total); + }, nullptr, true); +} + +void game_list_actions::BatchRemoveShaderCaches(const std::vector& games, bool is_interactive) +{ + if (!ValidateBatchRemoval("shader cache", is_interactive)) + { + return; + } + + std::set serials; + + if (games.empty()) + { + serials.emplace("vsh.self"); + } + + for (const auto& game : (games.empty() ? m_game_list_frame->GetGameInfo() : games)) + { + serials.emplace(game->info.serial); + } + + const u32 total = ::size32(serials); + + if (total == 0) + { + QMessageBox::information(m_game_list_frame, tr("Shader Cache Batch Removal"), tr("No files found"), QMessageBox::Ok); + return; + } + + progress_dialog* pdlg = new progress_dialog(tr("Shader Cache Batch Removal"), tr("Removing all shader caches"), tr("Cancel"), 0, total, false, m_game_list_frame); + pdlg->setAutoClose(false); + pdlg->setAutoReset(false); + pdlg->open(); + + BatchActionBySerials(pdlg, serials, tr("%0/%1 shader caches cleared"), [this](const std::string& serial) + { + return !serial.empty() && Emu.IsStopped(true) && RemoveShaderCache(serial); + }, + [](u32 removed, u32 total) + { + game_list_log.notice("Shader Cache Batch Removal was canceled. %d/%d caches cleared", removed, total); + }, nullptr, false); +} + void game_list_actions::BatchRemovePPUCaches(const std::vector& games, bool is_interactive) { if (!ValidateBatchRemoval("PPU cache", is_interactive)) @@ -1288,131 +1446,6 @@ void game_list_actions::BatchRemoveContentLists(const std::vector& ga }, false); } -void game_list_actions::BatchRemoveCustomConfigurations(const std::vector& games, bool is_interactive) -{ - if (is_interactive && QMessageBox::question(m_game_list_frame, tr("Confirm Removal"), tr("Remove custom configuration?")) != QMessageBox::Yes) - { - return; - } - - std::set serials; - - for (const auto& game : (games.empty() ? m_game_list_frame->GetGameInfo() : games)) - { - if (game->has_custom_config && !serials.count(game->info.serial)) - { - serials.emplace(game->info.serial); - } - } - - const u32 total = ::size32(serials); - - if (total == 0) - { - QMessageBox::information(m_game_list_frame, tr("Custom Configuration Batch Removal"), tr("No files found"), QMessageBox::Ok); - return; - } - - progress_dialog* pdlg = new progress_dialog(tr("Custom Configuration Batch Removal"), tr("Removing all custom configurations"), tr("Cancel"), 0, total, false, m_game_list_frame); - pdlg->setAutoClose(false); - pdlg->setAutoReset(false); - pdlg->open(); - - BatchActionBySerials(pdlg, serials, tr("%0/%1 custom configurations cleared"), - [this](const std::string& serial) - { - return !serial.empty() && Emu.IsStopped(true) && RemoveCustomConfiguration(serial); - }, - [](u32 removed, u32 total) - { - game_list_log.notice("Custom Configuration Batch Removal was canceled. %d/%d custom configurations cleared", removed, total); - }, nullptr, true); -} - -void game_list_actions::BatchRemoveCustomPadConfigurations(const std::vector& games, bool is_interactive) -{ - if (is_interactive && QMessageBox::question(m_game_list_frame, tr("Confirm Removal"), tr("Remove custom gamepad configuration?")) != QMessageBox::Yes) - { - return; - } - - std::set serials; - - for (const auto& game : (games.empty() ? m_game_list_frame->GetGameInfo() : games)) - { - if (game->has_custom_pad_config && !serials.count(game->info.serial)) - { - serials.emplace(game->info.serial); - } - } - - const u32 total = ::size32(serials); - - if (total == 0) - { - QMessageBox::information(m_game_list_frame, tr("Custom Gamepad Configuration Batch Removal"), tr("No files found"), QMessageBox::Ok); - return; - } - - progress_dialog* pdlg = new progress_dialog(tr("Custom Gamepad Configuration Batch Removal"), tr("Removing all custom gamepad configurations"), tr("Cancel"), 0, total, false, m_game_list_frame); - pdlg->setAutoClose(false); - pdlg->setAutoReset(false); - pdlg->open(); - - BatchActionBySerials(pdlg, serials, tr("%0/%1 custom gamepad configurations cleared"), - [this](const std::string& serial) - { - return !serial.empty() && Emu.IsStopped(true) && RemoveCustomPadConfiguration(serial); - }, - [](u32 removed, u32 total) - { - game_list_log.notice("Custom Gamepad Configuration Batch Removal was canceled. %d/%d custom gamepad configurations cleared", removed, total); - }, nullptr, true); -} - -void game_list_actions::BatchRemoveShaderCaches(const std::vector& games, bool is_interactive) -{ - if (!ValidateBatchRemoval("shader cache", is_interactive)) - { - return; - } - - std::set serials; - - if (games.empty()) - { - serials.emplace("vsh.self"); - } - - for (const auto& game : (games.empty() ? m_game_list_frame->GetGameInfo() : games)) - { - serials.emplace(game->info.serial); - } - - const u32 total = ::size32(serials); - - if (total == 0) - { - QMessageBox::information(m_game_list_frame, tr("Shader Cache Batch Removal"), tr("No files found"), QMessageBox::Ok); - return; - } - - progress_dialog* pdlg = new progress_dialog(tr("Shader Cache Batch Removal"), tr("Removing all shader caches"), tr("Cancel"), 0, total, false, m_game_list_frame); - pdlg->setAutoClose(false); - pdlg->setAutoReset(false); - pdlg->open(); - - BatchActionBySerials(pdlg, serials, tr("%0/%1 shader caches cleared"), - [this](const std::string& serial) - { - return !serial.empty() && Emu.IsStopped(true) && RemoveShaderCache(serial); - }, - [](u32 removed, u32 total) - { - game_list_log.notice("Shader Cache Batch Removal was canceled. %d/%d caches cleared", removed, total); - }, nullptr, false); -} - void game_list_actions::CreateShortcuts(const std::vector& games, const std::set& locations) { if (games.empty()) @@ -1501,7 +1534,7 @@ void game_list_actions::CreateShortcuts(const std::vector& games, con #endif } - if (!gameid_token_value.empty() && gui::utils::create_shortcut(gameinfo->info.name, gameinfo->info.serial, target_cli_args, gameinfo->info.name, gameinfo->info.icon_path, target_icon_dir, location)) + if (!gameid_token_value.empty() && gui::utils::create_shortcut(gameinfo->info.name, gameinfo->icon_in_archive ? gameinfo->info.path : "", gameinfo->info.serial, target_cli_args, gameinfo->info.name, gameinfo->info.icon_path, target_icon_dir, location)) { game_list_log.success("Created %s shortcut for %s", destination, QString::fromStdString(gameinfo->info.name).simplified()); } diff --git a/rpcs3/rpcs3qt/game_list_actions.h b/rpcs3/rpcs3qt/game_list_actions.h index 7c5f603337..ccb97ff89a 100644 --- a/rpcs3/rpcs3qt/game_list_actions.h +++ b/rpcs3/rpcs3qt/game_list_actions.h @@ -3,6 +3,7 @@ #include "gui_game_info.h" #include "shortcut_utils.h" +#include #include class progress_dialog; @@ -53,15 +54,7 @@ public: void ShowRemoveGameDialog(const std::vector& games); void ShowGameInfoDialog(const std::vector& games); - - void BatchCreateCPUCaches(const std::vector& games = {}, bool is_fast_compilation = false, bool is_interactive = false); - void BatchRemoveCustomConfigurations(const std::vector& games = {}, bool is_interactive = false); - void BatchRemoveCustomPadConfigurations(const std::vector& games = {}, bool is_interactive = false); - void BatchRemoveShaderCaches(const std::vector& games = {}, bool is_interactive = false); - void BatchRemovePPUCaches(const std::vector& games = {}, bool is_interactive = false); - void BatchRemoveSPUCaches(const std::vector& games = {}, bool is_interactive = false); - void BatchRemoveHDD1Caches(const std::vector& games = {}, bool is_interactive = false); - void BatchRemoveAllCaches(const std::vector& games = {}, bool is_interactive = false); + void ShowDiskUsageDialog(); // NOTES: // - SetContentList() MUST always be called to set the content's info to be removed by: @@ -69,8 +62,6 @@ public: // - BatchRemoveContentLists() // void SetContentList(u16 content_types, const content_info& content_info); - void BatchRemoveContentLists(const std::vector& games = {}, bool is_interactive = false); - void ClearContentList(bool refresh = false); content_info GetContentInfo(const std::vector& games); @@ -88,6 +79,16 @@ public: bool RemoveAllCaches(const std::string& serial, bool is_interactive = false); bool RemoveContentList(const std::string& serial, bool is_interactive = false); + void BatchCreateCPUCaches(const std::vector& games = {}, bool is_fast_compilation = false, bool is_interactive = false); + void BatchRemoveCustomConfigurations(const std::vector& games = {}, bool is_interactive = false); + void BatchRemoveCustomPadConfigurations(const std::vector& games = {}, bool is_interactive = false); + void BatchRemoveShaderCaches(const std::vector& games = {}, bool is_interactive = false); + void BatchRemovePPUCaches(const std::vector& games = {}, bool is_interactive = false); + void BatchRemoveSPUCaches(const std::vector& games = {}, bool is_interactive = false); + void BatchRemoveHDD1Caches(const std::vector& games = {}, bool is_interactive = false); + void BatchRemoveAllCaches(const std::vector& games = {}, bool is_interactive = false); + void BatchRemoveContentLists(const std::vector& games = {}, bool is_interactive = false); + static bool RemoveContentPath(const std::string& path, const std::string& desc); static u32 RemoveContentPathList(const std::set& path_list, const std::string& desc); static bool RemoveContentBySerial(const std::string& base_dir, const std::string& serial, const std::string& desc); @@ -95,6 +96,7 @@ public: private: game_list_frame* m_game_list_frame = nullptr; std::shared_ptr m_gui_settings; + QFuture m_disk_usage_future; // NOTE: // m_content_info is used by: diff --git a/rpcs3/rpcs3qt/game_list_base.cpp b/rpcs3/rpcs3qt/game_list_base.cpp index 51e43c95bf..edae4e51f6 100644 --- a/rpcs3/rpcs3qt/game_list_base.cpp +++ b/rpcs3/rpcs3qt/game_list_base.cpp @@ -1,4 +1,5 @@ #include "stdafx.h" +#include "qt_utils.h" #include "game_list_base.h" #include @@ -48,7 +49,7 @@ void game_list_base::IconLoadFunction(game_info game, qreal device_pixel_ratio, static std::unordered_set warn_once_list; static shared_mutex s_mtx; - if (game->icon.isNull() && (game->info.icon_path.empty() || !game->icon.load(QString::fromStdString(game->info.icon_path)))) + if (game->icon.isNull() && !gui::utils::load_icon(game->icon, game->info.icon_path, game->icon_in_archive ? game->info.path : "")) { if (game_list_log.warning) { diff --git a/rpcs3/rpcs3qt/game_list_context_menu.cpp b/rpcs3/rpcs3qt/game_list_context_menu.cpp index bb15bc80ec..720aac8596 100644 --- a/rpcs3/rpcs3qt/game_list_context_menu.cpp +++ b/rpcs3/rpcs3qt/game_list_context_menu.cpp @@ -303,13 +303,6 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info& QAction* remove_game = manage_game_menu->addAction(tr("&Remove %1").arg(gameinfo->localized_category)); remove_game->setEnabled(!is_current_running_game); - // Game info - QAction* game_info = manage_game_menu->addAction(tr("&Game Info")); - connect(game_info, &QAction::triggered, this, [this, gameinfo]() - { - m_game_list_actions->ShowGameInfoDialog({gameinfo}); - }); - // Custom Images menu QMenu* icon_menu = addMenu(tr("&Custom Images")); const std::array custom_icon_actions = @@ -457,8 +450,6 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info& icon_menu->setEnabled(false); } - addSeparator(); - // Open Folder menu QMenu* open_folder_menu = addMenu(tr("&Open Folder")); @@ -575,6 +566,22 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info& QAction* check_compat = addAction(tr("&Check Game Compatibility")); QAction* download_compat = addAction(tr("&Download Compatibility Database")); + addSeparator(); + + // Disk usage + QAction* disk_usage = addAction(tr("&Disk Usage")); + connect(disk_usage, &QAction::triggered, this, [this]() + { + m_game_list_actions->ShowDiskUsageDialog(); + }); + + // Game info + QAction* game_info = addAction(tr("&Game Info")); + connect(game_info, &QAction::triggered, this, [this, gameinfo]() + { + m_game_list_actions->ShowGameInfoDialog({gameinfo}); + }); + connect(boot, &QAction::triggered, m_game_list_frame, [this, gameinfo]() { sys_log.notice("Booting from gamelist per context menu..."); @@ -896,8 +903,25 @@ void game_list_context_menu::show_multi_selection_context_menu(const std::vector m_game_list_actions->ShowRemoveGameDialog(games); }); + addSeparator(); + + QAction* download_compat = addAction(tr("&Download Compatibility Database")); + connect(download_compat, &QAction::triggered, m_game_list_frame, [this] + { + ensure(m_game_list_frame->GetGameCompatibility())->RequestCompatibility(true); + }); + + addSeparator(); + + // Disk usage + QAction* disk_usage = addAction(tr("&Disk Usage")); + connect(disk_usage, &QAction::triggered, this, [this]() + { + m_game_list_actions->ShowDiskUsageDialog(); + }); + // Game info - QAction* game_info = manage_game_menu->addAction(tr("&Game Info")); + QAction* game_info = addAction(tr("&Game Info")); connect(game_info, &QAction::triggered, this, [this, games]() { m_game_list_actions->ShowGameInfoDialog(games); diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index f8b48e8508..ea7b87ff6d 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -15,6 +15,7 @@ #include "Emu/vfs_config.h" #include "Emu/system_utils.hpp" #include "Loader/PSF.h" +#include "Loader/ISO.h" #include "util/types.hpp" #include "Utilities/File.h" #include "util/sysinfo.hpp" @@ -522,13 +523,26 @@ void game_list_frame::OnParsingFinished() const auto add_game = [this, localized_title, localized_icon, localized_movie, dev_flash, cat_unknown_localized = localized.category.unknown.toStdString(), cat_unknown = cat::cat_unknown.toStdString(), game_icon_path, _hdd, play_hover_movies = m_play_hover_movies, show_custom_icons = m_show_custom_icons](const std::string& dir_or_elf) { + std::unique_ptr archive; + if (is_file_iso(dir_or_elf)) + { + archive = std::make_unique(dir_or_elf); + } + + const auto file_exists = [&archive](const std::string& path) + { + return archive ? archive->is_file(path) : fs::is_file(path); + }; + gui_game_info game{}; game.info.path = dir_or_elf; const Localized thread_localized; - const std::string sfo_dir = rpcs3::utils::get_sfo_dir_from_game_path(dir_or_elf); - const psf::registry psf = psf::load_object(sfo_dir + "/PARAM.SFO"); + const std::string sfo_dir = archive ? "PS3_GAME" : rpcs3::utils::get_sfo_dir_from_game_path(dir_or_elf); + const std::string sfo_path = sfo_dir + "/PARAM.SFO"; + + const psf::registry psf = archive ? archive->open_psf(sfo_path) : psf::load_object(sfo_path); const std::string_view title_id = psf::get_string(psf, "TITLE_ID", ""); if (title_id.empty()) @@ -596,7 +610,7 @@ void game_list_frame::OnParsingFinished() if (game.info.icon_path.empty()) { - if (std::string icon_path = sfo_dir + "/" + localized_icon; fs::is_file(icon_path)) + if (std::string icon_path = sfo_dir + "/" + localized_icon; file_exists(icon_path)) { game.info.icon_path = std::move(icon_path); } @@ -604,19 +618,20 @@ void game_list_frame::OnParsingFinished() { game.info.icon_path = sfo_dir + "/ICON0.PNG"; } + game.icon_in_archive = archive && archive->exists(game.info.icon_path); } - if (std::string movie_path = game_icon_path + game.info.serial + "/hover.gif"; fs::is_file(movie_path)) + if (std::string movie_path = game_icon_path + game.info.serial + "/hover.gif"; file_exists(movie_path)) { game.info.movie_path = std::move(movie_path); game.has_hover_gif = true; } - else if (std::string movie_path = sfo_dir + "/" + localized_movie; fs::is_file(movie_path)) + else if (std::string movie_path = sfo_dir + "/" + localized_movie; file_exists(movie_path)) { game.info.movie_path = std::move(movie_path); game.has_hover_pam = true; } - else if (std::string movie_path = sfo_dir + "/ICON1.PAM"; fs::is_file(movie_path)) + else if (std::string movie_path = sfo_dir + "/ICON1.PAM"; file_exists(movie_path)) { game.info.movie_path = std::move(movie_path); game.has_hover_pam = true; @@ -741,6 +756,10 @@ void game_list_frame::OnParsingFinished() add_disc_dir(entry.path, legit_paths); } + else if (is_file_iso(entry.path)) + { + push_path(entry.path, legit_paths); + } else { game_list_log.trace("Invalid game path registered: %s", entry.path); @@ -961,32 +980,31 @@ void game_list_frame::ShowContextMenu(const QPoint& pos) QPoint global_pos; std::vector games; - // NOTE: Currently, only m_game_list supports rows multi selection! - // - // TODO: Add support to rows multi selection to m_game_grid - if (m_is_list_layout) { global_pos = m_game_list->viewport()->mapToGlobal(pos); - const auto item_list = m_game_list->selectedItems(); - game_info gameinfo; - - for (const auto& item : item_list) + for (const QTableWidgetItem* item : m_game_list->selectedItems()) { - if (item->column() != static_cast(gui::game_list_columns::icon)) + if (!item || item->column() != static_cast(gui::game_list_columns::icon)) continue; - if (gameinfo = GetGameInfoFromItem(item); gameinfo) + if (game_info gameinfo = GetGameInfoFromItem(item)) games.push_back(gameinfo); } } - else if (game_list_grid_item* item = static_cast(m_game_grid->selected_item())) + else if (m_game_grid) { global_pos = m_game_grid->mapToGlobal(pos); - if (game_info gameinfo = item->game(); gameinfo) - games.push_back(gameinfo); + for (const flow_widget_item* selected_item : m_game_grid->selected_items()) + { + if (const game_list_grid_item* item = static_cast(selected_item)) + { + if (game_info gameinfo = item->game()) + games.push_back(gameinfo); + } + } } if (!games.empty()) @@ -1154,16 +1172,19 @@ bool game_list_frame::eventFilter(QObject *object, QEvent *event) if (object == m_game_list) { - QTableWidgetItem* item = m_game_list->item(m_game_list->currentRow(), static_cast(gui::game_list_columns::icon)); + const QTableWidgetItem* item = m_game_list->item(m_game_list->currentRow(), static_cast(gui::game_list_columns::icon)); - if (!item || !item->isSelected()) - return false; - - gameinfo = GetGameInfoFromItem(item); + if (item && item->isSelected()) + { + gameinfo = GetGameInfoFromItem(item); + } } - else if (game_list_grid_item* item = static_cast(m_game_grid->selected_item())) + else if (const auto items = m_game_grid->selected_items(); !items.empty()) { - gameinfo = item->game(); + if (const game_list_grid_item* item = static_cast(*items.begin())) + { + gameinfo = item->game(); + } } if (!gameinfo) @@ -1279,11 +1300,14 @@ std::set game_list_frame::CurrentSelectionPaths() } else if (m_game_grid) { - if (const game_list_grid_item* item = static_cast(m_game_grid->selected_item())) + for (const flow_widget_item* selected_item : m_game_grid->selected_items()) { - if (const game_info& game = item->game()) + if (const game_list_grid_item* item = static_cast(selected_item)) { - selection.insert(game->info.path + game->info.icon_path); + if (const game_info& game = item->game()) + { + selection.insert(game->info.path + game->info.icon_path); + } } } } diff --git a/rpcs3/rpcs3qt/game_list_grid.cpp b/rpcs3/rpcs3qt/game_list_grid.cpp index e60a832569..a33755ff54 100644 --- a/rpcs3/rpcs3qt/game_list_grid.cpp +++ b/rpcs3/rpcs3qt/game_list_grid.cpp @@ -4,6 +4,8 @@ #include "gui_settings.h" #include "qt_utils.h" +#include "Loader/ISO.h" + #include game_list_grid::game_list_grid() @@ -12,6 +14,8 @@ game_list_grid::game_list_grid() setObjectName("game_list_grid"); setContextMenuPolicy(Qt::CustomContextMenu); + set_multi_selection_enabled(true); + m_icon_ready_callback = [this](const game_info& game, const movie_item_base* item) { Q_EMIT IconReady(game, item); @@ -45,7 +49,7 @@ void game_list_grid::populate( { clear_list(); - game_list_grid_item* selected_item = nullptr; + std::set selected_items; blockSignals(true); @@ -108,11 +112,16 @@ void game_list_grid::populate( if (play_hover_movies && (game->has_hover_gif || game->has_hover_pam)) { item->set_video_path(game->info.movie_path); + + if (!fs::exists(game->info.movie_path) && is_file_iso(game->info.path)) + { + item->set_iso_path(game->info.path); + } } if (selected_item_ids.contains(game->info.path + game->info.icon_path)) { - selected_item = item; + selected_items.insert(item); } add_widget(item); @@ -125,7 +134,7 @@ void game_list_grid::populate( QApplication::processEvents(); - select_item(selected_item); + select_items(selected_items); } void game_list_grid::repaint_icons(std::vector& game_data, const QColor& icon_color, const QSize& icon_size, qreal device_pixel_ratio) diff --git a/rpcs3/rpcs3qt/game_list_table.cpp b/rpcs3/rpcs3qt/game_list_table.cpp index c3069ef67e..98b9ef344d 100644 --- a/rpcs3/rpcs3qt/game_list_table.cpp +++ b/rpcs3/rpcs3qt/game_list_table.cpp @@ -11,6 +11,8 @@ #include "Emu/vfs_config.h" #include "Utilities/StrUtil.h" +#include "Loader/ISO.h" + #include #include #include @@ -277,6 +279,13 @@ void game_list_table::populate( // Do not report size of apps inside /dev_flash (it does not make sense to do so) game->info.size_on_disk = 0; } + else if (is_file_iso(game->info.path)) + { + fs::stat_t iso_stat; + fs::get_stat(game->info.path, iso_stat); + + game->info.size_on_disk = iso_stat.size; + } else { game->info.size_on_disk = fs::get_dir_size(game->info.path, 1, cancel.get()); @@ -293,6 +302,11 @@ void game_list_table::populate( if (play_hover_movies && (game->has_hover_gif || game->has_hover_pam)) { icon_item->set_video_path(game->info.movie_path); + + if (!fs::exists(game->info.movie_path) && is_file_iso(game->info.path)) + { + icon_item->set_iso_path(game->info.path); + } } icon_item->setData(Qt::UserRole, index, true); diff --git a/rpcs3/rpcs3qt/gui_game_info.h b/rpcs3/rpcs3qt/gui_game_info.h index 984f8eb7bf..08483fa7fb 100644 --- a/rpcs3/rpcs3qt/gui_game_info.h +++ b/rpcs3/rpcs3qt/gui_game_info.h @@ -5,6 +5,7 @@ #include "Emu/GameInfo.h" +#include #include /* Having the icons associated with the game info simplifies logic internally */ @@ -20,6 +21,7 @@ struct gui_game_info bool has_custom_icon = false; bool has_hover_gif = false; bool has_hover_pam = false; + bool icon_in_archive = false; movie_item_base* item = nullptr; // Returns the visible version string in the game list diff --git a/rpcs3/rpcs3qt/log_frame.cpp b/rpcs3/rpcs3qt/log_frame.cpp index 1d2739608d..2080cdabcf 100644 --- a/rpcs3/rpcs3qt/log_frame.cpp +++ b/rpcs3/rpcs3qt/log_frame.cpp @@ -3,15 +3,12 @@ #include "gui_settings.h" #include "hex_validator.h" #include "memory_viewer_panel.h" +#include "syntax_highlighter.h" -#include "Emu/System.h" -#include "Emu/system_utils.hpp" #include "Utilities/lockless.h" #include "util/asm.hpp" -#include #include -#include #include #include #include @@ -21,8 +18,6 @@ #include #include -LOG_CHANNEL(sys_log, "SYS"); - extern fs::file g_tty; extern atomic_t g_tty_size; extern std::array, 16> g_tty_input; @@ -167,32 +162,15 @@ log_frame::log_frame(std::shared_ptr _gui_settings, QWidget* paren CreateAndConnectActions(); LoadSettings(); + if (m_ansi_tty) + { + m_tty_ansi_highlighter = new AnsiHighlighter(m_tty->document()); + } + m_timer = new QTimer(this); connect(m_timer, &QTimer::timeout, this, &log_frame::UpdateUI); } -void log_frame::show_disk_usage(const std::vector>& vfs_disk_usage, u64 cache_disk_usage) -{ - QString text; - u64 tot_data_size = 0; - - for (const auto& [dev, data_size] : vfs_disk_usage) - { - text += tr("\n %0: %1").arg(QString::fromStdString(dev)).arg(gui::utils::format_byte_size(data_size)); - tot_data_size += data_size; - } - - if (!text.isEmpty()) - { - text = tr("\n VFS disk usage: %0%1").arg(gui::utils::format_byte_size(tot_data_size)).arg(text); - } - - text += tr("\n Cache disk usage: %0").arg(gui::utils::format_byte_size(cache_disk_usage)); - - sys_log.success("%s", text); - QMessageBox::information(this, tr("Disk usage"), text); -} - void log_frame::SetLogLevel(logs::level lev) const { switch (lev) @@ -273,26 +251,6 @@ void log_frame::CreateAndConnectActions() m_tty->clear(); }); - m_show_disk_usage_act = new QAction(tr("Show Disk Usage"), this); - connect(m_show_disk_usage_act, &QAction::triggered, [this]() - { - if (m_disk_usage_future.isRunning()) - { - return; // Still running the last request - } - - m_disk_usage_future = QtConcurrent::run([this]() - { - const std::vector> vfs_disk_usage = rpcs3::utils::get_vfs_disk_usage(); - const u64 cache_disk_usage = rpcs3::utils::get_cache_disk_usage(); - - Emu.CallFromMainThread([this, vfs_disk_usage, cache_disk_usage]() - { - show_disk_usage(vfs_disk_usage, cache_disk_usage); - }, nullptr, false); - }); - }); - m_perform_goto_on_debugger = new QAction(tr("Go-To on Debugger"), this); connect(m_perform_goto_on_debugger, &QAction::triggered, [this]() { @@ -336,6 +294,16 @@ void log_frame::CreateAndConnectActions() { m_gui_settings->SetValue(gui::l_ansi_code, checked); m_ansi_tty = checked; + + if (m_ansi_tty && !m_tty_ansi_highlighter) + { + m_tty_ansi_highlighter = new AnsiHighlighter(m_tty->document()); + } + else if (!m_ansi_tty && m_tty_ansi_highlighter) + { + m_tty_ansi_highlighter->deleteLater(); + m_tty_ansi_highlighter = nullptr; + } }); m_tty_channel_acts = new QActionGroup(this); @@ -418,8 +386,6 @@ void log_frame::CreateAndConnectActions() QMenu* menu = m_log->createStandardContextMenu(); menu->addAction(m_clear_act); menu->addSeparator(); - menu->addAction(m_show_disk_usage_act); - menu->addSeparator(); menu->addAction(m_perform_goto_on_debugger); menu->addAction(m_perform_goto_thread_on_debugger); menu->addAction(m_perform_show_in_mem_viewer); @@ -649,8 +615,22 @@ void log_frame::UpdateUI() buf_line.assign(std::string_view(m_tty_buf).substr(str_index, m_tty_buf.find_first_of('\n', str_index) - str_index)); str_index += buf_line.size() + 1; - // Ignore control characters and greater/equal to 0x80 - buf_line.erase(std::remove_if(buf_line.begin(), buf_line.end(), [](s8 c) { return c <= 0x8 || c == 0x7F || (c >= 0xE && c <= 0x1F); }), buf_line.end()); + // If ANSI TTY is enabled, remove all control characters except for ESC (0x1B) for ANSI sequences + if (m_ansi_tty) + { + buf_line.erase(std::remove_if(buf_line.begin(), buf_line.end(), [](s8 c) + { + return c <= 0x8 || c == 0x7F || (c >= 0xE && c <= 0x1F && c != 0x1B); + }), buf_line.end()); + } + // Otherwise, remove all control characters to keep the output clean + else + { + buf_line.erase(std::remove_if(buf_line.begin(), buf_line.end(), [](s8 c) + { + return c <= 0x8 || c == 0x7F || (c >= 0xE && c <= 0x1F); + }), buf_line.end()); + } // save old scroll bar state QScrollBar* sb = m_tty->verticalScrollBar(); diff --git a/rpcs3/rpcs3qt/log_frame.h b/rpcs3/rpcs3qt/log_frame.h index 159fdd38aa..35bd3a7ab1 100644 --- a/rpcs3/rpcs3qt/log_frame.h +++ b/rpcs3/rpcs3qt/log_frame.h @@ -8,11 +8,12 @@ #include -#include #include #include #include +class AnsiHighlighter; + class gui_settings; class log_frame : public custom_dock_widget @@ -39,7 +40,6 @@ protected: private Q_SLOTS: void UpdateUI(); private: - void show_disk_usage(const std::vector>& vfs_disk_usage, u64 cache_disk_usage); void SetLogLevel(logs::level lev) const; void SetTTYLogging(bool val) const; @@ -50,7 +50,6 @@ private: std::unique_ptr m_find_dialog; QTimer* m_timer = nullptr; - QFuture m_disk_usage_future; std::vector m_color; QColor m_color_stack; @@ -72,10 +71,10 @@ private: QPlainTextEdit* m_tty = nullptr; QLineEdit* m_tty_input = nullptr; int m_tty_channel = -1; + AnsiHighlighter* m_tty_ansi_highlighter = nullptr; QAction* m_clear_act = nullptr; QAction* m_clear_tty_act = nullptr; - QAction* m_show_disk_usage_act = nullptr; QAction* m_perform_goto_on_debugger = nullptr; QAction* m_perform_goto_thread_on_debugger = nullptr; QAction* m_perform_show_in_mem_viewer = nullptr; diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 485f7e3f22..56e125ab11 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -78,6 +78,7 @@ #include "Loader/PUP.h" #include "Loader/TAR.h" #include "Loader/PSF.h" +#include "Loader/ISO.h" #include "Loader/mself.hpp" #include "Utilities/Thread.h" @@ -542,8 +543,16 @@ void main_window::Boot(const std::string& path, const std::string& title_id, boo } else { + std::string game_path = Emu.GetBoot(); + if (game_path.starts_with(iso_device::virtual_device_name)) + { + const auto device = fs::get_virtual_device(iso_device::virtual_device_name + "/"); + const auto iso_dev = ensure(dynamic_cast(device.get())); + game_path = iso_dev->get_loaded_iso(); + } + gui_log.success("Boot successful."); - AddRecentAction(gui::Recent_Game(QString::fromStdString(Emu.GetBoot()), QString::fromStdString(Emu.GetTitleAndTitleID())), false); + AddRecentAction(gui::Recent_Game(QString::fromStdString(game_path), QString::fromStdString(Emu.GetTitleAndTitleID())), false); } if (refresh_list) @@ -569,6 +578,7 @@ void main_window::BootElf() "SELF files (EBOOT.BIN *.self);;" "BOOT files (*BOOT.BIN);;" "BIN files (*.bin);;" + "ISO files (*.iso);;" "All executable files (*.SAVESTAT.zst *.SAVESTAT.gz *.SAVESTAT *.sprx *.SPRX *.self *.SELF *.bin *.BIN *.prx *.PRX *.elf *.ELF *.o *.O);;" "All files (*.*)"), Q_NULLPTR, QFileDialog::DontResolveSymlinks); @@ -690,6 +700,34 @@ void main_window::BootGame() Boot(dir_path.toStdString(), "", false, true); } +void main_window::BootISO() +{ + bool stopped = false; + + if (Emu.IsRunning()) + { + Emu.Pause(); + stopped = true; + } + + const QString path_last_game = m_gui_settings->GetValue(gui::fd_boot_game).toString(); + const QString path = QFileDialog::getOpenFileName(this, tr("Select ISO"), path_last_game, tr("ISO files (*.iso);;All files (*.*)")); + + if (path.isEmpty()) + { + if (stopped) + { + Emu.Resume(); + } + return; + } + + m_gui_settings->SetValue(gui::fd_boot_game, QFileInfo(path).dir().path()); + + gui_log.notice("Booting from BootISO..."); + Boot(path.toStdString(), "", true, true); +} + void main_window::BootVSH() { gui_log.notice("Booting from BootVSH..."); @@ -2469,6 +2507,7 @@ void main_window::CreateConnects() connect(ui->bootElfAct, &QAction::triggered, this, &main_window::BootElf); connect(ui->bootTestAct, &QAction::triggered, this, &main_window::BootTest); connect(ui->bootGameAct, &QAction::triggered, this, &main_window::BootGame); + connect(ui->bootIsoAct, &QAction::triggered, this, &main_window::BootISO); connect(ui->bootVSHAct, &QAction::triggered, this, &main_window::BootVSH); connect(ui->actionopen_rsx_capture, &QAction::triggered, this, [this](){ BootRsxCapture(); }); connect(ui->actionCreate_RSX_Capture, &QAction::triggered, this, []() @@ -2521,6 +2560,22 @@ void main_window::CreateConnects() AddGamesFromDirs(std::move(paths)); }); + connect(ui->addIsoGamesAct, &QAction::triggered, this, [this]() + { + if (!m_gui_settings->GetBootConfirmation(this)) + { + return; + } + + QStringList paths = QFileDialog::getOpenFileNames(this, tr("Select ISO files to add"), QString::fromStdString(fs::get_config_dir()), tr("ISO files (*.iso);;All files (*.*)")); + if (paths.isEmpty()) + { + return; + } + + AddGamesFromDirs(std::move(paths)); + }); + connect(ui->bootRecentMenu, &QMenu::aboutToShow, this, [this]() { // Enable/Disable Recent Games List @@ -3958,7 +4013,7 @@ main_window::drop_type main_window::IsValidFile(const QMimeData& md, QStringList const QString suffix_lo = info.suffix().toLower(); // check for directories first, only valid if all other paths led to directories until now. - if (info.isDir()) + if (info.isDir() || is_file_iso(path.toStdString())) { if (type != drop_type::drop_dir && type != drop_type::drop_error) { diff --git a/rpcs3/rpcs3qt/main_window.h b/rpcs3/rpcs3qt/main_window.h index ada8f01cad..588c6e6918 100644 --- a/rpcs3/rpcs3qt/main_window.h +++ b/rpcs3/rpcs3qt/main_window.h @@ -104,6 +104,7 @@ private Q_SLOTS: void BootElf(); void BootTest(); void BootGame(); + void BootISO(); void BootVSH(); void BootSavestate(); void BootRsxCapture(std::string path = ""); diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index bf75029d38..1f6a75c6f7 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -62,7 +62,6 @@ 10 - 50 false @@ -211,6 +210,7 @@ + @@ -218,6 +218,7 @@ + @@ -242,17 +243,17 @@ Configuration - - - Mice - - - - Devices + + + Mice + + + + @@ -296,9 +297,6 @@ Manage - - - Network Services @@ -307,7 +305,6 @@ - Portals and Gates @@ -317,6 +314,10 @@ + + + + @@ -1046,7 +1047,7 @@ - + :/Icons/rpcn.png:/Icons/rpcn.png @@ -1513,6 +1514,16 @@ Download Update + + + Add ISO Games + + + + + Boot ISO + + diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_settings_dialog.cpp index 36ece3b0ec..2940143a27 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_settings_dialog.cpp @@ -482,7 +482,7 @@ void pad_settings_dialog::InitButtons() if ((!is_connected || !m_remap_timer.isActive()) && ( is_connected != m_enable_buttons || (is_connected && ( - !capabilities.has_pressure_sensitivity != m_enable_pressure_intensity_button || + capabilities.has_pressure_intensity_button != m_enable_pressure_intensity_button || capabilities.has_rumble != m_enable_rumble || capabilities.has_battery_led != m_enable_battery_led || (capabilities.has_led || capabilities.has_mono_led) != m_enable_led || @@ -490,7 +490,7 @@ void pad_settings_dialog::InitButtons() { if (is_connected) { - m_enable_pressure_intensity_button = !capabilities.has_pressure_sensitivity; + m_enable_pressure_intensity_button = capabilities.has_pressure_intensity_button; m_enable_rumble = capabilities.has_rumble; m_enable_battery_led = capabilities.has_battery_led; m_enable_led = capabilities.has_led || capabilities.has_mono_led; diff --git a/rpcs3/rpcs3qt/qt_utils.cpp b/rpcs3/rpcs3qt/qt_utils.cpp index fc53dc542a..a957dba11c 100644 --- a/rpcs3/rpcs3qt/qt_utils.cpp +++ b/rpcs3/rpcs3qt/qt_utils.cpp @@ -12,6 +12,7 @@ #include "Emu/system_utils.hpp" #include "Utilities/File.h" +#include "Loader/ISO.h" #include LOG_CHANNEL(gui_log, "GUI"); @@ -392,27 +393,46 @@ namespace gui std::string icon_path = fs::get_config_dir() + "/Icons/game_icons/" + title_id + "/ICON0.PNG"; bool found_file = fs::is_file(icon_path); + std::unique_ptr pixmap; + if (!found_file) { // Get Icon for the gs_frame from path. this handles presumably all possible use cases - const QString qpath = QString::fromStdString(path); - const std::string path_list[] = { path, qpath.section("/", 0, -2, QString::SectionIncludeTrailingSep).toStdString(), - qpath.section("/", 0, -3, QString::SectionIncludeTrailingSep).toStdString() }; + std::vector path_list; - for (const std::string& pth : path_list) + const bool is_archive = is_file_iso(path); + if (is_archive) { - if (!fs::is_dir(pth)) + icon_path = "PS3_GAME/ICON0.PNG"; + + QPixmap px; + if (load_iso_icon(px, icon_path, path)) { - continue; + found_file = true; + pixmap = std::make_unique(std::move(px)); } + } + else + { + const QString qpath = QString::fromStdString(path); + path_list = { path, qpath.section("/", 0, -2, QString::SectionIncludeTrailingSep).toStdString(), + qpath.section("/", 0, -3, QString::SectionIncludeTrailingSep).toStdString() }; - const std::string sfo_dir = rpcs3::utils::get_sfo_dir_from_game_path(pth, title_id); - icon_path = sfo_dir + "/ICON0.PNG"; - found_file = fs::is_file(icon_path); - - if (found_file) + for (const std::string& pth : path_list) { - break; + if (!fs::is_dir(pth)) + { + continue; + } + + const std::string sfo_dir = rpcs3::utils::get_sfo_dir_from_game_path(pth, title_id); + icon_path = sfo_dir + "/ICON0.PNG"; + found_file = fs::is_file(icon_path); + + if (found_file) + { + break; + } } } } @@ -420,7 +440,7 @@ namespace gui if (found_file) { // load the image from path. It will most likely be a rectangle - const QImage source = QImage(QString::fromStdString(icon_path)); + const QImage source = pixmap ? pixmap->toImage() : QImage(QString::fromStdString(icon_path)); const int edge_max = std::max(source.width(), source.height()); // create a new transparent image with square size and same format as source (maybe handle other formats than RGB32 as well?) @@ -682,6 +702,36 @@ namespace gui return QString("%1 days ago %2").arg(current_date - exctrated_date).arg(date.toString(fmt_relative)); } + bool load_iso_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path) + { + if (icon_path.empty() || archive_path.empty()) return false; + if (!is_file_iso(archive_path)) return false; + + iso_archive archive(archive_path); + if (!archive.exists(icon_path)) return false; + + auto icon_file = archive.open(icon_path); + const auto icon_size = icon_file.size(); + if (icon_size == 0) return false; + + QByteArray data(icon_size, 0); + icon_file.read(data.data(), icon_size); + + return icon.loadFromData(data); + } + + bool load_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path) + { + if (icon_path.empty()) return false; + + if (archive_path.empty()) + { + return icon.load(QString::fromStdString(icon_path)); + } + + return load_iso_icon(icon, icon_path, archive_path); + } + QString format_timestamp(s64 time, const QString& fmt) { return format_datetime(datetime(time), fmt); diff --git a/rpcs3/rpcs3qt/qt_utils.h b/rpcs3/rpcs3qt/qt_utils.h index dc15385923..c08c5665a7 100644 --- a/rpcs3/rpcs3qt/qt_utils.h +++ b/rpcs3/rpcs3qt/qt_utils.h @@ -178,6 +178,12 @@ namespace gui return color_scheme() == Qt::ColorScheme::Dark; } + // Loads an icon from an (ISO) archive file. + bool load_iso_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path); + + // Loads an icon (optionally from an (ISO) archive file). + bool load_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path); + template void stop_future_watcher(QFutureWatcher& watcher, bool cancel, std::shared_ptr> cancel_flag = nullptr) { diff --git a/rpcs3/rpcs3qt/qt_video_source.cpp b/rpcs3/rpcs3qt/qt_video_source.cpp index 107e5ef850..d74d395c58 100644 --- a/rpcs3/rpcs3qt/qt_video_source.cpp +++ b/rpcs3/rpcs3qt/qt_video_source.cpp @@ -2,6 +2,8 @@ #include "Emu/System.h" #include "qt_video_source.h" +#include "Loader/ISO.h" + #include qt_video_source::qt_video_source() @@ -19,6 +21,11 @@ void qt_video_source::set_video_path(const std::string& video_path) m_video_path = QString::fromStdString(video_path); } +void qt_video_source::set_iso_path(const std::string& iso_path) +{ + m_iso_path = iso_path; +} + void qt_video_source::set_active(bool active) { if (m_active.exchange(active) == active) return; @@ -55,7 +62,7 @@ void qt_video_source::init_movie() return; } - if (!m_image_change_callback || m_video_path.isEmpty() || !QFile::exists(m_video_path)) + if (!m_image_change_callback || m_video_path.isEmpty() || (m_iso_path.empty() && !QFile::exists(m_video_path))) { m_video_path.clear(); return; @@ -65,8 +72,25 @@ void qt_video_source::init_movie() if (lower.endsWith(".gif")) { - m_movie = std::make_unique(m_video_path); - m_video_path.clear(); + if (m_iso_path.empty()) + { + m_movie = std::make_unique(m_video_path); + } + else + { + iso_archive archive(m_iso_path); + auto movie_file = archive.open(m_video_path.toStdString()); + const auto movie_size = movie_file.size(); + if (movie_size == 0) return; + + m_video_data = QByteArray(movie_size, 0); + movie_file.read(m_video_data.data(), movie_size); + + m_video_buffer = std::make_unique(&m_video_data); + m_video_buffer->open(QIODevice::ReadOnly); + m_movie = std::make_unique(m_video_buffer.get()); + + } if (!m_movie->isValid()) { @@ -85,14 +109,31 @@ void qt_video_source::init_movie() if (lower.endsWith(".pam")) { // We can't set PAM files as source of the video player, so we have to feed them as raw data. - QFile file(m_video_path); - if (!file.open(QFile::OpenModeFlag::ReadOnly)) + if (m_iso_path.empty()) { - return; + QFile file(m_video_path); + if (!file.open(QFile::OpenModeFlag::ReadOnly)) + { + return; + } + + // TODO: Decode the pam properly before pushing it to the player + m_video_data = file.readAll(); + } + else + { + iso_archive archive(m_iso_path); + auto movie_file = archive.open(m_video_path.toStdString()); + const auto movie_size = movie_file.size(); + if (movie_size == 0) + { + return; + } + + m_video_data = QByteArray(movie_size, 0); + movie_file.read(m_video_data.data(), movie_size); } - // TODO: Decode the pam properly before pushing it to the player - m_video_data = file.readAll(); if (m_video_data.isEmpty()) { return; @@ -148,6 +189,7 @@ void qt_video_source::stop_movie() if (m_movie) { m_movie->stop(); + m_movie.reset(); } m_video_sink.reset(); diff --git a/rpcs3/rpcs3qt/qt_video_source.h b/rpcs3/rpcs3qt/qt_video_source.h index a2710eea33..ce43d593d7 100644 --- a/rpcs3/rpcs3qt/qt_video_source.h +++ b/rpcs3/rpcs3qt/qt_video_source.h @@ -17,6 +17,7 @@ public: qt_video_source(); virtual ~qt_video_source(); + void set_iso_path(const std::string& iso_path); void set_video_path(const std::string& video_path) override; const QString& video_path() const { return m_video_path; } @@ -43,6 +44,7 @@ protected: atomic_t m_has_new = false; QString m_video_path; + std::string m_iso_path; // path of the source archive QByteArray m_video_data{}; QImage m_image{}; std::vector m_image_path; diff --git a/rpcs3/rpcs3qt/savestate_manager_dialog.cpp b/rpcs3/rpcs3qt/savestate_manager_dialog.cpp index ed664e1b65..2898c9b170 100644 --- a/rpcs3/rpcs3qt/savestate_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/savestate_manager_dialog.cpp @@ -7,6 +7,8 @@ #include "gui_settings.h" #include "progress_dialog.h" +#include "Loader/ISO.h" + #include #include #include @@ -208,7 +210,7 @@ savestate_manager_dialog::savestate_manager_dialog(std::shared_ptr m_savestate_table->create_header_actions(m_savestate_column_acts, [this](int col) { return m_gui_settings->GetSavestateListColVisibility(static_cast(col)); }, [this](int col, bool visible) { m_gui_settings->SetSavestateListColVisibility(static_cast(col), visible); }); - + m_game_table->create_header_actions(m_game_column_acts, [this](int col) { return m_gui_settings->GetSavestateGamelistColVisibility(static_cast(col)); }, [this](int col, bool visible) { m_gui_settings->SetSavestateGamelistColVisibility(static_cast(col), visible); }); @@ -416,9 +418,10 @@ void savestate_manager_dialog::ResizeGameIcons() { const qreal dpr = devicePixelRatioF(); const int savestate_index = item->data(GameUserRole::GameIndex).toInt(); - const std::string icon_path = m_savestate_db[savestate_index]->game_icon_path; + std::string game_icon_path = m_savestate_db[savestate_index]->game_icon_path; + std::string game_archive_path = m_savestate_db[savestate_index]->archive_path; - item->set_icon_load_func([this, icon_path, savestate_index, cancel = item->icon_loading_aborted(), dpr](int index) + item->set_icon_load_func([this, icon_path = std::move(game_icon_path), archive_path = std::move(game_archive_path), savestate_index, cancel = item->icon_loading_aborted(), dpr](int index) { if (cancel && cancel->load()) { @@ -432,7 +435,7 @@ void savestate_manager_dialog::ResizeGameIcons() if (!item->data(GameUserRole::GamePixmapLoaded).toBool()) { // Load game icon - if (!icon.load(QString::fromStdString(icon_path))) + if (!gui::utils::load_icon(icon, icon_path, archive_path)) { gui_log.warning("Could not load savestate game icon from path %s", icon_path); } @@ -617,6 +620,11 @@ void savestate_manager_dialog::StartSavestateLoadThreads() { game_data_ptr->game_name = gameinfo->info.name; game_data_ptr->game_icon_path = gameinfo->info.icon_path; + if (gameinfo->icon_in_archive) + { + game_data_ptr->archive_path = gameinfo->info.path; + } + break; } } diff --git a/rpcs3/rpcs3qt/savestate_manager_dialog.h b/rpcs3/rpcs3qt/savestate_manager_dialog.h index 28c3eddc8c..7a6a39a700 100644 --- a/rpcs3/rpcs3qt/savestate_manager_dialog.h +++ b/rpcs3/rpcs3qt/savestate_manager_dialog.h @@ -48,6 +48,7 @@ private: std::string game_name; std::string game_icon_path; std::string dir_path; + std::string archive_path; }; bool LoadSavestateFolderToDB(std::unique_ptr&& game_savestates); diff --git a/rpcs3/rpcs3qt/shortcut_utils.cpp b/rpcs3/rpcs3qt/shortcut_utils.cpp index 98f72d2872..00bec65f13 100644 --- a/rpcs3/rpcs3qt/shortcut_utils.cpp +++ b/rpcs3/rpcs3qt/shortcut_utils.cpp @@ -4,6 +4,7 @@ #include "Emu/VFS.h" #include "Utilities/File.h" #include "Utilities/StrUtil.h" +#include "Loader/ISO.h" #ifdef _WIN32 #include @@ -31,7 +32,7 @@ LOG_CHANNEL(sys_log, "SYS"); namespace gui::utils { - bool create_square_shortcut_icon_file(const std::string& src_icon_path, const std::string& target_icon_dir, std::string& target_icon_path, const std::string& extension, int size) + bool create_square_shortcut_icon_file(const std::string& path, const std::string& src_icon_path, const std::string& target_icon_dir, std::string& target_icon_path, const std::string& extension, int size) { if (src_icon_path.empty() || target_icon_dir.empty() || extension.empty()) { @@ -39,7 +40,15 @@ namespace gui::utils return false; } - QPixmap icon(QString::fromStdString(src_icon_path)); + const bool is_archive = is_file_iso(path); + + QPixmap icon; + if (!load_icon(icon, src_icon_path, is_archive ? path : "")) + { + sys_log.error("Failed to create shortcut. Failed to load %sicon: '%s'", is_archive ? "iso " : "", src_icon_path); + return false; + } + if (!gui::utils::create_square_pixmap(icon, size)) { sys_log.error("Failed to create shortcut. Icon empty."); @@ -67,6 +76,7 @@ namespace gui::utils } bool create_shortcut(const std::string& name, + const std::string& path, [[maybe_unused]] const std::string& serial, [[maybe_unused]] const std::string& target_cli_args, [[maybe_unused]] const std::string& description, @@ -189,7 +199,7 @@ namespace gui::utils if (!src_icon_path.empty() && !target_icon_dir.empty()) { std::string target_icon_path; - if (!create_square_shortcut_icon_file(src_icon_path, target_icon_dir, target_icon_path, "ico", 512)) + if (!create_square_shortcut_icon_file(path, src_icon_path, target_icon_dir, target_icon_path, "ico", 512)) return cleanup(false, ".ico creation failed"); const std::wstring w_icon_path = utf8_to_wchar(target_icon_path); @@ -301,7 +311,7 @@ namespace gui::utils if (!src_icon_path.empty()) { std::string target_icon_path = resources_dir; - if (!create_square_shortcut_icon_file(src_icon_path, resources_dir, target_icon_path, "icns", 512)) + if (!create_square_shortcut_icon_file(path, src_icon_path, resources_dir, target_icon_path, "icns", 512)) { // Error is logged in create_square_shortcut_icon_file return false; @@ -339,7 +349,7 @@ namespace gui::utils if (!src_icon_path.empty() && !target_icon_dir.empty()) { std::string target_icon_path; - if (!create_square_shortcut_icon_file(src_icon_path, target_icon_dir, target_icon_path, "png", 512)) + if (!create_square_shortcut_icon_file(path, src_icon_path, target_icon_dir, target_icon_path, "png", 512)) { // Error is logged in create_square_shortcut_icon_file return false; diff --git a/rpcs3/rpcs3qt/shortcut_utils.h b/rpcs3/rpcs3qt/shortcut_utils.h index 1b47182cbb..11208a2bf0 100644 --- a/rpcs3/rpcs3qt/shortcut_utils.h +++ b/rpcs3/rpcs3qt/shortcut_utils.h @@ -12,6 +12,7 @@ namespace gui::utils }; bool create_shortcut(const std::string& name, + const std::string& path, const std::string& serial, const std::string& target_cli_args, const std::string& description, diff --git a/rpcs3/rpcs3qt/syntax_highlighter.cpp b/rpcs3/rpcs3qt/syntax_highlighter.cpp index 412271eb6a..2fd43033d5 100644 --- a/rpcs3/rpcs3qt/syntax_highlighter.cpp +++ b/rpcs3/rpcs3qt/syntax_highlighter.cpp @@ -1,11 +1,11 @@ #include "syntax_highlighter.h" #include "qt_utils.h" -Highlighter::Highlighter(QTextDocument *parent) : QSyntaxHighlighter(parent) +Highlighter::Highlighter(QTextDocument* parent) : QSyntaxHighlighter(parent) { } -void Highlighter::addRule(const QString &pattern, const QBrush &brush) +void Highlighter::addRule(const QString& pattern, const QBrush& brush) { HighlightingRule rule; rule.pattern = QRegularExpression(pattern); @@ -13,14 +13,14 @@ void Highlighter::addRule(const QString &pattern, const QBrush &brush) highlightingRules.append(rule); } -void Highlighter::highlightBlock(const QString &text) +void Highlighter::highlightBlock(const QString& text) { for (const HighlightingRule& rule : highlightingRules) { QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text); while (matchIterator.hasNext()) { - QRegularExpressionMatch match = matchIterator.next(); + const QRegularExpressionMatch match = matchIterator.next(); setFormat(match.capturedStart(), match.capturedLength(), rule.format); } } @@ -67,7 +67,7 @@ LogHighlighter::LogHighlighter(QTextDocument* parent) : Highlighter(parent) addRule("^·T.*$", gui::utils::get_label_color("log_level_trace", color, color)); } -AsmHighlighter::AsmHighlighter(QTextDocument *parent) : Highlighter(parent) +AsmHighlighter::AsmHighlighter(QTextDocument* parent) : Highlighter(parent) { addRule("^\\b[A-Z0-9]+\\b", Qt::darkBlue); // Instructions addRule("-?R\\d[^,;\\s]*", Qt::darkRed); // -R0.* @@ -78,7 +78,7 @@ AsmHighlighter::AsmHighlighter(QTextDocument *parent) : Highlighter(parent) addRule("#[^\\n]*", Qt::darkGreen); // Single line comment } -GlslHighlighter::GlslHighlighter(QTextDocument *parent) : Highlighter(parent) +GlslHighlighter::GlslHighlighter(QTextDocument* parent) : Highlighter(parent) { const QStringList keywordPatterns = QStringList() // Selection-Iteration-Jump Statements: @@ -183,3 +183,100 @@ GlslHighlighter::GlslHighlighter(QTextDocument *parent) : Highlighter(parent) commentStartExpression = QRegularExpression("/\\*"); commentEndExpression = QRegularExpression("\\*/"); } + +AnsiHighlighter::AnsiHighlighter(QTextDocument* parent) : Highlighter(parent) +{ + m_escape_format.setForeground(Qt::darkGray); + m_escape_format.setFontItalic(true); + + m_foreground_color = gui::utils::get_foreground_color(); +} + +void AnsiHighlighter::highlightBlock(const QString& text) +{ + QTextCharFormat current_format; + current_format.setForeground(m_foreground_color); + + int pos = 0; + auto it = ansi_re.globalMatch(text); + while (it.hasNext()) + { + const auto match = it.next(); + const int start = match.capturedStart(); + const int length = match.capturedLength(); + + // Apply current format to the chunk before this escape sequence + if (start > pos) + { + setFormat(pos, start - pos, current_format); + } + + // Highlight the escape sequence itself + setFormat(start, length, m_escape_format); + + // Parse SGR parameters and update currentFormat + const QRegularExpressionMatch pm = param_re.match(match.captured()); + if (pm.hasMatch()) + { + const QString params = pm.captured(1); + if (params.isEmpty()) + { + // empty or just \x1b[m = reset + current_format = QTextCharFormat(); + current_format.setForeground(m_foreground_color); + } + else + { + const QStringList codes = params.split(';', Qt::SkipEmptyParts); + for (const QString& c : codes) + { + bool ok = false; + const int code = c.toInt(&ok); + if (!ok) continue; + switch (code) + { + case 0: + current_format = QTextCharFormat(); + current_format.setForeground(m_foreground_color); + break; + case 1: + current_format.setFontWeight(QFont::Bold); + break; + case 3: + current_format.setFontItalic(true); + break; + case 4: + current_format.setFontUnderline(true); + break; + case 30: current_format.setForeground(Qt::black); break; + case 31: current_format.setForeground(Qt::red); break; + case 32: current_format.setForeground(Qt::darkGreen); break; + case 33: current_format.setForeground(Qt::darkYellow); break; + case 34: current_format.setForeground(Qt::darkBlue); break; + case 35: current_format.setForeground(Qt::darkMagenta); break; + case 36: current_format.setForeground(Qt::darkCyan); break; + case 37: current_format.setForeground(Qt::lightGray); break; + case 39: current_format.setForeground(m_foreground_color); break; + case 90: current_format.setForeground(Qt::darkGray); break; + case 91: current_format.setForeground(Qt::red); break; + case 92: current_format.setForeground(Qt::green); break; + case 93: current_format.setForeground(Qt::yellow); break; + case 94: current_format.setForeground(Qt::blue); break; + case 95: current_format.setForeground(Qt::magenta); break; + case 96: current_format.setForeground(Qt::cyan); break; + case 97: current_format.setForeground(Qt::white); break; + // Background and extended colors not yet handled + default: + break; + } + } + } + } + + pos = start + length; + } + + // Apply remaining format + if (pos < text.length()) + setFormat(pos, text.length() - pos, current_format); +} diff --git a/rpcs3/rpcs3qt/syntax_highlighter.h b/rpcs3/rpcs3qt/syntax_highlighter.h index 3e701ffb0e..3854059dd7 100644 --- a/rpcs3/rpcs3qt/syntax_highlighter.h +++ b/rpcs3/rpcs3qt/syntax_highlighter.h @@ -2,6 +2,7 @@ #include #include +#include // Inspired by https://doc.qt.io/qt-5/qtwidgets-richtext-syntaxhighlighter-example.html @@ -10,11 +11,11 @@ class Highlighter : public QSyntaxHighlighter Q_OBJECT public: - explicit Highlighter(QTextDocument *parent = nullptr); + explicit Highlighter(QTextDocument* parent = nullptr); protected: - void highlightBlock(const QString &text) override; - void addRule(const QString &pattern, const QBrush &brush); + void highlightBlock(const QString& text) override; + void addRule(const QString& pattern, const QBrush& brush); struct HighlightingRule { @@ -42,7 +43,7 @@ class AsmHighlighter : public Highlighter Q_OBJECT public: - explicit AsmHighlighter(QTextDocument *parent = nullptr); + explicit AsmHighlighter(QTextDocument* parent = nullptr); }; class GlslHighlighter : public Highlighter @@ -50,5 +51,22 @@ class GlslHighlighter : public Highlighter Q_OBJECT public: - explicit GlslHighlighter(QTextDocument *parent = nullptr); + explicit GlslHighlighter(QTextDocument* parent = nullptr); +}; + +class AnsiHighlighter : public Highlighter +{ + Q_OBJECT + +public: + explicit AnsiHighlighter(QTextDocument* parent = nullptr); + +protected: + const QRegularExpression ansi_re = QRegularExpression("\x1b\\[[0-9;]*m"); + const QRegularExpression param_re = QRegularExpression("\x1b\\[([0-9;]*)m"); + + QTextCharFormat m_escape_format; + QColor m_foreground_color; + + void highlightBlock(const QString& text) override; }; diff --git a/rpcs3/rpcs3qt/trophy_manager_dialog.cpp b/rpcs3/rpcs3qt/trophy_manager_dialog.cpp index d784f16f6e..51ef2d2e9f 100644 --- a/rpcs3/rpcs3qt/trophy_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/trophy_manager_dialog.cpp @@ -649,9 +649,9 @@ void trophy_manager_dialog::ResizeGameIcons() { const qreal dpr = devicePixelRatioF(); const int trophy_index = item->data(GameUserRole::GameIndex).toInt(); - const QString icon_path = QString::fromStdString(m_trophies_db[trophy_index]->path); + QString trophy_icon_path = QString::fromStdString(m_trophies_db[trophy_index]->path); - item->set_icon_load_func([this, icon_path, localized_icon, trophy_index, cancel = item->icon_loading_aborted(), dpr](int index) + item->set_icon_load_func([this, icon_path = std::move(trophy_icon_path), localized_icon, trophy_index, cancel = item->icon_loading_aborted(), dpr](int index) { if (cancel && cancel->load()) { diff --git a/rpcs3/rpcs3qt/welcome_dialog.cpp b/rpcs3/rpcs3qt/welcome_dialog.cpp index cbe2433bfd..e20415594d 100644 --- a/rpcs3/rpcs3qt/welcome_dialog.cpp +++ b/rpcs3/rpcs3qt/welcome_dialog.cpp @@ -83,12 +83,12 @@ welcome_dialog::welcome_dialog(std::shared_ptr gui_settings, bool { if (ui->create_desktop_shortcut->isChecked()) { - gui::utils::create_shortcut("RPCS3", "", "", "RPCS3", ":/rpcs3.svg", fs::get_temp_dir(), gui::utils::shortcut_location::desktop); + gui::utils::create_shortcut("RPCS3", "", "", "", "RPCS3", ":/rpcs3.svg", fs::get_temp_dir(), gui::utils::shortcut_location::desktop); } if (ui->create_applications_menu_shortcut->isChecked()) { - gui::utils::create_shortcut("RPCS3", "", "", "RPCS3", ":/rpcs3.svg", fs::get_temp_dir(), gui::utils::shortcut_location::applications); + gui::utils::create_shortcut("RPCS3", "", "", "", "RPCS3", ":/rpcs3.svg", fs::get_temp_dir(), gui::utils::shortcut_location::applications); } if (ui->use_dark_theme->isChecked() && ui->use_dark_theme->isEnabled()) // if checked and also on initial welcome dialog diff --git a/rpcs3/tests/packages.config b/rpcs3/tests/packages.config index a71561e644..011e648cec 100644 --- a/rpcs3/tests/packages.config +++ b/rpcs3/tests/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/rpcs3/tests/rpcs3_test.vcxproj b/rpcs3/tests/rpcs3_test.vcxproj index 2851f2faa6..867a6e651f 100644 --- a/rpcs3/tests/rpcs3_test.vcxproj +++ b/rpcs3/tests/rpcs3_test.vcxproj @@ -98,7 +98,7 @@ - + diff --git a/rpcs3/util/media_utils.cpp b/rpcs3/util/media_utils.cpp index 60a337e750..571eaa74fd 100644 --- a/rpcs3/util/media_utils.cpp +++ b/rpcs3/util/media_utils.cpp @@ -347,9 +347,22 @@ namespace utils { if (!codec) return false; - for (const AVSampleFormat* p = codec->sample_fmts; p && *p != AV_SAMPLE_FMT_NONE; p++) + const void* sample_formats = nullptr; + int num = 0; + + if (const int err = avcodec_get_supported_config(nullptr, codec, AVCodecConfig::AV_CODEC_CONFIG_SAMPLE_FORMAT, 0, &sample_formats, &num)) { - if (*p == sample_fmt) + media_log.error("check_sample_fmt: avcodec_get_supported_config error: %d='%s'", err, av_error_to_string(err)); + return false; + } + + if (!sample_formats) + return true; // All supported + + int i = 0; + for (const AVSampleFormat* fmt = static_cast(sample_formats); fmt && *fmt != AV_SAMPLE_FMT_NONE && i < num; fmt++, i++) + { + if (*fmt == sample_fmt) { return true; } @@ -360,18 +373,33 @@ namespace utils // just pick the highest supported samplerate static int select_sample_rate(const AVCodec* codec) { - if (!codec || !codec->supported_samplerates) - return 48000; + constexpr int default_sample_rate = 48000; - int best_samplerate = 0; - for (const int* samplerate = codec->supported_samplerates; samplerate && *samplerate != 0; samplerate++) + if (!codec) + return default_sample_rate; + + const void* sample_rates = nullptr; + int num = 0; + + if (const int err = avcodec_get_supported_config(nullptr, codec, AVCodecConfig::AV_CODEC_CONFIG_SAMPLE_RATE, 0, &sample_rates, &num)) { - if (!best_samplerate || abs(48000 - *samplerate) < abs(48000 - best_samplerate)) + media_log.error("select_sample_rate: avcodec_get_supported_config error: %d='%s'", err, av_error_to_string(err)); + return default_sample_rate; + } + + if (!sample_rates) + return default_sample_rate; + + int i = 0; + int best_sample_rate = 0; + for (const int* sample_rate = static_cast(sample_rates); sample_rate && *sample_rate != 0 && i < num; sample_rate++, i++) + { + if (!best_sample_rate || abs(default_sample_rate - *sample_rate) < abs(default_sample_rate - best_sample_rate)) { - best_samplerate = *samplerate; + best_sample_rate = *sample_rate; } } - return best_samplerate; + return best_sample_rate; } AVChannelLayout get_preferred_channel_layout(int channels) @@ -397,12 +425,25 @@ namespace utils { if (!codec) return nullptr; + const void* ch_layouts = nullptr; + int num = 0; + + if (const int err = avcodec_get_supported_config(nullptr, codec, AVCodecConfig::AV_CODEC_CONFIG_CHANNEL_LAYOUT, 0, &ch_layouts, &num)) + { + media_log.error("select_channel_layout: avcodec_get_supported_config error: %d='%s'", err, av_error_to_string(err)); + return nullptr; + } + + if (!ch_layouts) + return nullptr; + const AVChannelLayout preferred_ch_layout = get_preferred_channel_layout(channels); const AVChannelLayout* found_ch_layout = nullptr; - for (const AVChannelLayout* ch_layout = codec->ch_layouts; - ch_layout && memcmp(ch_layout, &empty_ch_layout, sizeof(AVChannelLayout)) != 0; - ch_layout++) + int i = 0; + for (const AVChannelLayout* ch_layout = static_cast(ch_layouts); + i < num && ch_layout && memcmp(ch_layout, &empty_ch_layout, sizeof(AVChannelLayout)) != 0; + ch_layout++, i++) { media_log.notice("select_channel_layout: listing channel layout '%s' with %d channels", channel_layout_name(*ch_layout), ch_layout->nb_channels);