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