Compare commits

...

40 Commits

Author SHA1 Message Date
PCSX2 Bot
748f232976 [ci skip] Qt: Update Base Translation. 2025-12-15 19:50:06 -05:00
chaoticgd
a33612cf7d Qt: Display when the hovered cheat will be applied in the UI
Some checks are pending
🐧 Linux Builds / AppImage (push) Waiting to run
🐧 Linux Builds / Flatpak (push) Waiting to run
🍎 MacOS Builds / Defaults (push) Waiting to run
🖥️ Windows Builds / Lint VS Project Files (push) Waiting to run
🖥️ Windows Builds / SSE4 (push) Blocked by required conditions
🖥️ Windows Builds / AVX2 (push) Blocked by required conditions
🖥️ Windows Builds / CMake (push) Waiting to run
2025-12-15 14:20:58 -05:00
chaoticgd
5c123f3183 Patch: Fix bug causing the UI to show the wrong place value 2025-12-15 14:20:58 -05:00
PCSX2 Bot
d30a7fb991 [ci skip] PAD: Update to latest controller database. 2025-12-15 19:37:04 +01:00
chaoticgd
d4f661c27c SaveState: Swap two OSD error messages that were the wrong way round 2025-12-15 10:40:43 -05:00
dependabot[bot]
6d05d0220d
[ci skip] Bump the ci-deps group with 4 updates (#13708)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 08:26:27 -05:00
PCSX2 Bot
7fab935c2d [ci skip] Qt: Update Base Translation. 2025-12-14 19:22:23 -05:00
chaoticgd
cd120c3cfd
Patch: Restore original behaviour of PPT_ONCE_ON_LOAD, add new PPT_ON_LOAD_OR_WHEN_ENABLED place option, and display when a patch is applied in the GUI (#13698)
Some checks failed
🐧 Linux Builds / AppImage (push) Waiting to run
🐧 Linux Builds / Flatpak (push) Waiting to run
🍎 MacOS Builds / Defaults (push) Waiting to run
🖥️ Windows Builds / Lint VS Project Files (push) Waiting to run
🖥️ Windows Builds / SSE4 (push) Blocked by required conditions
🖥️ Windows Builds / AVX2 (push) Blocked by required conditions
🖥️ Windows Builds / CMake (push) Waiting to run
🏭 Update Controller Database / update-controller-db (push) Has been cancelled
2025-12-14 09:09:53 -05:00
Stern
0180ec060b
FSUI/Qt: Add Center/Tile background modes and remove redundant Qt null checks (#13564)
Signed-off-by: SternXD <stern@sidestore.io>
Signed-off-by: SternXD <stern@sidestore.io
Co-authored-by: KamFretoZ <14798312+kamfretoz@users.noreply.github.com>
2025-12-14 09:08:34 -05:00
chaoticgd
cf4412ecbe SaveState: Fix error handling in SaveState_ZipToDisk
Some checks are pending
🐧 Linux Builds / AppImage (push) Waiting to run
🐧 Linux Builds / Flatpak (push) Waiting to run
🍎 MacOS Builds / Defaults (push) Waiting to run
🖥️ Windows Builds / Lint VS Project Files (push) Waiting to run
🖥️ Windows Builds / SSE4 (push) Blocked by required conditions
🖥️ Windows Builds / AVX2 (push) Blocked by required conditions
🖥️ Windows Builds / CMake (push) Waiting to run
2025-12-13 21:42:52 -05:00
chaoticgd
743b0b11d8 VMManager: Reword save state error messages 2025-12-13 21:42:52 -05:00
chaoticgd
e8c2cfa843 SaveState: Rework error handling when saving states 2025-12-13 21:42:52 -05:00
chaoticgd
764875ddbf Qt: Add setting to show state load errors using a dialog or OSD message 2025-12-13 21:42:52 -05:00
KamFretoZ
92c7eaa383 FSUI: Add Start Big Picture UI Option 2025-12-13 20:00:17 -05:00
KamFretoZ
4d2c1a82c9 Qt: Misc Shortcut Cleanups 2025-12-13 20:00:17 -05:00
KamFretoZ
eb50aaea35 Qt: Fixup statefile shortcut and icon name
Update ShortcutCreationDialog.cpp
2025-12-13 20:00:17 -05:00
chaoticgd
d69c71e058 Qt: Don't use wildcard disconnection for some settings combo boxes
Some checks are pending
🐧 Linux Builds / AppImage (push) Waiting to run
🐧 Linux Builds / Flatpak (push) Waiting to run
🍎 MacOS Builds / Defaults (push) Waiting to run
🖥️ Windows Builds / Lint VS Project Files (push) Waiting to run
🖥️ Windows Builds / SSE4 (push) Blocked by required conditions
🖥️ Windows Builds / AVX2 (push) Blocked by required conditions
🖥️ Windows Builds / CMake (push) Waiting to run
2025-12-13 14:55:48 +01:00
lightningterror
7cc8c7eee6 GS/DX12: Add debug log when end stencil is discarded. 2025-12-13 14:48:22 +01:00
lightningterror
dd96f2c296 GS/DX12: Preserve end stencil when doing fb copies.
When copying the fb we require the stencil data to be valid so let's preserve it.
2025-12-13 14:48:22 +01:00
JordanTheToaster
3cb2f3d2d9 Deps: Update libpng-apng patch to v1.6.53
Some checks are pending
🐧 Linux Builds / AppImage (push) Waiting to run
🐧 Linux Builds / Flatpak (push) Waiting to run
🍎 MacOS Builds / Defaults (push) Waiting to run
🖥️ Windows Builds / Lint VS Project Files (push) Waiting to run
🖥️ Windows Builds / SSE4 (push) Blocked by required conditions
🖥️ Windows Builds / AVX2 (push) Blocked by required conditions
🖥️ Windows Builds / CMake (push) Waiting to run
2025-12-12 13:31:35 -05:00
JordanTheToaster
1f9a9940e9 Deps: Update libpng to v1.6.53 2025-12-12 13:31:35 -05:00
Ty
8164f2b2db CDVD: Fix an out of bounds read exploit in MG SCMD handlers
Some checks are pending
🐧 Linux Builds / AppImage (push) Waiting to run
🐧 Linux Builds / Flatpak (push) Waiting to run
🍎 MacOS Builds / Defaults (push) Waiting to run
🖥️ Windows Builds / Lint VS Project Files (push) Waiting to run
🖥️ Windows Builds / SSE4 (push) Blocked by required conditions
🖥️ Windows Builds / AVX2 (push) Blocked by required conditions
🖥️ Windows Builds / CMake (push) Waiting to run
2025-12-12 11:39:53 -05:00
Triticum0
d0c54de330
[ci skip] GitHub: Update issue template program version. (#13691) 2025-12-12 16:41:46 +01:00
KamFretoZ
af5cd8d48e Qt/FSUI: Base Translation Update
Co-Authored-By: TellowKrinkle <3315070+TellowKrinkle@users.noreply.github.com>
2025-12-12 16:31:57 +01:00
KamFretoZ
a43f051852 Qt: Add Dedup Comment hint to toolbar buttons 2025-12-12 16:31:57 +01:00
KamFretoZ
0f8c5066e3 Qt: Fix untranslatable strings 2025-12-12 16:31:57 +01:00
KamFretoZ
1bf1f458d0 Qt/Shortcut: Fix missing parenthesis 2025-12-12 16:31:57 +01:00
KamFretoZ
5ef1993496 Qt: Remove unused String 2025-12-12 16:31:57 +01:00
KamFretoZ
80baa73578 Qt/FSUI: Unify VSync String 2025-12-12 16:31:57 +01:00
KamFretoZ
b46c85ee7f FSUI: Translation Strings Fixes 2025-12-12 16:31:57 +01:00
KamFretoZ
7f233ca620 Qt: Translation Fixes From Crowdin
Co-Authored-By: TellowKrinkle <3315070+TellowKrinkle@users.noreply.github.com>
2025-12-12 16:31:57 +01:00
KamFretoZ
47fe2344a5 Qt/FSUI: Few Save State String Fixes 2025-12-12 16:31:57 +01:00
KamFretoZ
4e763d6ff7 Qt/FSUI: Fix FIFA case spelling 2025-12-12 16:31:57 +01:00
KamFretoZ
e1cc994cca Qt: Unify Bilinear Dirty Upscale string 2025-12-12 16:31:57 +01:00
KamFretoZ
5b7f85e571 Qt: Unify Skip Draw String 2025-12-12 16:31:57 +01:00
KamFretoZ
7475cfb325 Qt: Clarify open directory menu 2025-12-12 16:31:57 +01:00
KamFretoZ
cee01a22e1 Qt/Host: Clarify download error message
This is used if it can't write the downloaded data
2025-12-12 16:31:57 +01:00
KamFretoZ
c6437bccad Qt/Debugger: Dont translate symbolSourceErrorMessage 2025-12-12 16:31:57 +01:00
KamFretoZ
7419311296 Qt: Fix DEV9 Host name header title 2025-12-12 16:31:57 +01:00
KamFretoZ
2807a06d76 Qt: Fix audio sync description confusion 2025-12-12 16:31:57 +01:00
73 changed files with 4154 additions and 3334 deletions

View File

@ -59,7 +59,7 @@ body:
attributes:
label: PCSX2 Revision
description: "Please ensure you are on the latest version before making an issue"
placeholder: "Example: v1.7.1337"
placeholder: "Example: v2.5.374"
validations:
required: true
- type: dropdown

View File

@ -76,7 +76,7 @@ body:
attributes:
label: PCSX2 Revision
description: "We only accept bug reports for the latest dev version. Please try upgrading before making an issue."
placeholder: "Example: v1.7.1337"
placeholder: "Example: v2.5.374"
validations:
required: true
- type: dropdown

View File

@ -17,7 +17,7 @@ jobs:
run: ./.github/workflows/scripts/common/update_base_translation.sh
- name: Create Pull Request
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725
with:
title: "Qt: Update Base Translation"
commit-message: "[ci skip] Qt: Update Base Translation."

View File

@ -19,7 +19,7 @@ jobs:
mv ./game_controller_db.txt ${{github.workspace}}/bin/resources/game_controller_db.txt
- name: Create Pull Request
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725
with:
title: "PAD: Update to latest controller database"
commit-message: "[ci skip] PAD: Update to latest controller database."

View File

@ -153,7 +153,7 @@ jobs:
mv "./${{ steps.artifact-metadata.outputs.artifact-name }}.flatpak" "$GITHUB_WORKSPACE"/ci-artifacts/
- name: Upload artifact
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
path: ci-artifacts

View File

@ -92,7 +92,7 @@ jobs:
run: echo "timestamp=$(date -u "+%Y-%m-%d-%H;%M;%S")" >> $GITHUB_OUTPUT
- name: ccache cache files
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: .ccache
key: ${{ inputs.os }} ${{ inputs.platform }} ${{ inputs.compiler }} ${{ inputs.detail }} ccache ${{ steps.ccache_cache_timestamp.outputs.timestamp }}
@ -114,7 +114,7 @@ jobs:
- name: Cache Dependencies
id: cache-deps
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/deps
key: ${{ inputs.os }} ${{ inputs.platform }} deps ${{ hashFiles('.github/workflows/scripts/linux/build-dependencies-qt.sh', '.github/workflows/scripts/common/*.patch') }}
@ -174,7 +174,7 @@ jobs:
- name: Upload artifact
if: inputs.buildAppImage == true
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
path: ci-artifacts

View File

@ -91,7 +91,7 @@ jobs:
- name: Cache Dependencies
id: cache-deps
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/deps
key: ${{ inputs.os }} deps ${{ hashFiles('.github/workflows/scripts/macos/*', '.github/workflows/scripts/common/*.patch') }}
@ -112,7 +112,7 @@ jobs:
run: echo "timestamp=$(date -u "+%Y-%m-%d-%H;%M;%S")" >> $GITHUB_OUTPUT
- name: Cache ccache cache
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: .ccache
key: ${{ inputs.os }} ccache ${{ steps.ccache_cache_timestamp.outputs.timestamp }}
@ -197,7 +197,7 @@ jobs:
cp "${{ steps.artifact-metadata.outputs.artifact-name }}.tar.xz" ci-artifacts/macOS.tar.xz
- name: Upload Artifact
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
path: "*.tar.xz"

View File

@ -168,7 +168,7 @@ jobs:
- name: Prepare Artifact Folder
run: mkdir ./ci-artifacts/
- uses: actions/download-artifact@v6
- uses: actions/download-artifact@v7
name: Download all Artifacts
with:
path: ./ci-artifacts/

View File

@ -23,7 +23,7 @@ FREETYPE=2.14.1
HARFBUZZ=12.2.0
LIBBACKTRACE=ad106d5fdd5d960bd33fae1c48a351af567fd075
LIBJPEGTURBO=3.1.2
LIBPNG=1.6.51
LIBPNG=1.6.53
LIBWEBP=1.6.0
NVENC=11.1.5.3
SDL=SDL3-3.2.26
@ -52,10 +52,10 @@ b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG
f63fc519f150465bd0bdafcdf3d0e9c23474f4c474171cd515ea1b3a72c081fb harfbuzz-$HARFBUZZ.tar.gz
fd6f417fe9e3a071cf1424a5152d926a34c4a3c5070745470be6cf12a404ed79 $LIBBACKTRACE.zip
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
a050a892d3b4a7bb010c3a95c7301e49656d72a64f1fc709a90b8aded192bed2 libpng-$LIBPNG.tar.xz
1d3fb8ccc2932d04aa3663e22ef5ef490244370f4e568d7850165068778d98d4 libpng-$LIBPNG.tar.xz
e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWEBP.tar.gz
dad488474a51a0b01d547cd2834893d6299328d2e30f479a3564088b5476bae2 $SDL.tar.gz
9c16ec5654be709f062a705d0c6f529193f1c2123fe7f102fda6733913689023 libpng-$LIBPNG-apng.patch.gz
452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c libpng-$LIBPNG-apng.patch.gz
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
2974b91062197e0527dffa3aadd8fe3bfa6681ae45f5ff9181bc0ca6479abd59 nv-codec-headers-$NVENC.tar.gz
c465aa56757e7746ac707f582b6e2d51546569a4a2488c1172fb543aa5fdfc2c vulkan-sdk-$VULKAN.tar.gz

View File

@ -14,21 +14,21 @@
"sources": [
{
"type": "archive",
"url": "https://downloads.sourceforge.net/project/libpng/libpng16/1.6.51/libpng-1.6.51.tar.xz",
"sha256": "a050a892d3b4a7bb010c3a95c7301e49656d72a64f1fc709a90b8aded192bed2"
"url": "https://downloads.sourceforge.net/project/libpng/libpng16/1.6.53/libpng-1.6.53.tar.xz",
"sha256": "1d3fb8ccc2932d04aa3663e22ef5ef490244370f4e568d7850165068778d98d4"
},
{
"type": "file",
"url": "https://download.sourceforge.net/libpng-apng/libpng-1.6.51-apng.patch.gz",
"dest-filename": "libpng-1.6.51-apng.patch.gz",
"sha256": "9c16ec5654be709f062a705d0c6f529193f1c2123fe7f102fda6733913689023"
"url": "https://download.sourceforge.net/libpng-apng/libpng-1.6.53-apng.patch.gz",
"dest-filename": "libpng-1.6.53-apng.patch.gz",
"sha256": "452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c"
},
{
"type": "shell",
"commands":
[
"gunzip -f libpng-1.6.51-apng.patch.gz",
"patch -p1 < \"libpng-1.6.51-apng.patch\""
"gunzip -f libpng-1.6.53-apng.patch.gz",
"patch -p1 < \"libpng-1.6.53-apng.patch\""
]
}
],

View File

@ -43,7 +43,7 @@ HARFBUZZ=12.2.0
SDL=SDL3-3.2.26
ZSTD=1.5.7
LZ4=1.10.0
LIBPNG=1.6.51
LIBPNG=1.6.53
LIBJPEGTURBO=3.1.2
LIBWEBP=1.6.0
FFMPEG=8.0
@ -83,9 +83,9 @@ f63fc519f150465bd0bdafcdf3d0e9c23474f4c474171cd515ea1b3a72c081fb harfbuzz-$HARF
dad488474a51a0b01d547cd2834893d6299328d2e30f479a3564088b5476bae2 $SDL.tar.gz
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
a050a892d3b4a7bb010c3a95c7301e49656d72a64f1fc709a90b8aded192bed2 libpng-$LIBPNG.tar.xz
1d3fb8ccc2932d04aa3663e22ef5ef490244370f4e568d7850165068778d98d4 libpng-$LIBPNG.tar.xz
e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWEBP.tar.gz
9c16ec5654be709f062a705d0c6f529193f1c2123fe7f102fda6733913689023 libpng-$LIBPNG-apng.patch.gz
452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c libpng-$LIBPNG-apng.patch.gz
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG.tar.xz
f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e v$MOLTENVK.tar.gz

View File

@ -25,7 +25,7 @@ HARFBUZZ=12.2.0
SDL=SDL3-3.2.26
ZSTD=1.5.7
LZ4=1.10.0
LIBPNG=1.6.51
LIBPNG=1.6.53
LIBJPEGTURBO=3.1.2
LIBWEBP=1.6.0
FFMPEG=8.0
@ -64,9 +64,9 @@ f63fc519f150465bd0bdafcdf3d0e9c23474f4c474171cd515ea1b3a72c081fb harfbuzz-$HARF
dad488474a51a0b01d547cd2834893d6299328d2e30f479a3564088b5476bae2 $SDL.tar.gz
eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3 zstd-$ZSTD.tar.gz
537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b lz4-$LZ4.tar.gz
a050a892d3b4a7bb010c3a95c7301e49656d72a64f1fc709a90b8aded192bed2 libpng-$LIBPNG.tar.xz
1d3fb8ccc2932d04aa3663e22ef5ef490244370f4e568d7850165068778d98d4 libpng-$LIBPNG.tar.xz
e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 libwebp-$LIBWEBP.tar.gz
9c16ec5654be709f062a705d0c6f529193f1c2123fe7f102fda6733913689023 libpng-$LIBPNG-apng.patch.gz
452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c libpng-$LIBPNG-apng.patch.gz
8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf libjpeg-turbo-$LIBJPEGTURBO.tar.gz
b2751fccb6cc4c77708113cd78b561059b6fa904b24162fa0be2d60273d27b8e ffmpeg-$FFMPEG.tar.xz
f415a09385030c6510a936155ce211f617c31506db5fbc563e804345f1ecf56e v$MOLTENVK.tar.gz

View File

@ -45,8 +45,8 @@ cd "%BUILDDIR%"
set FREETYPE=2.14.1
set HARFBUZZ=12.2.0
set LIBJPEGTURBO=3.1.2
set LIBPNG=1651
set LIBPNGLONG=1.6.51
set LIBPNG=1653
set LIBPNGLONG=1.6.53
set SDL=SDL3-3.2.26
set QT=6.10.1
set QTMINOR=6.10
@ -67,8 +67,8 @@ set SHADERC_SPIRVTOOLS=971a7b6e8d7740035bbff089bbbf9f42951ecfd5
call :downloadfile "freetype-%FREETYPE%.tar.gz" https://sourceforge.net/projects/freetype/files/freetype2/%FREETYPE%/freetype-%FREETYPE%.tar.gz/download 174d9e53402e1bf9ec7277e22ec199ba3e55a6be2c0740cb18c0ee9850fc8c34 || goto error
call :downloadfile "harfbuzz-%HARFBUZZ%.zip" https://github.com/harfbuzz/harfbuzz/archive/refs/tags/%HARFBUZZ%.zip 31490c781bacd2ce56862555b11c51c964977c39f14f51b817dfaecf0be089fe || goto error
call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lpng1651.zip 31c2c6505fc1bb613574fd12357684b4e0292650607416ef1e68e6e4e0c470c8 || goto error
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz 9c16ec5654be709f062a705d0c6f529193f1c2123fe7f102fda6733913689023 || goto error
call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lpng1653.zip 140566abc64bb2320cb35f1d154d1cb3eb7174a12234d33bfdffb446bdc0a1d2 || goto error
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz 452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c || goto error
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf || goto error
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 739356eef1192fff9d641c320a8f5ef4a10506b8927def4b9ceb764c7e947369 || goto error

View File

@ -43,8 +43,8 @@ cd "%BUILDDIR%"
set FREETYPE=2.14.1
set HARFBUZZ=12.2.0
set LIBJPEGTURBO=3.1.2
set LIBPNG=1651
set LIBPNGLONG=1.6.51
set LIBPNG=1653
set LIBPNGLONG=1.6.53
set SDL=SDL3-3.2.26
set QT=6.10.1
set QTMINOR=6.10
@ -65,8 +65,8 @@ set SHADERC_SPIRVTOOLS=971a7b6e8d7740035bbff089bbbf9f42951ecfd5
call :downloadfile "freetype-%FREETYPE%.tar.gz" https://sourceforge.net/projects/freetype/files/freetype2/%FREETYPE%/freetype-%FREETYPE%.tar.gz/download 174d9e53402e1bf9ec7277e22ec199ba3e55a6be2c0740cb18c0ee9850fc8c34 || goto error
call :downloadfile "harfbuzz-%HARFBUZZ%.zip" https://github.com/harfbuzz/harfbuzz/archive/refs/tags/%HARFBUZZ%.zip 31490c781bacd2ce56862555b11c51c964977c39f14f51b817dfaecf0be089fe || goto error
call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lpng1651.zip 31c2c6505fc1bb613574fd12357684b4e0292650607416ef1e68e6e4e0c470c8 || goto error
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz 9c16ec5654be709f062a705d0c6f529193f1c2123fe7f102fda6733913689023 || goto error
call :downloadfile "lpng%LIBPNG%.zip" https://download.sourceforge.net/libpng/lpng1653.zip 140566abc64bb2320cb35f1d154d1cb3eb7174a12234d33bfdffb446bdc0a1d2 || goto error
call :downloadfile "lpng%LIBPNG%-apng.patch.gz" https://download.sourceforge.net/libpng-apng/libpng-%LIBPNGLONG%-apng.patch.gz 452a1a290bd0cf18737fad0057dc17b7fdf10a73eda2d6d4f31ba04fda25ef2c || goto error
call :downloadfile "libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" "https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/%LIBJPEGTURBO%/libjpeg-turbo-%LIBJPEGTURBO%.tar.gz" 8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf || goto error
call :downloadfile "libwebp-%WEBP%.tar.gz" "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-%WEBP%.tar.gz" e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 739356eef1192fff9d641c320a8f5ef4a10506b8927def4b9ceb764c7e947369 || goto error

View File

@ -115,7 +115,7 @@ jobs:
- name: Cache Dependencies
id: cache-deps
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: deps
key: ${{ inputs.os }} ${{ inputs.platform }} deps ${{ hashFiles('.github/workflows/scripts/windows/build-dependencies.bat', '.github/workflows/scripts/common/*.patch') }}
@ -154,7 +154,7 @@ jobs:
cmake --build build --config Release --target unittests
- name: Upload artifact
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
path: |
@ -186,7 +186,7 @@ jobs:
}
- name: Upload artifact - with symbols
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}-symbols
path: |

View File

@ -78,7 +78,7 @@
03000000c82d00001230000000000000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001260000000000000,8BitDo Ultimate 2,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b17,paddle2:b16,paddle3:b2,paddle4:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001b30000000000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001c30000000000000,8BitDo Ultimate 2C,a:b0,b:b1,x:b3,y:b4,back:b10,guide:b12,start:b11,leftstick:b13,rightstick:b14,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b8,righttrigger:b9,paddle1:b2,paddle2:b5,platform:Windows,
03000000c82d00001c30000000000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001d30000000000000,8BitDo Ultimate 2C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,paddle1:b5,paddle2:b2,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001530000000000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
03000000c82d00001630000000000000,8BitDo Ultimate C,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,
@ -985,6 +985,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Mac OS X,
03000000242f00002d00000007010000,JYS Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
030000006d04000019c2000000000000,Logitech Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d04000019c2000000020000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d04000016c2000000020000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d04000016c2000000030000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d04000016c2000014040000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
@ -993,7 +994,6 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000006d04000019c2000005030000,Logitech F710,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d0400001fc2000000000000,Logitech F710,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,
030000006d04000018c2000000010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b0,y:b3,platform:Mac OS X,
030000006d04000019c2000000020000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3~,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000380700005032000000010000,Mad Catz PS3 Fightpad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000380700008433000000010000,Mad Catz PS3 Fightstick TE S Plus,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,
03000000380700005082000000010000,Mad Catz PS4 Fightpad Pro,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Mac OS X,
@ -1395,6 +1395,10 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000242e00008816000001010000,Hyperkin X91,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
03000000f00300008d03000011010000,HyperX Clutch,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
03000000830500006020000010010000,iBuffalo Super Famicom Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,
03000000d80400004bea000011010000,icedragon.io STAC Dance Pad,a:b0,b:b1,x:b2,y:b3,back:b4,platform:Linux,
03000000d80400004aea000011010000,icedragon.io STAC Dance Pad,a:b0,b:b1,x:b2,y:b3,back:b4,platform:Linux,
030000008a2e0000d910000011010000,icedragon.io STAC2 Dance Pad,a:b0,b:b1,x:b2,y:b3,back:b4,platform:Linux,
030000008a2e0000e910000011010000,icedragon.io STAC2 Dance Pad,a:b8,b:b9,x:b10,y:b11,back:b12,platform:Linux,
030000008f0e00001330000001010000,iCode Retro Adapter,b:b3,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b9,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b1,start:b7,x:b2,y:b0,platform:Linux,
050000006964726f69643a636f6e0000,idroidcon Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000b50700001503000010010000,Impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,
@ -1591,6 +1595,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
030000006d040000d2ca000011010000,Precision Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
03000000250900000017000010010000,PS/SS/N64 Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b5,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2~,righty:a3,start:b8,platform:Linux,
03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,
03000000120c0000160e000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,
030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,
030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,
@ -1701,7 +1706,6 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000952e00004e43000011010000,Scuf Envision,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Linux,
03000000a30c00002500000011010000,Sega Genesis Mini 3B Controller,a:b2,b:b1,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,righttrigger:b5,start:b9,platform:Linux,
03000000790000001100000011010000,Sega Saturn,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b4,start:b9,x:b0,y:b3,platform:Linux,
03000000790000002201000011010000,Sega Saturn,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux,
03000000b40400000a01000000010000,Sega Saturn,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Linux,
03000000632500002305000010010000,ShanWan Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,
03000000632500002605000010010000,ShanWan Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
@ -1858,4 +1862,5 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000120c0000100e000011010000,Zeroplus P4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
03000000120c0000101e000011010000,Zeroplus P4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
03000000120c0000182e000011010000,Zeroplus PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,
03000000790000002201000011010000,ZhiXu GuliKit D,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,

View File

@ -28,7 +28,7 @@
#endif
#if defined(_WIN32)
#include "RedtapeWindows.h"
#include "common/RedtapeWindows.h"
#include <io.h>
#include <malloc.h>
#include <pathcch.h>

View File

@ -1148,7 +1148,7 @@ bool BMPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
return false;
}
if (width > 65536 || height > 65536)
if (width >= 65536 || height >= 65536)
{
Console.Error("BMP dimensions too large: %ux%u", width, height);
return false;

View File

@ -272,7 +272,7 @@ if (NOT APPLE)
)
endif()
file(GLOB TS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Translations/*.ts)
file(GLOB TS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Translations/pcsx2-qt_*-*.ts)
target_precompile_headers(pcsx2-qt PRIVATE PrecompiledHeader.h)
set_source_files_properties(PrecompiledHeader.cpp PROPERTIES HEADER_FILE_ONLY TRUE)

View File

@ -220,6 +220,8 @@ void GameListWidget::initialize()
m_sort_model->setSourceModel(m_model);
m_ui.setupUi(this);
m_ui.stack->installEventFilter(this);
m_ui.stack->setAutoFillBackground(false);
for (u32 type = 0; type < static_cast<u32>(GameList::EntryType::Count); type++)
{
@ -353,6 +355,7 @@ void GameListWidget::setCustomBackground()
m_background_movie = nullptr;
}
// Get the path to the custom background
std::string path = Host::GetBaseStringSettingValue("UI", "GameListBackgroundPath");
if (!Path::IsAbsolute(path))
path = Path::Combine(EmuFolders::DataRoot, path);
@ -360,27 +363,26 @@ void GameListWidget::setCustomBackground()
// Only try to create background if path are valid
if (!path.empty() && FileSystem::FileExists(path.c_str()))
{
QMovie* new_movie;
QString img_path = QString::fromStdString(path);
if (img_path.endsWith(".png", Qt::CaseInsensitive))
// Use apng plugin
new_movie = new QMovie(img_path, "apng", this);
else
new_movie = new QMovie(img_path, QByteArray(), this);
if (new_movie->isValid())
m_background_movie = new_movie;
else
const QByteArray format = (img_path.endsWith(".png", Qt::CaseInsensitive)) ? QByteArray("apng") : QByteArray();
m_background_movie = new QMovie(img_path, format, this);
if (!m_background_movie->isValid())
{
Console.Warning("Failed to load background movie from: %s", path.c_str());
delete new_movie;
delete m_background_movie;
m_background_movie = nullptr;
}
}
// If there is no valid background then reset fallback to default UI state
if (!m_background_movie)
{
m_ui.stack->setPalette(QApplication::palette());
m_background_pixmap = QPixmap();
m_ui.stack->setAutoFillBackground(true);
m_table_view->viewport()->setAutoFillBackground(true);
m_list_view->viewport()->setAutoFillBackground(true);
m_ui.stack->update();
m_table_view->setAlternatingRowColors(true);
return;
}
@ -390,7 +392,7 @@ void GameListWidget::setCustomBackground()
const std::string ar_value = Host::GetBaseStringSettingValue("UI", "GameListBackgroundMode", InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[static_cast<u8>(QtUtils::ScalingMode::Fit)]);
for (u8 i = 0; i < static_cast<u8>(QtUtils::ScalingMode::MaxCount); i++)
{
if (!(InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[i] == nullptr))
if (InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[i] != nullptr)
{
if (ar_value == InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[i])
{
@ -405,8 +407,13 @@ void GameListWidget::setCustomBackground()
// Selected Custom background is valid, connect the signals and start animation in gamelist
connect(m_background_movie, &QMovie::frameChanged, this, &GameListWidget::processBackgroundFrames, Qt::UniqueConnection);
m_ui.stack->setAutoFillBackground(false);
m_table_view->viewport()->setAutoFillBackground(false);
m_list_view->viewport()->setAutoFillBackground(false);
updateCustomBackgroundState(true);
m_table_view->setAlternatingRowColors(false);
processBackgroundFrames();
}
void GameListWidget::updateCustomBackgroundState(const bool force_start)
@ -422,7 +429,7 @@ void GameListWidget::updateCustomBackgroundState(const bool force_start)
void GameListWidget::processBackgroundFrames()
{
if (m_background_movie && m_background_movie->isValid())
if (m_background_movie && m_background_movie->isValid() && isVisible())
{
const int widget_width = m_ui.stack->width();
const int widget_height = m_ui.stack->height();
@ -435,9 +442,8 @@ void GameListWidget::processBackgroundFrames()
QtUtils::resizeAndScalePixmap(&pm, widget_width, widget_height, dpr, m_background_scaling, m_background_opacity);
QPalette bg_palette(m_ui.stack->palette());
bg_palette.setBrush(QPalette::Base, pm);
m_ui.stack->setPalette(bg_palette);
m_background_pixmap = std::move(pm);
m_ui.stack->update();
}
}
@ -725,6 +731,7 @@ bool GameListWidget::event(QEvent* event)
if (event->type() == QEvent::DevicePixelRatioChange)
{
m_model->setDevicePixelRatio(devicePixelRatioF());
processBackgroundFrames();
QWidget::event(event);
return true;
}
@ -732,6 +739,25 @@ bool GameListWidget::event(QEvent* event)
return QWidget::event(event);
}
bool GameListWidget::eventFilter(QObject* watched, QEvent* event)
{
if (watched == m_ui.stack && event->type() == QEvent::Paint)
{
if (!m_background_pixmap.isNull())
{
QPainter painter(m_ui.stack);
const auto* paint_event = static_cast<QPaintEvent*>(event);
painter.save();
painter.setClipRect(paint_event->rect());
painter.drawTiledPixmap(m_ui.stack->rect(), m_background_pixmap);
painter.restore();
return true;
}
}
return QWidget::eventFilter(watched, event);
}
void GameListWidget::resizeTableViewColumnsToFit()
{
QtUtils::ResizeColumnsForTableView(m_table_view, {

View File

@ -10,7 +10,9 @@
#include "pcsx2/GameList.h"
#include <QtGui/QMovie>
#include <QtGui/QPixmap>
#include <QtWidgets/QListView>
#include <QtWidgets/QStackedWidget>
#include <QtWidgets/QTableView>
Q_DECLARE_METATYPE(const GameList::Entry*);
@ -101,6 +103,7 @@ protected:
void hideEvent(QHideEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
bool event(QEvent* event) override;
bool eventFilter(QObject* watched, QEvent* event) override;
private:
void loadTableHeaderState();
@ -123,6 +126,7 @@ private:
GameListRefreshThread* m_refresh_thread = nullptr;
QMovie* m_background_movie = nullptr;
QPixmap m_background_pixmap;
QtUtils::ScalingMode m_background_scaling = QtUtils::ScalingMode::Fit;
float m_background_opacity = 100.0f;
};

View File

@ -37,6 +37,7 @@
#include "pcsx2/PerformanceMetrics.h"
#include "pcsx2/Recording/InputRecording.h"
#include "pcsx2/Recording/InputRecordingControls.h"
#include "pcsx2/SaveState.h"
#include "pcsx2/SIO/Sio.h"
#include "pcsx2/GS/GSExtra.h"
@ -1192,6 +1193,12 @@ void MainWindow::cancelGameListRefresh()
m_game_list_widget->cancelRefresh();
}
void MainWindow::updateGameListBackground()
{
if (m_game_list_widget)
m_game_list_widget->setCustomBackground();
}
void MainWindow::reportInfo(const QString& title, const QString& message)
{
QMessageBox::information(this, title, message);
@ -1213,6 +1220,103 @@ void MainWindow::onStatusMessage(const QString& message)
m_ui.statusBar->showMessage(message);
}
void MainWindow::reportStateLoadError(const QString& message, std::optional<s32> slot, bool backup)
{
const bool prompt_on_error = Host::GetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", true);
if (!prompt_on_error)
{
SaveState_ReportLoadErrorOSD(message.toStdString(), slot, backup);
return;
}
QString title;
if (slot.has_value())
{
if (backup)
title = tr("Failed to Load State From Backup Slot %1").arg(*slot);
else
title = tr("Failed to Load State From Slot %1").arg(*slot);
}
else
{
title = tr("Failed to Load State");
}
VMLock lock(pauseAndLockVM());
QCheckBox* do_not_show_again = new QCheckBox(tr("Do not show again"));
QPointer<QMessageBox> message_box = new QMessageBox(this);
message_box->setWindowTitle(title);
message_box->setText(message);
message_box->setIcon(QMessageBox::Critical);
message_box->addButton(QMessageBox::Ok);
message_box->setDefaultButton(QMessageBox::Ok);
message_box->setCheckBox(do_not_show_again);
message_box->exec();
if (message_box.isNull())
return;
if (do_not_show_again->isChecked())
{
Host::SetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", false);
Host::CommitBaseSettingChanges();
if (m_settings_window)
{
InterfaceSettingsWidget* interface_settings = m_settings_window->getInterfaceSettingsWidget();
interface_settings->updatePromptOnStateLoadSaveFailureCheckbox(Qt::Unchecked);
}
}
delete message_box;
}
void MainWindow::reportStateSaveError(const QString& message, std::optional<s32> slot)
{
const bool prompt_on_error = Host::GetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", true);
if (!prompt_on_error)
{
SaveState_ReportSaveErrorOSD(message.toStdString(), slot);
return;
}
QString title;
if (slot.has_value())
title = tr("Failed to Save State To Slot %1").arg(*slot);
else
title = tr("Failed to Save State");
VMLock lock(pauseAndLockVM());
QCheckBox* do_not_show_again = new QCheckBox(tr("Do not show again"));
QPointer<QMessageBox> message_box = new QMessageBox(this);
message_box->setWindowTitle(title);
message_box->setText(message);
message_box->setIcon(QMessageBox::Critical);
message_box->addButton(QMessageBox::Ok);
message_box->setDefaultButton(QMessageBox::Ok);
message_box->setCheckBox(do_not_show_again);
message_box->exec();
if (message_box.isNull())
return;
if (do_not_show_again->isChecked())
{
Host::SetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", false);
Host::CommitBaseSettingChanges();
if (m_settings_window)
{
InterfaceSettingsWidget* interface_settings = m_settings_window->getInterfaceSettingsWidget();
interface_settings->updatePromptOnStateLoadSaveFailureCheckbox(Qt::Unchecked);
}
}
delete message_box;
}
void MainWindow::runOnUIThread(const std::function<void()>& func)
{
func();
@ -1734,8 +1838,8 @@ void MainWindow::onCreateGameShortcutTriggered()
const QString title = QString::fromStdString(entry->GetTitle());
const QString path = QString::fromStdString(entry->path);
VMLock lock(pauseAndLockVM());
ShortcutCreationDialog dlg(lock.getDialogParent(), title, path);
ShortcutCreationDialog dlg(this, title, path);
dlg.exec();
}
#endif
@ -1876,7 +1980,7 @@ void MainWindow::onInputRecPlayActionTriggered()
QFileDialog dialog(this);
dialog.setFileMode(QFileDialog::ExistingFile);
dialog.setWindowTitle("Select a File");
dialog.setWindowTitle(tr("Select a File"));
dialog.setNameFilter(tr("Input Recording Files (*.p2m2)"));
QStringList fileNames;
if (dialog.exec())
@ -3058,7 +3162,7 @@ void MainWindow::populateLoadStateMenu(QMenu* menu, const QString& filename, con
}
const u32 deleted = VMManager::DeleteSaveStates(serial.toUtf8().constData(), crc, true);
QMessageBox::information(this, tr("Delete Save States"), tr("%1 save states deleted.").arg(deleted));
QMessageBox::information(this, tr("Delete Save States"), tr("%n save states deleted.", "", deleted));
});
}
}

View File

@ -116,10 +116,13 @@ public Q_SLOTS:
void checkForUpdates(bool display_message, bool force_check);
void refreshGameList(bool invalidate_cache, bool popup_on_error);
void cancelGameListRefresh();
void updateGameListBackground();
void reportInfo(const QString& title, const QString& message);
void reportError(const QString& title, const QString& message);
bool confirmMessage(const QString& title, const QString& message);
void onStatusMessage(const QString& message);
void reportStateLoadError(const QString& message, std::optional<s32> slot, bool backup);
void reportStateSaveError(const QString& message, std::optional<s32> slot);
void runOnUIThread(const std::function<void()>& func);
void requestReset();

View File

@ -1047,7 +1047,7 @@
<iconset theme="camera-video"/>
</property>
<property name="text">
<string>&amp;Video Capture</string>
<string comment="In Toolbar">&amp;Video Capture</string>
</property>
</action>
<action name="actionEditCheats">

View File

@ -194,6 +194,9 @@ void EmuThread::stopFullscreenUI()
{
m_run_fullscreen_ui.store(false, std::memory_order_release);
emit onFullscreenUIStateChange(false);
// Resume and refresh background when FullscreenUI exits
QMetaObject::invokeMethod(g_main_window, "updateGameListBackground", Qt::QueuedConnection);
}
}
@ -299,7 +302,11 @@ void EmuThread::loadState(const QString& filename)
Error error;
if (!VMManager::LoadState(filename.toUtf8().constData(), &error))
Host::ReportErrorAsync(TRANSLATE_SV("QtHost", "Failed to Load State"), error.GetDescription());
{
QtHost::RunOnUIThread([message = QString::fromStdString(error.GetDescription())]() {
g_main_window->reportStateLoadError(message, std::nullopt, false);
});
}
}
void EmuThread::loadStateFromSlot(qint32 slot, bool load_backup)
@ -315,7 +322,11 @@ void EmuThread::loadStateFromSlot(qint32 slot, bool load_backup)
Error error;
if (!VMManager::LoadStateFromSlot(slot, load_backup, &error))
Host::ReportErrorAsync(TRANSLATE_SV("QtHost", "Failed to Load State"), error.GetDescription());
{
QtHost::RunOnUIThread([message = QString::fromStdString(error.GetDescription()), slot, load_backup]() {
g_main_window->reportStateLoadError(message, slot, load_backup);
});
}
}
void EmuThread::saveState(const QString& filename)
@ -329,11 +340,11 @@ void EmuThread::saveState(const QString& filename)
if (!VMManager::HasValidVM())
return;
if (!VMManager::SaveState(filename.toUtf8().constData()))
{
// this one is usually the result of a user-chosen path, so we can display a message box safely here
Console.Error("Failed to save state");
}
VMManager::SaveState(filename.toUtf8().constData(), true, false, [](const std::string& error) {
QtHost::RunOnUIThread([message = QString::fromStdString(error)]() {
g_main_window->reportStateSaveError(message, std::nullopt);
});
});
}
void EmuThread::saveStateToSlot(qint32 slot)
@ -347,7 +358,11 @@ void EmuThread::saveStateToSlot(qint32 slot)
if (!VMManager::HasValidVM())
return;
VMManager::SaveStateToSlot(slot);
VMManager::SaveStateToSlot(slot, true, [slot](const std::string& error) {
QtHost::RunOnUIThread([message = QString::fromStdString(error), slot]() {
g_main_window->reportStateSaveError(message, slot);
});
});
}
void EmuThread::run()
@ -1586,7 +1601,7 @@ bool QtHost::DownloadFile(QWidget* parent, const QString& title, std::string url
!FileSystem::WriteBinaryFile(path.c_str(), data.data(), data.size()))
{
QMessageBox::critical(parent, qApp->translate("EmuThread", "Error"),
qApp->translate("EmuThread", "Failed to write '%1'.").arg(QString::fromStdString(path)));
qApp->translate("EmuThread", "Failed to write downloaded data to file '%1'.").arg(QString::fromStdString(path)));
return false;
}

View File

@ -141,9 +141,16 @@ namespace QtUtils
void resizeAndScalePixmap(QPixmap* pm, const int expected_width, const int expected_height, const qreal dpr, const ScalingMode scaling_mode, const float opacity)
{
if (!pm || pm->isNull() || pm->width() <= 0 || pm->height() <= 0)
if (!pm || pm->width() <= 0 || pm->height() <= 0)
return;
if (dpr <= 0.0)
{
Console.ErrorFmt("resizeAndScalePixmap: Invalid device pixel ratio ({}) - pixmap will be null", dpr);
*pm = QPixmap();
return;
}
const int dpr_expected_width = qRound(expected_width * dpr);
const int dpr_expected_height = qRound(expected_height * dpr);
@ -196,8 +203,7 @@ namespace QtUtils
qRound(scaledSize.width()),
qRound(scaledSize.height()),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation
);
Qt::SmoothTransformation);
const QRectF scaledSrcRect(0, 0, pm->width(), pm->height());
@ -211,16 +217,7 @@ namespace QtUtils
}
case ScalingMode::Stretch:
{
*pm = pm->scaled(
dpr_expected_width,
dpr_expected_height,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation
);
const QRectF scaledSrcRect(0, 0, pm->width(), pm->height());
painter.drawPixmap(painterRect, *pm, scaledSrcRect);
painter.drawPixmap(painterRect, *pm, srcRect);
break;
}
case ScalingMode::Center:
@ -229,7 +226,6 @@ namespace QtUtils
const qreal pmHeight = pm->height() / dpr;
QRectF destRect(0, 0, pmWidth, pmHeight);
destRect.moveCenter(painterRect.center());
painter.drawPixmap(destRect, *pm, srcRect);
@ -243,13 +239,19 @@ namespace QtUtils
if (tileWidth <= 0 || tileHeight <= 0)
break;
if (pm->devicePixelRatio() == dpr)
{
QBrush tileBrush(*pm);
painter.fillRect(painterRect, tileBrush);
}
else
{
QPixmap tileSource = pm->scaled(tileWidth, tileHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
tileSource.setDevicePixelRatio(dpr);
QBrush tileBrush(tileSource);
tileBrush.setTextureImage(tileSource.toImage());
painter.fillRect(painterRect, tileBrush);
}
break;
}
default:
@ -285,12 +287,12 @@ namespace QtUtils
{
#if defined(_WIN32)
//: Windows action to show a file in Windows Explorer
return QCoreApplication::translate("FileOperations", "Show in Folder");
return QCoreApplication::translate("FileOperations", "Show in Explorer");
#elif defined(__APPLE__)
//: macOS action to show a file in Finder
return QCoreApplication::translate("FileOperations", "Show in Finder");
#else
//: Opens the system file manager to the directory containing a selected file
//: Linux/*NIX: Opens the system file manager to the directory containing a selected file
return QCoreApplication::translate("FileOperations", "Open Containing Directory");
#endif
}

View File

@ -1263,7 +1263,7 @@ namespace SettingWidgetBinder
static inline void BindWidgetToFileSetting(SettingsInterface* sif, QLineEdit* widget, QAbstractButton* browse_button,
QAbstractButton* open_button, QAbstractButton* reset_button, std::string section, std::string key, std::string default_value,
const char* filter, bool allow_pergame = false, bool use_relative = true)
const QString& filter, bool allow_pergame = false, bool use_relative = true)
{
using Accessor = SettingAccessor<QLineEdit>;

View File

@ -17,7 +17,7 @@
#include <QtCore/QDateTime>
#include <QtWidgets/QMessageBox>
const char* AUDIO_FILE_FILTER = QT_TRANSLATE_NOOP("MainWindow", "Audio Files (*.wav)");
const char* AchievementSettingsWidget::AUDIO_FILE_FILTER = QT_TRANSLATE_NOOP("AchievementSettingsWidget", "WAV Audio Files (*.wav)");
AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* settings_dialog, QWidget* parent)
: SettingsWidget(settings_dialog, parent)
@ -44,9 +44,9 @@ AchievementSettingsWidget::AchievementSettingsWidget(SettingsWindow* settings_di
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.achievementNotificationsDuration, "Achievements", "NotificationsDuration", Pcsx2Config::AchievementsOptions::DEFAULT_NOTIFICATION_DURATION);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.leaderboardNotificationsDuration, "Achievements", "LeaderboardsDuration", Pcsx2Config::AchievementsOptions::DEFAULT_LEADERBOARD_DURATION);
SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.notificationSoundPath, m_ui.notificationSoundBrowse, m_ui.notificationSoundOpen, m_ui.notificationSoundReset, "Achievements", "InfoSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_INFO_SOUND_NAME), AUDIO_FILE_FILTER, true, false);
SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.unlockSoundPath, m_ui.unlockSoundBrowse, m_ui.unlockSoundOpen, m_ui.unlockSoundReset, "Achievements", "UnlockSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_UNLOCK_SOUND_NAME), AUDIO_FILE_FILTER, true, false);
SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.lbSoundPath, m_ui.lbSoundBrowse, m_ui.lbSoundOpen, m_ui.lbSoundReset, "Achievements", "LBSubmitSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_LBSUBMIT_SOUND_NAME), AUDIO_FILE_FILTER, true, false);
SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.notificationSoundPath, m_ui.notificationSoundBrowse, m_ui.notificationSoundOpen, m_ui.notificationSoundReset, "Achievements", "InfoSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_INFO_SOUND_NAME), qApp->translate("AchievementSettingsWidget", AUDIO_FILE_FILTER), true, false);
SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.unlockSoundPath, m_ui.unlockSoundBrowse, m_ui.unlockSoundOpen, m_ui.unlockSoundReset, "Achievements", "UnlockSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_UNLOCK_SOUND_NAME), qApp->translate("AchievementSettingsWidget", AUDIO_FILE_FILTER), true, false);
SettingWidgetBinder::BindWidgetToFileSetting(sif, m_ui.lbSoundPath, m_ui.lbSoundBrowse, m_ui.lbSoundOpen, m_ui.lbSoundReset, "Achievements", "LBSubmitSoundName", Path::Combine(EmuFolders::Resources, EmuConfig.Achievements.DEFAULT_LBSUBMIT_SOUND_NAME), qApp->translate("AchievementSettingsWidget", AUDIO_FILE_FILTER), true, false);
dialog()->registerWidgetHelp(m_ui.enable, tr("Enable Achievements"), tr("Unchecked"), tr("When enabled and logged in, PCSX2 will scan for achievements on startup."));
dialog()->registerWidgetHelp(m_ui.hardcoreMode, tr("Enable Hardcore Mode"), tr("Unchecked"), tr("\"Challenge\" mode for achievements, including leaderboard tracking. Disables save state, cheats, and slowdown functions."));

View File

@ -26,6 +26,7 @@ private Q_SLOTS:
private:
void updateLoginState();
static const char* AUDIO_FILE_FILTER;
Ui::AchievementSettingsWidget m_ui;
};

View File

@ -115,7 +115,7 @@ AudioSettingsWidget::AudioSettingsWidget(SettingsWindow* settings_dialog, QWidge
dialog()->registerWidgetHelp(m_ui.expansionSettings, tr("Expansion Settings"), tr("N/A"),
tr("These settings fine-tune the behavior of the FreeSurround-based channel expander."));
dialog()->registerWidgetHelp(m_ui.syncMode, tr("Synchronization"), tr("TimeStretch (Recommended)"),
tr("When running outside of 100% speed, adjusts the tempo on audio instead of dropping frames. Produces much nicer fast-forward/slowdown audio."));
tr("When the emulation isn't running at 100% speed, adjusts the tempo of the audio which produces much nicer sound during fast-forward/slowdown."));
dialog()->registerWidgetHelp(m_ui.stretchSettings, tr("Stretch Settings"), tr("N/A"),
tr("These settings fine-tune the behavior of the SoundTouch audio time stretcher when running outside of 100% speed."));
dialog()->registerWidgetHelp(m_ui.resetStandardVolume, tr("Reset Standard Volume"), tr("N/A"),
@ -180,7 +180,7 @@ void AudioSettingsWidget::updateDriverNames()
const AudioBackend backend = getEffectiveBackend();
const std::vector<std::pair<std::string, std::string>> names = AudioStream::GetDriverNames(backend);
m_ui.driver->disconnect();
QObject::disconnect(m_ui.driver, &QComboBox::currentIndexChanged, nullptr, nullptr);
m_ui.driver->clear();
if (names.empty())
{
@ -208,7 +208,7 @@ void AudioSettingsWidget::updateDeviceNames()
const std::string current_device = dialog()->getEffectiveStringValue("SPU2/Output", "DeviceName", "");
const std::vector<AudioStream::DeviceInfo> devices = AudioStream::GetOutputDevices(backend, driver_name.c_str());
m_ui.outputDevice->disconnect();
QObject::disconnect(m_ui.outputDevice, &QComboBox::currentIndexChanged, nullptr, nullptr);
m_ui.outputDevice->clear();
m_output_device_latency = 0;

View File

@ -39,6 +39,8 @@ BIOSSettingsWidget::BIOSSettingsWidget(SettingsWindow* settings_dialog, QWidget*
connect(m_ui.refresh, &QPushButton::clicked, this, &BIOSSettingsWidget::refreshList);
connect(m_ui.fileList, &QTreeWidget::currentItemChanged, this, &BIOSSettingsWidget::listItemChanged);
connect(m_ui.fastBoot, &QCheckBox::checkStateChanged, this, &BIOSSettingsWidget::fastBootChanged);
fastBootChanged();
}
BIOSSettingsWidget::~BIOSSettingsWidget() = default;

View File

@ -30,7 +30,7 @@ DEV9DnsHostDialog::DEV9DnsHostDialog(std::vector<HostEntryUi> hosts, QWidget* pa
QStringList headers;
headers.push_back(tr("Selected"));
headers.push_back(tr("Name"));
headers.push_back(tr("Url"));
headers.push_back(tr("Hostname"));
headers.push_back(tr("Address"));
headers.push_back(tr("Enabled"));
m_ethHost_model->setHorizontalHeaderLabels(headers);

View File

@ -139,7 +139,7 @@ DEV9SettingsWidget::DEV9SettingsWidget(SettingsWindow* settings_dialog, QWidget*
QStringList headers;
headers.push_back(tr("Name"));
headers.push_back(tr("Url"));
headers.push_back(tr("Hostname"));
headers.push_back(tr("Address"));
headers.push_back(tr("Enabled"));
m_ethHost_model->setHorizontalHeaderLabels(headers);

View File

@ -83,7 +83,7 @@
<item>
<widget class="QLabel" name="symbolSourceErrorMessage">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>

View File

@ -40,6 +40,9 @@ GameCheatSettingsWidget::GameCheatSettingsWidget(SettingsWindow* settings_dialog
m_ui.cheatList->expandAll();
m_ui.cheatList->viewport()->installEventFilter(this);
m_ui.cheatList->viewport()->setMouseTracking(true);
SettingsInterface* sif = dialog()->getSettingsInterface();
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableCheats, "EmuCore", "EnableCheats", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.allCRCsCheckbox, "EmuCore", "ShowCheatsForAllCRCs", false);
@ -83,7 +86,7 @@ void GameCheatSettingsWidget::onCheatListItemDoubleClicked(const QModelIndex& in
return;
}
QVariant data = item->data(Qt::UserRole);
QVariant data = item->data(NAME_ROLE);
if (!data.isValid())
return;
@ -95,7 +98,7 @@ void GameCheatSettingsWidget::onCheatListItemDoubleClicked(const QModelIndex& in
void GameCheatSettingsWidget::onCheatListItemChanged(QStandardItem* item)
{
QVariant data = item->data(Qt::UserRole);
QVariant data = item->data(NAME_ROLE);
if (!data.isValid())
return;
@ -109,6 +112,31 @@ void GameCheatSettingsWidget::onCheatListItemChanged(QStandardItem* item)
setCheatEnabled(std::move(cheat_name), current_checked, true);
}
void GameCheatSettingsWidget::onCheatListItemHovered(const QModelIndex& index)
{
const QModelIndex source_index = m_model_proxy->mapToSource(index);
const QModelIndex sibling_index = source_index.siblingAtColumn(0);
QStandardItem* item = m_model->itemFromIndex(sibling_index);
if (!item)
{
// No item is selected.
m_ui.appliedLabel->clear();
return;
}
std::optional<Patch::patch_place_type> place;
bool ok;
int place_value = item->data(PLACE_ROLE).toInt(&ok);
if (ok)
{
// The patch commands in the group are all applied at the same time.
place = static_cast<Patch::patch_place_type>(place_value);
}
m_ui.appliedLabel->setText(tr("<strong>Applied:</strong> %1").arg(Patch::PlaceToString(place)));
}
void GameCheatSettingsWidget::onReloadClicked()
{
reloadList();
@ -136,6 +164,32 @@ void GameCheatSettingsWidget::disableAllCheats()
si->Save();
}
bool GameCheatSettingsWidget::eventFilter(QObject* watched, QEvent* event)
{
if (watched == m_ui.cheatList->viewport())
{
switch (event->type())
{
case QEvent::MouseMove:
{
QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
onCheatListItemHovered(m_ui.cheatList->indexAt(mouse_event->position().toPoint()));
return true;
}
case QEvent::Leave:
{
onCheatListItemHovered(QModelIndex());
return true;
}
default:
{
}
}
}
return SettingsWidget::eventFilter(watched, event);
}
void GameCheatSettingsWidget::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
@ -185,7 +239,7 @@ void GameCheatSettingsWidget::setStateRecursively(QStandardItem* parent, bool en
for (int i = 0; i < count; i++)
{
QStandardItem* item = parent ? parent->child(i, 0) : m_model->item(i, 0);
QVariant data = item->data(Qt::UserRole);
QVariant data = item->data(NAME_ROLE);
if (data.isValid())
{
if ((item->checkState() == Qt::Checked) != enabled)
@ -277,7 +331,9 @@ QList<QStandardItem*> GameCheatSettingsWidget::populateTreeViewRow(const Patch::
const std::string_view name_part = pi.GetNamePart();
nameItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemNeverHasChildren | Qt::ItemIsEnabled);
nameItem->setCheckState(enabled ? Qt::Checked : Qt::Unchecked);
nameItem->setData(QString::fromStdString(pi.name), Qt::UserRole);
nameItem->setData(QString::fromStdString(pi.name), NAME_ROLE);
if (pi.place.has_value())
nameItem->setData(static_cast<int>(*pi.place), PLACE_ROLE);
if (!name_part.empty())
nameItem->setText(QString::fromUtf8(name_part.data(), name_part.length()));

View File

@ -32,6 +32,7 @@ public:
~GameCheatSettingsWidget();
void disableAllCheats();
bool eventFilter(QObject* watched, QEvent* event) override;
protected:
void resizeEvent(QResizeEvent* event) override;
@ -39,6 +40,7 @@ protected:
private Q_SLOTS:
void onCheatListItemDoubleClicked(const QModelIndex& index);
void onCheatListItemChanged(QStandardItem* item);
void onCheatListItemHovered(const QModelIndex& index);
void onReloadClicked();
void updateListEnabled();
void reloadList();
@ -50,6 +52,12 @@ private:
void setStateForAll(bool enabled);
void setStateRecursively(QStandardItem* parent, bool enabled);
enum Roles
{
NAME_ROLE = Qt::UserRole,
PLACE_ROLE = Qt::UserRole + 1
};
Ui::GameCheatSettingsWidget m_ui;
QStandardItemModel* m_model = nullptr;
QSortFilterProxyModel* m_model_proxy = nullptr;

View File

@ -90,6 +90,13 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="appliedLabel">
<property name="text">
<string notr="true"/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">

View File

@ -44,7 +44,7 @@ GameFixSettingsWidget::GameFixSettingsWidget(SettingsWindow* settings_dialog, QW
dialog()->registerWidgetHelp(m_ui.EETimingHack, tr("EE Timing Hack"), tr("Unchecked"), tr("General-purpose timing hack. Known to affect following games: Digital Devil Saga, SSX."));
dialog()->registerWidgetHelp(m_ui.InstantDMAHack, tr("Instant DMA Hack"), tr("Unchecked"), tr("Good for cache emulation problems. Known to affect following games: Fire Pro Wrestling Z."));
dialog()->registerWidgetHelp(m_ui.DMABusyHack, tr("DMA Busy Hack"), tr("Unchecked"), tr("Known to affect following games: Mana Khemia 1, Metal Saga, Pilot Down Behind Enemy Lines."));
dialog()->registerWidgetHelp(m_ui.GIFFIFOHack, tr("Emulate GIF FIFO"), tr("Unchecked"), tr("Correct but slower. Known to affect the following games: Fifa Street 2."));
dialog()->registerWidgetHelp(m_ui.GIFFIFOHack, tr("Emulate GIF FIFO"), tr("Unchecked"), tr("Correct but slower. Known to affect the following games: FIFA Street 2."));
dialog()->registerWidgetHelp(m_ui.VIFFIFOHack, tr("Emulate VIF FIFO"), tr("Unchecked"), tr("Simulate VIF1 FIFO read ahead. Known to affect following games: Test Drive Unlimited, Transformers."));
dialog()->registerWidgetHelp(m_ui.VIF1StallHack, tr("Delay VIF1 Stalls"), tr("Unchecked"), tr("For SOCOM 2 HUD and Spy Hunter loading hang."));
dialog()->registerWidgetHelp(m_ui.VuAddSubHack, tr("VU Add Hack"), tr("Unchecked"), tr("For Tri-Ace Games: Star Ocean 3, Radiata Stories, Valkyrie Profile 2."));

View File

@ -15,19 +15,23 @@
#include <algorithm>
GamePatchDetailsWidget::GamePatchDetailsWidget(std::string name, const std::string& author,
const std::string& description, bool tristate, Qt::CheckState checkState, SettingsWindow* dialog, QWidget* parent)
GamePatchDetailsWidget::GamePatchDetailsWidget(const Patch::PatchInfo& info, bool tristate, Qt::CheckState checkState, SettingsWindow* dialog, QWidget* parent)
: QWidget(parent)
, m_dialog(dialog)
, m_name(name)
, m_name(info.name)
{
m_ui.setupUi(this);
m_ui.name->setText(QString::fromStdString(name));
const QString name = QString::fromStdString(info.name);
const QString author = !info.author.empty() ? QString::fromStdString(info.author) : tr("Unknown");
const QString place = QString::fromUtf8(PlaceToString(info.place));
const QString description = !info.description.empty() ? QString::fromStdString(info.description) : tr("No description provided.");
m_ui.name->setText(name);
m_ui.description->setText(
tr("<strong>Author: </strong>%1<br>%2")
.arg(author.empty() ? tr("Unknown") : QString::fromStdString(author))
.arg(description.empty() ? tr("No description provided.") : QString::fromStdString(description)));
tr("<strong>Author:</strong> %1<br><strong>Applied:</strong> %2<br>%3")
.arg(author)
.arg(place)
.arg(description));
pxAssert(dialog->getSettingsInterface());
m_ui.enabled->setTristate(tristate);
@ -178,7 +182,7 @@ void GamePatchSettingsWidget::reloadList()
}
GamePatchDetailsWidget* it =
new GamePatchDetailsWidget(std::move(pi.name), pi.author, pi.description, globally_toggleable_option, check_state, dialog(), container);
new GamePatchDetailsWidget(pi, globally_toggleable_option, check_state, dialog(), container);
layout->addWidget(it);
}
}

View File

@ -20,7 +20,7 @@ class GamePatchDetailsWidget : public QWidget
Q_OBJECT
public:
GamePatchDetailsWidget(std::string name, const std::string& author, const std::string& description, bool tristate, Qt::CheckState checkState,
GamePatchDetailsWidget(const Patch::PatchInfo& info, bool tristate, Qt::CheckState checkState,
SettingsWindow* dialog, QWidget* parent);
~GamePatchDetailsWidget();

View File

@ -15,7 +15,7 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<widget class="QGroupBox" name="screenshotGroupBox">
<property name="title">
<string>Screenshot Capture Setup</string>
</property>
@ -42,7 +42,7 @@
</property>
<item>
<property name="text">
<string>Window Resolution (Aspect Corrected)</string>
<string>Display Resolution (Aspect Corrected)</string>
</property>
</item>
<item>
@ -186,6 +186,9 @@
<property name="singleStep">
<number>1</number>
</property>
<property name="value">
<number>67</number>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
@ -273,6 +276,9 @@
<property name="singleStep">
<number>100</number>
</property>
<property name="value">
<number>420</number>
</property>
</widget>
</item>
<item row="3" column="0">
@ -325,7 +331,7 @@
<number>16</number>
</property>
<property name="value">
<number>240</number>
<number>480</number>
</property>
</widget>
</item>

View File

@ -472,7 +472,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
dialog()->registerWidgetHelp(m_display.interlacing, tr("Deinterlacing"), tr("Automatic (Default)"), tr("Determines the deinterlacing method to be used on the interlaced screen of the emulated console. Automatic should be able to correctly deinterlace most games, but if you see visibly shaky graphics, try one of the other options."));
dialog()->registerWidgetHelp(m_capture.screenshotSize, tr("Screenshot Resolution"), tr("Screen Resolution"),
dialog()->registerWidgetHelp(m_capture.screenshotSize, tr("Screenshot Resolution"), tr("Display Resolution"),
tr("Determines the resolution at which screenshots will be saved. Internal resolutions preserve more detail at the cost of "
"file size."));
@ -587,10 +587,10 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
dialog()->registerWidgetHelp(m_fixes.gpuTargetCLUTMode, tr("GPU Target CLUT"), tr("Disabled"),
tr("Tries to detect when a game is drawing its own color palette and then renders it on the GPU with special handling."));
dialog()->registerWidgetHelp(m_fixes.skipDrawStart, tr("Skipdraw Range Start"), tr("0"),
dialog()->registerWidgetHelp(m_fixes.skipDrawStart, tr("Skip Draw Range Start"), tr("0"),
tr("Completely skips drawing surfaces from the surface in the left box up to the surface specified in the box on the right."));
dialog()->registerWidgetHelp(m_fixes.skipDrawEnd, tr("Skipdraw Range End"), tr("0"),
dialog()->registerWidgetHelp(m_fixes.skipDrawEnd, tr("Skip Draw Range End"), tr("0"),
tr("Completely skips drawing surfaces from the surface in the left box up to the surface specified in the box on the right."));
dialog()->registerWidgetHelp(m_fixes.hwAutoFlush, tr("Auto Flush"), tr("Unchecked"),
@ -660,7 +660,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
//: Wild Arms: name of a game series. Leave as-is or use an official translation.
tr("Lowers the GS precision to avoid gaps between pixels when upscaling. Fixes the text on Wild Arms games."));
dialog()->registerWidgetHelp(m_upscaling.bilinearHack, tr("Bilinear Upscale"), tr("Unchecked"),
dialog()->registerWidgetHelp(m_upscaling.bilinearHack, tr("Bilinear Dirty Upscale"), tr("Unchecked"),
tr("Can smooth out textures due to be bilinear filtered when upscaling. E.g. Brave sun glare."));
dialog()->registerWidgetHelp(m_upscaling.mergeSprite, tr("Merge Sprite"), tr("Unchecked"),
@ -729,7 +729,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
tr("Shows the number of internal video frames displayed per second by the system."));
dialog()->registerWidgetHelp(m_osd.showVPS, tr("Show VPS"), tr("Unchecked"),
tr("Shows the number of V-syncs performed per second by the system."));
tr("Shows the number of Vsyncs performed per second by the system."));
dialog()->registerWidgetHelp(m_osd.showResolution, tr("Show Resolution"), tr("Unchecked"),
tr("Shows the internal resolution of the game."));
@ -765,10 +765,10 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* settings_dialog,
tr("Shows the current controller state of the system in the bottom-left corner of the display."));
dialog()->registerWidgetHelp(m_osd.showVideoCapture, tr("Show Video Capture Status"), tr("Checked"),
tr("Shows the status of the currently active video capture in the top-right corner of the display.."));
tr("Shows the status of the currently active video capture in the top-right corner of the display."));
dialog()->registerWidgetHelp(m_osd.showInputRec, tr("Show Input Recording Status"), tr("Checked"),
tr("Shows the status of the currently active input recording in the top-right corner of the display.."));
tr("Shows the status of the currently active input recording in the top-right corner of the display."));
dialog()->registerWidgetHelp(m_osd.showTextureReplacements, tr("Show Texture Replacement Status"), tr("Unchecked"),
tr("Shows the status of the number of dumped and loaded texture replacements in the top-right corner of the display."));
@ -981,7 +981,7 @@ void GraphicsSettingsWidget::onCaptureContainerChanged()
const std::string container(
dialog()->getEffectiveStringValue("EmuCore/GS", "CaptureContainer", Pcsx2Config::GSOptions::DEFAULT_CAPTURE_CONTAINER));
m_capture.videoCaptureCodec->disconnect();
QObject::disconnect(m_capture.videoCaptureCodec, &QComboBox::currentIndexChanged, nullptr, nullptr);
m_capture.videoCaptureCodec->clear();
//: This string refers to a default codec, whether it's an audio codec or a video codec.
m_capture.videoCaptureCodec->addItem(tr("Default"), QString());
@ -996,7 +996,7 @@ void GraphicsSettingsWidget::onCaptureContainerChanged()
dialog()->getSettingsInterface(), m_capture.videoCaptureCodec, "EmuCore/GS", "VideoCaptureCodec");
connect(m_capture.videoCaptureCodec, &QComboBox::currentIndexChanged, this, &GraphicsSettingsWidget::onCaptureCodecChanged);
m_capture.audioCaptureCodec->disconnect();
QObject::disconnect(m_capture.audioCaptureCodec, &QComboBox::currentIndexChanged, nullptr, nullptr);
m_capture.audioCaptureCodec->clear();
m_capture.audioCaptureCodec->addItem(tr("Default"), QString());
for (const auto& [format, name] : GSCapture::GetAudioCodecList(container.c_str()))
@ -1012,7 +1012,7 @@ void GraphicsSettingsWidget::onCaptureContainerChanged()
void GraphicsSettingsWidget::GraphicsSettingsWidget::onCaptureCodecChanged()
{
m_capture.videoCaptureFormat->disconnect();
QObject::disconnect(m_capture.videoCaptureFormat, &QComboBox::currentIndexChanged, nullptr, nullptr);
m_capture.videoCaptureFormat->clear();
//: This string refers to a default pixel format
m_capture.videoCaptureFormat->addItem(tr("Default"), "");

View File

@ -93,7 +93,7 @@
<item row="1" column="0">
<widget class="QLabel" name="nativeScalingLabel">
<property name="text">
<string>Native Scaling</string>
<string>Native Scaling:</string>
</property>
<property name="buddy">
<cstring>nativeScaling</cstring>

View File

@ -22,9 +22,6 @@
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="title">
<property name="text">
<string>Bindings for Controller0/ButtonCircle</string>
</property>
<property name="buddy">
<cstring>bindingList</cstring>
</property>

View File

@ -97,6 +97,7 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.confirmShutdown, "UI", "ConfirmShutdown", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnFocusLoss, "UI", "PauseOnFocusLoss", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnControllerDisconnection, "UI", "PauseOnControllerDisconnection", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.promptOnStateLoadSaveFailure, "UI", "PromptOnStateLoadSaveFailure", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.discordPresence, "EmuCore", "EnableDiscordPresence", false);
#ifdef __linux__ // Mouse locking is only supported on X11
@ -196,6 +197,8 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
"and unpauses when you switch back."));
dialog()->registerWidgetHelp(m_ui.pauseOnControllerDisconnection, tr("Pause On Controller Disconnection"),
tr("Unchecked"), tr("Pauses the emulator when a controller with bindings is disconnected."));
dialog()->registerWidgetHelp(m_ui.promptOnStateLoadSaveFailure, tr("Pause On State Load/Save Failure"),
tr("Checked"), tr("Display a modal dialog when a save state load/save operation fails."));
dialog()->registerWidgetHelp(m_ui.startFullscreen, tr("Start Fullscreen"), tr("Unchecked"),
tr("Automatically switches to fullscreen mode when a game is started."));
dialog()->registerWidgetHelp(m_ui.hideMouseCursor, tr("Hide Cursor In Fullscreen"), tr("Unchecked"),
@ -241,6 +244,12 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog
InterfaceSettingsWidget::~InterfaceSettingsWidget() = default;
void InterfaceSettingsWidget::updatePromptOnStateLoadSaveFailureCheckbox(Qt::CheckState state)
{
QSignalBlocker blocker(m_ui.promptOnStateLoadSaveFailure);
m_ui.promptOnStateLoadSaveFailure->setCheckState(state);
}
void InterfaceSettingsWidget::onRenderToSeparateWindowChanged()
{
m_ui.hideMainWindow->setEnabled(m_ui.renderToSeparateWindow->isChecked());
@ -261,12 +270,12 @@ void InterfaceSettingsWidget::populateLanguages()
void InterfaceSettingsWidget::onSetGameListBackgroundTriggered()
{
const QString path = QDir::toNativeSeparators(
QFileDialog::getOpenFileName(this, tr("Select Background Image"), QString(), IMAGE_FILE_FILTER));
QFileDialog::getOpenFileName(this, tr("Select Background Image"), QString(), qApp->translate("InterfaceSettingsWidget", IMAGE_FILE_FILTER)));
if (path.isEmpty())
return;
std::string relative_path = Path::MakeRelative(QDir::toNativeSeparators(path).toStdString(), EmuFolders::DataRoot);
std::string relative_path = Path::MakeRelative(path.toStdString(), EmuFolders::DataRoot);
Host::SetBaseStringSettingValue("UI", "GameListBackgroundPath", relative_path.c_str());
Host::CommitBaseSettingChanges();

View File

@ -15,6 +15,8 @@ public:
InterfaceSettingsWidget(SettingsWindow* settings_dialog, QWidget* parent);
~InterfaceSettingsWidget();
void updatePromptOnStateLoadSaveFailureCheckbox(Qt::CheckState state);
Q_SIGNALS:
void themeChanged();
void languageChanged();

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>725</width>
<height>625</height>
<height>637</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@ -78,6 +78,13 @@
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="promptOnStateLoadSaveFailure">
<property name="text">
<string>Prompt On State Load/Save Failure</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -380,6 +387,7 @@
<tabstop>discordPresence</tabstop>
<tabstop>pauseOnControllerDisconnection</tabstop>
<tabstop>mouseLock</tabstop>
<tabstop>promptOnStateLoadSaveFailure</tabstop>
<tabstop>startFullscreen</tabstop>
<tabstop>doubleClickTogglesFullscreen</tabstop>
<tabstop>renderToSeparateWindow</tabstop>

View File

@ -261,8 +261,10 @@ void SetupWizardDialog::onDirectoryListContextMenuRequested(const QPoint& point)
const int row = selection[0].row();
QMenu menu;
//: Part of the right-click menu for game directory entries
menu.addAction(tr("Remove"), [this]() { onRemoveSearchDirectoryButtonClicked(); });
menu.addSeparator();
//: Part of the right-click menu for game directory entries
menu.addAction(tr("Open Directory..."),
[this, row]() { QtUtils::OpenURL(this, QUrl::fromLocalFile(m_ui.searchDirectoryList->item(row, 0)->text())); });
menu.exec(m_ui.searchDirectoryList->mapToGlobal(point));

View File

@ -13,7 +13,7 @@
#include "VMManager.h"
#if defined(_WIN32)
#include <Windows.h>
#include "common/RedtapeWindows.h"
#include <shlobj.h>
#include <winnls.h>
#include <shobjidl.h>
@ -61,16 +61,6 @@ ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& t
connect(m_ui.bootOptionToggle, &QCheckBox::toggled, m_ui.bootOptionDropdown, &QPushButton::setEnabled);
connect(m_ui.fullscreenMode, &QCheckBox::toggled, m_ui.fullscreenModeDropdown, &QPushButton::setEnabled);
m_ui.shortcutDesktop->setChecked(true);
m_ui.overrideBootELFPath->setEnabled(false);
m_ui.overrideBootELFButton->setEnabled(false);
m_ui.gameArgs->setEnabled(false);
m_ui.bootOptionDropdown->setEnabled(false);
m_ui.fullscreenModeDropdown->setEnabled(false);
m_ui.loadStateIndex->setEnabled(false);
m_ui.loadStateFileBrowse->setEnabled(false);
m_ui.loadStateFilePath->setEnabled(false);
m_ui.loadStateIndex->setMaximum(VMManager::NUM_SAVE_STATE_SLOTS);
if (std::getenv("container"))
@ -80,6 +70,7 @@ ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& t
m_ui.shortcutStartMenu->setEnabled(false);
}
connect(m_ui.dialogButtons, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(m_ui.dialogButtons, &QDialogButtonBox::accepted, this, [&]() {
std::vector<std::string> args;
@ -111,21 +102,24 @@ ShortcutCreationDialog::ShortcutCreationDialog(QWidget* parent, const QString& t
}
}
if (m_ui.loadStateFileToggle->isChecked() && !m_ui.loadStateFilePath->text().isEmpty())
{
args.push_back("-statefile");
args.push_back(m_ui.loadStateFilePath->text().toStdString());
}
if (m_ui.fullscreenMode->isChecked())
args.push_back(m_ui.fullscreenModeDropdown->currentIndex() ? "-nofullscreen" : "-fullscreen");
if (m_ui.bigPictureModeToggle->isChecked())
args.push_back("-bigpicture");
m_desktop = m_ui.shortcutDesktop->isChecked();
std::string custom_args = m_ui.customArgsInput->text().toStdString();
ShortcutCreationDialog::CreateShortcut(title.toStdString(), path.toStdString(), args, custom_args, m_desktop);
ShortcutCreationDialog::CreateShortcut(title.toStdString(), path.toStdString(), args, custom_args, m_ui.shortcutDesktop->isChecked());
accept();
});
connect(m_ui.dialogButtons, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::string game_path, std::vector<std::string> passed_cli_args, std::string custom_args, bool is_desktop)
@ -183,7 +177,10 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
lossless &= ShortcutCreationDialog::EscapeShortcutCommandLine(&arg);
if (!lossless)
QMessageBox::warning(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s). The resulting shortcut may not work."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
{
QMessageBox::warning(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
ShortcutCreationDialog::EscapeShortcutCommandLine(&clean_path);
std::string combined_args = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " ");
@ -203,7 +200,7 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
if (FAILED(res))
{
Console.ErrorFmt("Failed to create shortcut: CoInitialize failed ({})", str_error(res));
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("CoInitialize failed (%1").arg(str_error(res)), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("CoInitialize failed (%1)").arg(str_error(res)), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
@ -313,9 +310,6 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
return;
}
if (is_flatpak) // Flatpak
executable_path = "flatpak run net.pcsx2.PCSX2";
// Find home directory
std::string link_path;
const char* home = std::getenv("HOME");
@ -340,15 +334,30 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
}
else
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Home path is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("Path to the Home directory is empty."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
// Checks if a shortcut already exist
if (FileSystem::FileExists(link_path.c_str()))
// Copy PCSX2 icon
std::string icon_dest;
if (xdg_data_home)
icon_dest = fmt::format("{}/icons/hicolor/512x512/apps/", xdg_data_home);
else
icon_dest = fmt::format("{}/.local/share/icons/hicolor/512x512/apps/", home);
std::string icon_name;
if (is_flatpak) // Flatpak
{
QMessageBox::critical(this, tr("Failed to create shortcut"), tr("A shortcut with the same name already exists."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
executable_path = "flatpak run net.pcsx2.PCSX2";
icon_name = "net.pcsx2.PCSX2";
}
else
{
icon_name = "PCSX2";
std::string icon_path = fmt::format("{}/{}.png", icon_dest, icon_name).c_str();
if (FileSystem::EnsureDirectoryExists(icon_dest.c_str(), true))
FileSystem::CopyFilePath(Path::Combine(EmuFolders::Resources, "icons/AppIconLarge.png").c_str(), icon_path.c_str(), false);
}
// Shortcut CmdLine Args
@ -357,25 +366,13 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
lossless &= ShortcutCreationDialog::EscapeShortcutCommandLine(&arg);
if (!lossless)
QMessageBox::warning(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s). The resulting shortcut may not work."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
{
QMessageBox::warning(this, tr("Failed to create shortcut"), tr("File path contains invalid character(s)."), QMessageBox::StandardButton::Ok, QMessageBox::StandardButton::Ok);
return;
}
std::string cmdline = StringUtil::JoinString(passed_cli_args.begin(), passed_cli_args.end(), " ");
if (!is_flatpak)
{
// Copy PCSX2 icon
std::string icon_dest;
if (xdg_data_home)
icon_dest = fmt::format("{}/icons/hicolor/512x512/apps/", xdg_data_home);
else
icon_dest = fmt::format("{}/.local/share/icons/hicolor/512x512/apps/", home);
std::string icon_name = "PCSX2.png";
std::string icon_path = fmt::format("{}/{}", icon_dest, icon_name).c_str();
if (FileSystem::EnsureDirectoryExists(icon_dest.c_str(), true))
FileSystem::CopyFilePath(Path::Combine(EmuFolders::Resources, "icons/AppIconLarge.png").c_str(), icon_path.c_str(), false);
}
// Further string sanitization
if (!is_flatpak)
ShortcutCreationDialog::EscapeShortcutCommandLine(&executable_path);
@ -393,7 +390,7 @@ void ShortcutCreationDialog::CreateShortcut(const std::string name, const std::s
"StartupWMClass=PCSX2\n"
"Exec=" + final_args + "\n"
"Name=" + clean_name + "\n"
"Icon=net.pcsx2.PCSX2\n"
"Icon=" + icon_name + "\n"
"Categories=Game;Emulator;\n";
std::string_view sv(file_content);

View File

@ -22,8 +22,9 @@ public:
bool EscapeShortcutCommandLine(std::string* cmdline);
protected:
QString m_title;
QString m_path;
bool m_desktop;
const QString m_title;
const QString m_path;
private:
Ui::ShortcutCreationDialog m_ui;
};

View File

@ -10,7 +10,7 @@
<x>0</x>
<y>0</y>
<width>500</width>
<height>685</height>
<height>700</height>
</rect>
</property>
<layout class="QGridLayout" name="shortcutLayout">
@ -59,6 +59,9 @@
</item>
<item row="0" column="1">
<widget class="QComboBox" name="fullscreenModeDropdown">
<property name="enabled">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>Force Enable</string>
@ -82,10 +85,17 @@
</widget>
</item>
<item row="3" column="2" colspan="2">
<widget class="QLineEdit" name="gameArgs"/>
<widget class="QLineEdit" name="gameArgs">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="2" colspan="2">
<widget class="QComboBox" name="bootOptionDropdown">
<property name="enabled">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>Fast Boot</string>
@ -107,13 +117,20 @@
</item>
<item row="2" column="3">
<widget class="QPushButton" name="overrideBootELFButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLineEdit" name="overrideBootELFPath"/>
<widget class="QLineEdit" name="overrideBootELFPath">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="10" column="0" colspan="4">
<widget class="QGroupBox" name="saveStateGroup">
@ -123,6 +140,9 @@
<layout class="QGridLayout" name="savestateGridLayout">
<item row="1" column="1">
<widget class="QSpinBox" name="loadStateIndex">
<property name="enabled">
<bool>false</bool>
</property>
<property name="showGroupSeparator" stdset="0">
<bool>false</bool>
</property>
@ -132,7 +152,11 @@
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="loadStateFilePath"/>
<widget class="QLineEdit" name="loadStateFilePath">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QRadioButton" name="loadStateNone">
@ -160,6 +184,9 @@
</item>
<item row="2" column="2">
<widget class="QPushButton" name="loadStateFileBrowse">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Browse...</string>
</property>
@ -194,7 +221,7 @@
<item row="0" column="0">
<widget class="QLabel" name="customArgsInstruction">
<property name="text">
<string>You may add additional (space-separated) &lt;a href=&quot;https://pcsx2.net/docs/post/cli/&quot;&gt;custom arguments&lt;/a&gt; that are not listed above here:</string>
<string>You may add additional (space-separated) &lt;a href=&quot;https://pcsx2.net/docs/advanced/cli/&quot;&gt;custom arguments&lt;/a&gt; that are not listed above here:</string>
</property>
<property name="textFormat">
<enum>Qt::TextFormat::RichText</enum>

View File

@ -82,7 +82,7 @@ void InputRecordingViewer::openFile()
{
QFileDialog dialog(this);
dialog.setFileMode(QFileDialog::ExistingFile);
dialog.setWindowTitle("Select a File");
dialog.setWindowTitle(tr("Select a File"));
dialog.setNameFilter(tr("Input Recording Files (*.p2m2)"));
QStringList fileNames;
if (dialog.exec())

View File

@ -4,8 +4,8 @@
<context>
<name>AchievementSettingsWidget</name>
<message numerus="yes">
<location filename="../Settings/AchievementSettingsWidget.cpp" line="134"/>
<location filename="../Settings/AchievementSettingsWidget.cpp" line="141"/>
<location filename="../Settings/AchievementSettingsWidget.cpp" line="196"/>
<location filename="../Settings/AchievementSettingsWidget.cpp" line="203"/>
<source>%n seconds</source>
<translation>
<numerusform>%n second</numerusform>
@ -16,7 +16,7 @@
<context>
<name>Achievements</name>
<message numerus="yes">
<location filename="../../pcsx2/Achievements.cpp" line="1023"/>
<location filename="../../pcsx2/Achievements.cpp" line="1104"/>
<source>You have unlocked {} of %n achievements</source>
<comment>Achievement popup</comment>
<translation>
@ -25,7 +25,7 @@
</translation>
</message>
<message numerus="yes">
<location filename="../../pcsx2/Achievements.cpp" line="1026"/>
<location filename="../../pcsx2/Achievements.cpp" line="1107"/>
<source>and earned {} of %n points</source>
<comment>Achievement popup</comment>
<translation>
@ -34,7 +34,7 @@
</translation>
</message>
<message numerus="yes">
<location filename="../../pcsx2/Achievements.cpp" line="1111"/>
<location filename="../../pcsx2/Achievements.cpp" line="1191"/>
<source>%n achievements</source>
<comment>Mastery popup</comment>
<translation>
@ -43,7 +43,7 @@
</translation>
</message>
<message numerus="yes">
<location filename="../../pcsx2/Achievements.cpp" line="1113"/>
<location filename="../../pcsx2/Achievements.cpp" line="1193"/>
<source>%n points</source>
<comment>Mastery popup</comment>
<translation>
@ -55,8 +55,8 @@
<context>
<name>GameList</name>
<message numerus="yes">
<location filename="../GameList/GameListModel.cpp" line="268"/>
<location filename="../../pcsx2/GameList.cpp" line="1142"/>
<location filename="../GameList/GameListModel.cpp" line="223"/>
<location filename="../../pcsx2/GameList.cpp" line="1248"/>
<source>%n hours</source>
<translation>
<numerusform>%n hour</numerusform>
@ -64,19 +64,27 @@
</translation>
</message>
<message numerus="yes">
<location filename="../GameList/GameListModel.cpp" line="270"/>
<location filename="../../pcsx2/GameList.cpp" line="1144"/>
<location filename="../GameList/GameListModel.cpp" line="227"/>
<location filename="../../pcsx2/GameList.cpp" line="1250"/>
<source>%n minutes</source>
<translation>
<numerusform>%n minute</numerusform>
<numerusform>%n minutes</numerusform>
</translation>
</message>
<message numerus="yes">
<location filename="../../pcsx2/GameList.cpp" line="1252"/>
<source>%n seconds</source>
<translation>
<numerusform>%n second</numerusform>
<numerusform>%n seconds</numerusform>
</translation>
</message>
</context>
<context>
<name>InputBindingWidget</name>
<message numerus="yes">
<location filename="../Settings/InputBindingWidget.cpp" line="73"/>
<location filename="../Settings/InputBindingWidget.cpp" line="77"/>
<source>%n bindings</source>
<translation>
<numerusform>%n binding</numerusform>
@ -84,10 +92,21 @@
</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message numerus="yes">
<location filename="../MainWindow.cpp" line="3061"/>
<source>%n save states deleted.</source>
<translation>
<numerusform>%n save state deleted.</numerusform>
<numerusform>%n save states deleted.</numerusform>
</translation>
</message>
</context>
<context>
<name>Patch</name>
<message numerus="yes">
<location filename="../../pcsx2/Patch.cpp" line="698"/>
<location filename="../../pcsx2/Patch.cpp" line="768"/>
<source>%n GameDB patches are active.</source>
<comment>OSD Message</comment>
<translation>
@ -96,7 +115,7 @@
</translation>
</message>
<message numerus="yes">
<location filename="../../pcsx2/Patch.cpp" line="705"/>
<location filename="../../pcsx2/Patch.cpp" line="775"/>
<source>%n game patches are active.</source>
<comment>OSD Message</comment>
<translation>
@ -105,7 +124,7 @@
</translation>
</message>
<message numerus="yes">
<location filename="../../pcsx2/Patch.cpp" line="712"/>
<location filename="../../pcsx2/Patch.cpp" line="781"/>
<source>%n cheat patches are active.</source>
<comment>OSD Message</comment>
<translation>

File diff suppressed because it is too large Load Diff

View File

@ -2942,8 +2942,19 @@ static void cdvdWrite16(u8 rt) // SCOMMAND
bit_ofs = mg_BIToffset(&cdvd.mg_buffer[0]);
memcpy(&cdvd.mg_kbit[0], &cdvd.mg_buffer[bit_ofs - 0x20], 0x10);
memcpy(&cdvd.mg_kcon[0], &cdvd.mg_buffer[bit_ofs - 0x10], 0x10);
const size_t buf_size = sizeof(cdvd.mg_buffer);
if (bit_ofs < 0x20 || (size_t)bit_ofs > buf_size)
{
fail_pol_cal();
break;
}
const size_t kbit_ofs = bit_ofs - 0x20;
const size_t kcon_ofs = bit_ofs - 0x10;
std::memcpy(&cdvd.mg_kbit[0], &cdvd.mg_buffer[kbit_ofs], 0x10);
std::memcpy(&cdvd.mg_kcon[0], &cdvd.mg_buffer[kcon_ofs], 0x10);
if ((cdvd.mg_buffer[bit_ofs + 5] || cdvd.mg_buffer[bit_ofs + 6] || cdvd.mg_buffer[bit_ofs + 7]) ||
(GetBufferU16(&cdvd.mg_buffer[0],bit_ofs + 4) * 16 + bit_ofs + 8 + 16 != GetBufferU16(&cdvd.mg_buffer[0], 0x14)))
@ -2969,7 +2980,31 @@ static void cdvdWrite16(u8 rt) // SCOMMAND
{
SetSCMDResultSize(3); //in:0
const int bit_ofs = mg_BIToffset(&cdvd.mg_buffer[0]);
memcpy(&cdvd.mg_buffer[0], &cdvd.mg_buffer[bit_ofs], static_cast<size_t>(8 + 16 * static_cast<int>(cdvd.mg_buffer[bit_ofs + 4])));
if (bit_ofs < 0)
{
fail_pol_cal();
break;
}
const size_t bufsize = sizeof(cdvd.mg_buffer);
const size_t ofs = static_cast<size_t>(bit_ofs);
if (ofs > bufsize - 5) // Make sure we can read the block count
{
fail_pol_cal();
break;
}
const unsigned int blocks = static_cast<unsigned int>(cdvd.mg_buffer[ofs + 4]);
const size_t copy_len = 8 + 16 * static_cast<size_t>(blocks);
if (copy_len > bufsize - ofs) // Make sure we can read the blocks
{
fail_pol_cal();
break;
}
std::memmove(&cdvd.mg_buffer[0], &cdvd.mg_buffer[ofs], copy_len);
cdvd.mg_maxsize = 0; // don't allow any write
cdvd.mg_size = 8 + 16 * cdvd.mg_buffer[4]; //new offset, i just moved the data

View File

@ -3427,6 +3427,9 @@ void GSDevice12::BeginRenderPass(D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE color_b
m_dirty_flags &= ~DIRTY_FLAG_RENDER_TARGET;
m_in_render_pass = true;
if (stencil_end == D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD)
GL_INS("D3D12: BeginRenderPass() end stencil is DISCARDED.");
D3D12_RENDER_PASS_RENDER_TARGET_DESC rt = {};
if (m_current_render_target)
{
@ -4008,7 +4011,8 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
draw_ds ? D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE : D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS,
stencil_DATE ? D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_PRESERVE :
D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_NO_ACCESS,
stencil_DATE ? D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD :
stencil_DATE ? (draw_rt_clone ? D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE :
D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_DISCARD) :
D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_NO_ACCESS,
clear_color, draw_ds ? draw_ds->GetClearDepth() : 0.0f, 1);
}

View File

@ -106,14 +106,15 @@ static void HotkeyLoadStateSlot(s32 slot)
Error error;
if (!VMManager::LoadStateFromSlot(slot, false, &error))
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_TRIANGLE_EXCLAMATION,
error.GetDescription(), Host::OSD_INFO_DURATION);
FullscreenUI::ReportStateLoadError(error.GetDescription(), slot, false);
});
}
static void HotkeySaveStateSlot(s32 slot)
{
VMManager::SaveStateToSlot(slot);
VMManager::SaveStateToSlot(slot, true, [slot](const std::string& error) {
FullscreenUI::ReportStateSaveError(error, slot);
});
}
static bool CanPause()

View File

@ -630,9 +630,9 @@ namespace FullscreenUI
static std::unique_ptr<GameList::Entry> s_game_settings_entry;
static std::vector<std::pair<std::string, bool>> s_game_list_directories_cache;
static std::vector<GSAdapterInfo> s_graphics_adapter_list_cache;
static Patch::PatchInfoList s_game_patch_list;
static std::vector<Patch::PatchInfo> s_game_patch_list;
static std::vector<std::string> s_enabled_game_patch_cache;
static Patch::PatchInfoList s_game_cheats_list;
static std::vector<Patch::PatchInfo> s_game_cheats_list;
static std::vector<std::string> s_enabled_game_cheat_cache;
static u32 s_game_cheat_unlabelled_count = 0;
static std::vector<const HotkeyInfo*> s_hotkey_list_cache;
@ -670,7 +670,8 @@ namespace FullscreenUI
static void DrawSaveStateSelector(bool is_loading);
static bool OpenLoadStateSelectorForGameResume(const GameList::Entry* entry);
static void DrawResumeStateSelector();
static void DoLoadState(std::string path);
static void DoLoadState(std::string path, std::optional<s32> slot, bool backup);
static void DoSaveState(s32 slot);
static std::vector<SaveStateListEntry> s_save_state_selector_slots;
static std::string s_save_state_selector_game_path;
@ -1220,7 +1221,7 @@ void FullscreenUI::Render()
// see if background setting changed
static std::string s_last_background_path;
std::string current_path = Host::GetBaseStringSettingValue("UI", "GameListBackgroundPath");
std::string current_path = Host::GetBaseStringSettingValue("UI", "FSUIBackgroundPath");
if (s_last_background_path != current_path)
{
s_last_background_path = current_path;
@ -1239,7 +1240,8 @@ void FullscreenUI::Render()
s_current_main_window == MainWindowType::Exit ||
s_current_main_window == MainWindowType::GameList ||
s_current_main_window == MainWindowType::GameListSettings ||
s_current_main_window == MainWindowType::Settings) && s_custom_background_enabled && s_custom_background_texture;
s_current_main_window == MainWindowType::Settings) &&
!VMManager::HasValidVM() && s_custom_background_enabled && s_custom_background_texture;
ImVec4 original_background_color;
if (should_draw_background)
@ -1690,7 +1692,7 @@ bool FullscreenUI::ShouldDefaultToGameList()
void FullscreenUI::LoadCustomBackground()
{
std::string path = Host::GetBaseStringSettingValue("UI", "GameListBackgroundPath");
std::string path = Host::GetBaseStringSettingValue("UI", "FSUIBackgroundPath");
if (path.empty())
{
@ -1758,19 +1760,25 @@ void FullscreenUI::DrawCustomBackground()
const ImGuiIO& io = ImGui::GetIO();
const ImVec2 display_size = io.DisplaySize;
const float opacity = Host::GetBaseFloatSettingValue("UI", "GameListBackgroundOpacity", 100.0f) / 100.0f;
const std::string mode = Host::GetBaseStringSettingValue("UI", "GameListBackgroundMode", "fit");
const u8 alpha = static_cast<u8>(Host::GetBaseFloatSettingValue("UI", "FSUIBackgroundOpacity", 100.0f) * 2.55f);
const std::string mode = Host::GetBaseStringSettingValue("UI", "FSUIBackgroundMode", "fit");
const float tex_width = static_cast<float>(s_custom_background_texture->GetWidth());
const float tex_height = static_cast<float>(s_custom_background_texture->GetHeight());
ImVec2 img_min, img_max;
// Override the UIBackgroundColor that windows use
// We need to make windows transparent so our background image shows through
const ImVec4 transparent_bg = ImVec4(UIBackgroundColor.x, UIBackgroundColor.y, UIBackgroundColor.z, 0.0f);
ImGuiFullscreen::UIBackgroundColor = transparent_bg;
ImDrawList* bg_draw_list = ImGui::GetBackgroundDrawList();
const ImU32 col = IM_COL32(255, 255, 255, alpha);
const ImTextureID tex_id = reinterpret_cast<ImTextureID>(s_custom_background_texture->GetNativeHandle());
if (mode == "stretch")
{
// stretch to fill entire display (ignores aspect ratio)
img_min = ImVec2(0.0f, 0.0f);
img_max = display_size;
bg_draw_list->AddImage(tex_id, ImVec2(0.0f, 0.0f), display_size, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), col);
}
else if (mode == "fill")
{
@ -1795,8 +1803,64 @@ void FullscreenUI::DrawCustomBackground()
const float offset_x = (display_size.x - scaled_width) * 0.5f;
const float offset_y = (display_size.y - scaled_height) * 0.5f;
img_min = ImVec2(offset_x, offset_y);
img_max = ImVec2(offset_x + scaled_width, offset_y + scaled_height);
bg_draw_list->AddImage(tex_id,
ImVec2(offset_x, offset_y),
ImVec2(offset_x + scaled_width, offset_y + scaled_height),
ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), col);
}
else if (mode == "center")
{
// Center image at original size
const float offset_x = (display_size.x - tex_width) * 0.5f;
const float offset_y = (display_size.y - tex_height) * 0.5f;
bg_draw_list->AddImage(tex_id,
ImVec2(offset_x, offset_y),
ImVec2(offset_x + tex_width, offset_y + tex_height),
ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), col);
}
else if (mode == "tile")
{
// Tile image across entire display
// If the image is extremely small, this approach can generate millions of quads
// and overflow the backend stream buffer (e.g. Vulkan assertion in VKStreamBuffer).
// Since we cannot switch ImGui's sampler to wrap (yet), clamp the maximum number of quads
constexpr int MAX_TILE_QUADS = 16384;
float tile_width = tex_width;
float tile_height = tex_height;
int tiles_x = static_cast<int>(std::ceil(display_size.x / tile_width));
int tiles_y = static_cast<int>(std::ceil(display_size.y / tile_height));
const int total_tiles = tiles_x * tiles_y;
if (total_tiles > MAX_TILE_QUADS)
{
const float scale = std::sqrt(static_cast<float>(total_tiles) / static_cast<float>(MAX_TILE_QUADS));
tile_width *= scale;
tile_height *= scale;
tiles_x = static_cast<int>(std::ceil(display_size.x / tile_width));
tiles_y = static_cast<int>(std::ceil(display_size.y / tile_height));
}
for (int y = 0; y < tiles_y; y++)
{
for (int x = 0; x < tiles_x; x++)
{
const float tile_x = static_cast<float>(x) * tile_width;
const float tile_y = static_cast<float>(y) * tile_height;
const float tile_max_x = std::min(tile_x + tile_width, display_size.x);
const float tile_max_y = std::min(tile_y + tile_height, display_size.y);
// get uvs for partial tiles at edges
const float uv_max_x = (tile_max_x - tile_x) / tile_width;
const float uv_max_y = (tile_max_y - tile_y) / tile_height;
bg_draw_list->AddImage(tex_id,
ImVec2(tile_x, tile_y),
ImVec2(tile_max_x, tile_max_y),
ImVec2(0.0f, 0.0f), ImVec2(uv_max_x, uv_max_y), col);
}
}
}
else // "fit" or default
{
@ -1821,19 +1885,11 @@ void FullscreenUI::DrawCustomBackground()
const float offset_x = (display_size.x - scaled_width) * 0.5f;
const float offset_y = (display_size.y - scaled_height) * 0.5f;
img_min = ImVec2(offset_x, offset_y);
img_max = ImVec2(offset_x + scaled_width, offset_y + scaled_height);
bg_draw_list->AddImage(tex_id,
ImVec2(offset_x, offset_y),
ImVec2(offset_x + scaled_width, offset_y + scaled_height),
ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), col);
}
// Override the UIBackgroundColor that windows use
// We need to make windows transparent so our background image shows through
const ImVec4 transparent_bg = ImVec4(UIBackgroundColor.x, UIBackgroundColor.y, UIBackgroundColor.z, 0.0f);
ImGuiFullscreen::UIBackgroundColor = transparent_bg;
ImDrawList* bg_draw_list = ImGui::GetBackgroundDrawList();
const ImU32 col = IM_COL32(255, 255, 255, static_cast<u8>(opacity * 255.0f));
bg_draw_list->AddImage(reinterpret_cast<ImTextureID>(s_custom_background_texture->GetNativeHandle()),
img_min, img_max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), col);
}
//////////////////////////////////////////////////////////////////////////
@ -3581,7 +3637,7 @@ void FullscreenUI::PopulateGameListDirectoryCache(SettingsInterface* si)
void FullscreenUI::PopulatePatchesAndCheatsList(const std::string_view serial, u32 crc)
{
constexpr auto sort_patches = [](Patch::PatchInfoList& list) {
constexpr auto sort_patches = [](std::vector<Patch::PatchInfo>& list) {
std::sort(list.begin(), list.end(), [](const Patch::PatchInfo& lhs, const Patch::PatchInfo& rhs) { return lhs.name < rhs.name; });
};
@ -3629,7 +3685,7 @@ void FullscreenUI::DrawSettingsWindow()
ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) +
(LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f));
const bool using_custom_bg = s_custom_background_enabled && s_custom_background_texture;
const bool using_custom_bg = !VMManager::HasValidVM() && s_custom_background_enabled && s_custom_background_texture;
const float header_bg_alpha = VMManager::HasValidVM() ? 0.90f : 1.0f;
const float content_bg_alpha = using_custom_bg ? 0.0f : (VMManager::HasValidVM() ? 0.90f : 1.0f);
SettingsInterface* bsi = GetEditingSettingsInterface();
@ -4052,21 +4108,19 @@ void FullscreenUI::DrawInterfaceSettingsPage()
MenuHeading(FSUI_CSTR("Background"));
std::string background_path = bsi->GetStringValue("UI", "GameListBackgroundPath", "");
const bool background_enabled = bsi->GetBoolValue("UI", "GameListBackgroundEnabled", false);
std::string background_path = bsi->GetStringValue("UI", "FSUIBackgroundPath", "");
std::string background_display = FSUI_STR("None");
if (!background_path.empty() && background_enabled)
if (!background_path.empty())
{
background_display = Path::GetFileName(background_path);
}
if (MenuButtonWithValue(FSUI_ICONSTR(ICON_FA_IMAGE, "Background Image"),
FSUI_CSTR("Select a custom background image to use in Big Picture Mode menus."),
FSUI_CSTR("Select a custom background image to use in Big Picture Mode menus.\n\nSupported formats: PNG, JPG, JPEG, BMP."),
background_display.c_str()))
{
OpenFileSelector(FSUI_ICONSTR(ICON_FA_IMAGE, "Select Background Image"), false,
[](const std::string& path) {
OpenFileSelector(FSUI_ICONSTR(ICON_FA_IMAGE, "Select Background Image"), false, [](const std::string& path) {
if (!path.empty())
{
{
@ -4074,23 +4128,20 @@ void FullscreenUI::DrawInterfaceSettingsPage()
SettingsInterface* bsi = GetEditingSettingsInterface(false);
std::string relative_path = Path::MakeRelative(path, EmuFolders::DataRoot);
bsi->SetStringValue("UI", "GameListBackgroundPath", relative_path.c_str());
bsi->SetBoolValue("UI", "GameListBackgroundEnabled", true);
bsi->SetStringValue("UI", "FSUIBackgroundPath", relative_path.c_str());
bsi->SetBoolValue("UI", "FSUIBackgroundEnabled", true);
SetSettingsChanged(bsi);
}
LoadCustomBackground();
}
CloseFileSelector();
},
GetImageFileFilters());
CloseFileSelector(); }, GetImageFileFilters());
}
if (MenuButton(FSUI_ICONSTR(ICON_FA_XMARK, "Clear Background Image"),
FSUI_CSTR("Removes the custom background image.")))
{
bsi->DeleteValue("UI", "GameListBackgroundPath");
bsi->SetBoolValue("UI", "GameListBackgroundEnabled", false);
bsi->DeleteValue("UI", "FSUIBackgroundPath");
SetSettingsChanged(bsi);
s_custom_background_texture.reset();
@ -4100,21 +4151,25 @@ void FullscreenUI::DrawInterfaceSettingsPage()
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_DROPLET, "Background Opacity"),
FSUI_CSTR("Sets the transparency of the custom background image."),
"UI", "GameListBackgroundOpacity", 100, 0, 100, "%d%%");
"UI", "FSUIBackgroundOpacity", 100, 0, 100, "%d%%");
static constexpr const char* s_background_mode_names[] = {
FSUI_NSTR("Fit"),
FSUI_NSTR("Fill"),
FSUI_NSTR("Stretch"),
FSUI_NSTR("Center"),
FSUI_NSTR("Tile"),
};
static constexpr const char* s_background_mode_values[] = {
"fit",
"fill",
"stretch",
"center",
"tile",
};
DrawStringListSetting(bsi, FSUI_ICONSTR(ICON_FA_EXPAND, "Background Mode"),
FSUI_CSTR("Select how to display the background image."),
"UI", "GameListBackgroundMode", "fit", s_background_mode_names, s_background_mode_values, std::size(s_background_mode_names), true);
"UI", "FSUIBackgroundMode", "fit", s_background_mode_names, s_background_mode_values, std::size(s_background_mode_names), true);
MenuHeading(FSUI_CSTR("Behaviour"));
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_PF_SNOOZE, "Inhibit Screensaver"),
@ -4129,6 +4184,8 @@ void FullscreenUI::DrawInterfaceSettingsPage()
FSUI_CSTR("Pauses the emulator when a controller with bindings is disconnected."), "UI", "PauseOnControllerDisconnection", false);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_RECTANGLE_LIST, "Pause On Menu"),
FSUI_CSTR("Pauses the emulator when you open the quick menu, and unpauses when you close it."), "UI", "PauseOnMenu", true);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_FLOPPY_DISK, "Prompt On State Load/Save Failure"),
FSUI_CSTR("Display a modal dialog when a save state load/save operation fails."), "UI", "PromptOnStateLoadSaveFailure", true);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_POWER_OFF, "Confirm Shutdown"),
FSUI_CSTR("Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed."),
"UI", "ConfirmShutdown", true);
@ -4162,7 +4219,7 @@ void FullscreenUI::DrawInterfaceSettingsPage()
}
SmallStackString<256> swap_summery;
swap_summery.format(FSUI_FSTR("Uses {} as confirm when using a controller"), ICON_PF_BUTTON_CIRCLE);
swap_summery.format(FSUI_FSTR("Uses {} as confirm when using a controller."), ICON_PF_BUTTON_CIRCLE);
if (MenuButtonWithValue(FSUI_ICONSTR(ICON_FA_GAMEPAD, "Swap OK/Cancel in Big Picture Mode"), swap_summery.c_str(),
(swap_index < std::size(swap_values)) ? Host::TranslateToCString(TR_CONTEXT, swap_names[swap_index]) : FSUI_CSTR("Unknown")))
{
@ -4230,6 +4287,8 @@ void FullscreenUI::DrawInterfaceSettingsPage()
true);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_ARROW_POINTER, "Hide Cursor In Fullscreen"),
FSUI_CSTR("Hides the mouse pointer/cursor when the emulator is in fullscreen mode."), "UI", "HideMouseCursor", false);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_TABLET_SCREEN_BUTTON, "Start Big Picture UI"),
FSUI_CSTR("Automatically starts Big Picture Mode instead of the regular Qt interface when PCSX2 launches."), "UI", "StartBigPictureMode", false);
MenuHeading(FSUI_CSTR("On-Screen Display"));
DrawIntSpinBoxSetting(bsi, FSUI_ICONSTR(ICON_FA_MAGNIFYING_GLASS, "OSD Scale"),
@ -4268,7 +4327,7 @@ void FullscreenUI::DrawInterfaceSettingsPage()
FSUI_CSTR("Shows the number of internal video frames displayed per second by the system."),
"EmuCore/GS", "OsdShowFPS", false);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_CLAPPERBOARD, "Show VPS"),
FSUI_CSTR("Shows the number of V-syncs performed per second by the system."), "EmuCore/GS", "OsdShowVPS", false);
FSUI_CSTR("Shows the number of Vsyncs performed per second by the system."), "EmuCore/GS", "OsdShowVPS", false);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_PF_MONITOR_CODE, "Show Resolution"),
FSUI_CSTR("Shows the internal resolution of the game."), "EmuCore/GS", "OsdShowResolution", false);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_COMPUTER, "Show Hardware Info"),
@ -4723,9 +4782,9 @@ void FullscreenUI::DrawGraphicsSettingsPage(SettingsInterface* bsi, bool show_ad
FSUI_NSTR("Disabled (Ignore Transfers)"),
};
static constexpr const char* s_screenshot_sizes[] = {
FSUI_NSTR("Screen Resolution"),
FSUI_NSTR("Internal Resolution"),
FSUI_NSTR("Internal Resolution (Aspect Uncorrected)"),
FSUI_NSTR("Display Resolution (Aspect Corrected)"),
FSUI_NSTR("Internal Resolution (Aspect Corrected)"),
FSUI_NSTR("Internal Resolution (No Aspect Correction)"),
};
static constexpr const char* s_screenshot_formats[] = {
FSUI_NSTR("PNG"),
@ -4965,7 +5024,7 @@ void FullscreenUI::DrawGraphicsSettingsPage(SettingsInterface* bsi, bool show_ad
"UserHacks_native_scaling", 0, s_native_scaling_options, std::size(s_native_scaling_options), true);
DrawIntListSetting(bsi, FSUI_CSTR("Round Sprite"), FSUI_CSTR("Adjusts sprite coordinates."), "EmuCore/GS",
"UserHacks_round_sprite_offset", 0, s_round_sprite_options, std::size(s_round_sprite_options), true);
DrawIntListSetting(bsi, FSUI_CSTR("Bilinear Upscale"),
DrawIntListSetting(bsi, FSUI_CSTR("Bilinear Dirty Upscale"),
FSUI_CSTR("Can smooth out textures due to be bilinear filtered when upscaling. E.g. Brave sun glare."), "EmuCore/GS",
"UserHacks_BilinearHack", static_cast<int>(GSBilinearDirtyMode::Automatic), s_bilinear_dirty_options,
std::size(s_bilinear_dirty_options), true);
@ -5141,7 +5200,7 @@ void FullscreenUI::DrawAudioSettingsPage()
DrawEnumSetting(
bsi, FSUI_ICONSTR(ICON_FA_VOLUME_OFF, "Audio Backend"),
FSUI_CSTR("The audio backend determines how frames produced by the emulator are submitted to the host."), "SPU2/Output",
FSUI_CSTR("Determines how audio frames produced by the emulator are submitted to the host."), "SPU2/Output",
"Backend", Pcsx2Config::SPU2Options::DEFAULT_BACKEND, &AudioStream::ParseBackendName, &AudioStream::GetBackendName,
&AudioStream::GetBackendDisplayName, AudioBackend::Count);
DrawEnumSetting(bsi, FSUI_ICONSTR(ICON_PF_SPEAKER_ALT, "Expansion"),
@ -5196,7 +5255,7 @@ void FullscreenUI::DrawMemoryCardSettingsPage()
std::string file_key(fmt::format("Slot{}_Filename", port + 1));
DrawToggleSetting(bsi,
SmallString::from_format(fmt::runtime(FSUI_ICONSTR_S(ICON_PF_MEMORY_CARD, "Card Enabled", "##card_enabled_{}")), port),
SmallString::from_format(fmt::runtime(FSUI_ICONSTR_S(ICON_PF_MEMORY_CARD, "Memory Card Enabled", "##card_enabled_{}")), port),
FSUI_CSTR("If not set, this card will be considered unplugged."), "MemoryCards", enable_key.c_str(), true);
const bool enabled = GetEffectiveBoolSetting(bsi, "MemoryCards", enable_key.c_str(), true);
@ -6613,7 +6672,7 @@ void FullscreenUI::DrawAdvancedSettingsPage()
FSUI_CSTR("Performs just-in-time binary translation of 32-bit MIPS-I machine code to native code."), "EmuCore/CPU/Recompiler",
"EnableIOP", true);
MenuHeading(FSUI_CSTR("Savestate"));
MenuHeading(FSUI_CSTR("Save State Management"));
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_BOX_OPEN, "Compression Method"), FSUI_CSTR("Sets the compression algorithm for savestate."), "EmuCore",
"SavestateCompressionType", static_cast<int>(SavestateCompressionMethod::Zstandard), s_savestate_compression_type, std::size(s_savestate_compression_type), true);
DrawIntListSetting(bsi, FSUI_ICONSTR(ICON_FA_COMPRESS, "Compression Level"), FSUI_CSTR("Sets the compression level for savestate."), "EmuCore",
@ -6631,7 +6690,7 @@ void FullscreenUI::DrawPatchesOrCheatsSettingsPage(bool cheats)
{
SettingsInterface* bsi = GetEditingSettingsInterface();
const Patch::PatchInfoList& patch_list = cheats ? s_game_cheats_list : s_game_patch_list;
const std::vector<Patch::PatchInfo>& patch_list = cheats ? s_game_cheats_list : s_game_patch_list;
std::vector<std::string>& enable_list = cheats ? s_enabled_game_cheat_cache : s_enabled_game_patch_cache;
const char* section = cheats ? Patch::CHEATS_CONFIG_SECTION : Patch::PATCHES_CONFIG_SECTION;
const bool master_enable = cheats ? GetEffectiveBoolSetting(bsi, "EmuCore", "EnableCheats", false) : true;
@ -6739,7 +6798,7 @@ void FullscreenUI::DrawGameFixesSettingsPage()
FSUI_CSTR("Known to affect following games: Bleach Blade Battlers, Growlanser II and III, Wizardry."), "EmuCore/Gamefixes",
"OPHFlagHack", false);
DrawToggleSetting(
bsi, FSUI_CSTR("Emulate GIF FIFO"), FSUI_CSTR("Correct but slower. Known to affect the following games: Fifa Street 2."), "EmuCore/Gamefixes", "GIFFIFOHack", false);
bsi, FSUI_CSTR("Emulate GIF FIFO"), FSUI_CSTR("Correct but slower. Known to affect the following games: FIFA Street 2."), "EmuCore/Gamefixes", "GIFFIFOHack", false);
DrawToggleSetting(bsi, FSUI_CSTR("DMA Busy Hack"),
FSUI_CSTR("Known to affect following games: Mana Khemia 1, Metal Saga, Pilot Down Behind Enemy Lines."), "EmuCore/Gamefixes",
"DMABusyHack", false);
@ -7343,9 +7402,9 @@ void FullscreenUI::DrawSaveStateSelector(bool is_loading)
false, is_loading ? !Achievements::IsHardcoreModeActive() : true, LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY))
{
if (is_loading)
DoLoadState(std::move(entry.path));
DoLoadState(std::move(entry.path), entry.slot, false);
else
Host::RunOnCPUThread([slot = entry.slot]() { VMManager::SaveStateToSlot(slot); });
DoSaveState(entry.slot);
CloseSaveStateSelector();
ReturnToMainWindow();
@ -7481,9 +7540,9 @@ void FullscreenUI::DrawSaveStateSelector(bool is_loading)
if (pressed)
{
if (is_loading)
DoLoadState(entry.path);
DoLoadState(entry.path, entry.slot, false);
else
Host::RunOnCPUThread([slot = entry.slot]() { VMManager::SaveStateToSlot(slot); });
DoSaveState(entry.slot);
CloseSaveStateSelector();
ReturnToMainWindow();
@ -7637,19 +7696,16 @@ void FullscreenUI::DrawResumeStateSelector()
}
}
void FullscreenUI::DoLoadState(std::string path)
void FullscreenUI::DoLoadState(std::string path, std::optional<s32> slot, bool backup)
{
Host::RunOnCPUThread([boot_path = s_save_state_selector_game_path, path = std::move(path)]() {
std::string boot_path = s_save_state_selector_game_path;
Host::RunOnCPUThread([boot_path = std::move(boot_path), path = std::move(path), slot, backup]() {
if (VMManager::HasValidVM())
{
Error error;
if (!VMManager::LoadState(path.c_str(), &error))
{
MTGS::RunOnGSThread([error = std::move(error)]() {
ImGuiFullscreen::OpenInfoMessageDialog(
FSUI_ICONSTR(ICON_FA_TRIANGLE_EXCLAMATION, "Failed to Load State"),
error.GetDescription());
});
ReportStateLoadError(error.GetDescription(), slot, backup);
return;
}
@ -7666,6 +7722,15 @@ void FullscreenUI::DoLoadState(std::string path)
});
}
void FullscreenUI::DoSaveState(s32 slot)
{
Host::RunOnCPUThread([slot]() {
VMManager::SaveStateToSlot(slot, true, [slot](const std::string& error) {
ReportStateSaveError(error, slot);
});
});
}
void FullscreenUI::PopulateGameListEntryList()
{
const int sort = Host::GetBaseIntSettingValue("UI", "FullscreenUIGameSort", 0);
@ -9167,6 +9232,75 @@ void FullscreenUI::DrawAchievementsSettingsPage(std::unique_lock<std::mutex>& se
EndMenuButtons();
}
void FullscreenUI::ReportStateLoadError(const std::string& message, std::optional<s32> slot, bool backup)
{
MTGS::RunOnGSThread([message, slot, backup]() {
const bool prompt_on_error = Host::GetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", true);
if (!prompt_on_error || !ImGuiManager::InitializeFullscreenUI())
{
SaveState_ReportLoadErrorOSD(message, slot, backup);
return;
}
std::string title;
if (slot.has_value())
{
if (backup)
title = fmt::format(FSUI_FSTR("Failed to Load State From Backup Slot {}"), *slot);
else
title = fmt::format(FSUI_FSTR("Failed to Load State From Slot {}"), *slot);
}
else
{
title = FSUI_STR("Failed to Load State");
}
ImGuiFullscreen::InfoMessageDialogCallback callback;
if (VMManager::GetState() == VMState::Running)
{
Host::RunOnCPUThread([]() { VMManager::SetPaused(true); });
callback = []() {
Host::RunOnCPUThread([]() { VMManager::SetPaused(false); });
};
}
ImGuiFullscreen::OpenInfoMessageDialog(
fmt::format("{} {}", ICON_FA_TRIANGLE_EXCLAMATION, title),
std::move(message), std::move(callback));
});
}
void FullscreenUI::ReportStateSaveError(const std::string& message, std::optional<s32> slot)
{
MTGS::RunOnGSThread([message, slot]() {
const bool prompt_on_error = Host::GetBaseBoolSettingValue("UI", "PromptOnStateLoadSaveFailure", true);
if (!prompt_on_error || !ImGuiManager::InitializeFullscreenUI())
{
SaveState_ReportSaveErrorOSD(message, slot);
return;
}
std::string title;
if (slot.has_value())
title = fmt::format(FSUI_FSTR("Failed to Save State To Slot {}"), *slot);
else
title = FSUI_STR("Failed to Save State");
ImGuiFullscreen::InfoMessageDialogCallback callback;
if (VMManager::GetState() == VMState::Running)
{
Host::RunOnCPUThread([]() { VMManager::SetPaused(true); });
callback = []() {
Host::RunOnCPUThread([]() { VMManager::SetPaused(false); });
};
}
ImGuiFullscreen::OpenInfoMessageDialog(
fmt::format("{} {}", ICON_FA_TRIANGLE_EXCLAMATION, title),
std::move(message), std::move(callback));
});
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Translation String Area
// To avoid having to type T_RANSLATE("FullscreenUI", ...) everywhere, we use the shorter macros at the top
@ -9226,6 +9360,8 @@ TRANSLATE_NOOP("FullscreenUI", "Reset System");
TRANSLATE_NOOP("FullscreenUI", "Hardcore mode will not be enabled until the system is reset. Do you want to reset the system now?");
TRANSLATE_NOOP("FullscreenUI", "This game has no achievements.");
TRANSLATE_NOOP("FullscreenUI", "This game has no leaderboards.");
TRANSLATE_NOOP("FullscreenUI", "Failed to Load State");
TRANSLATE_NOOP("FullscreenUI", "Failed to Save State");
TRANSLATE_NOOP("FullscreenUI", "Game List");
TRANSLATE_NOOP("FullscreenUI", "Launch a game from images scanned from your game directories.");
TRANSLATE_NOOP("FullscreenUI", "Start Game");
@ -9271,7 +9407,7 @@ TRANSLATE_NOOP("FullscreenUI", "Selects the color style to be used for Big Pictu
TRANSLATE_NOOP("FullscreenUI", "When Big Picture mode is started, the game list will be displayed instead of the main menu.");
TRANSLATE_NOOP("FullscreenUI", "Show a save state selector UI when switching slots instead of showing a notification bubble.");
TRANSLATE_NOOP("FullscreenUI", "Background");
TRANSLATE_NOOP("FullscreenUI", "Select a custom background image to use in Big Picture Mode menus.");
TRANSLATE_NOOP("FullscreenUI", "Select a custom background image to use in Big Picture Mode menus.\n\nSupported formats: PNG, JPG, JPEG, BMP.");
TRANSLATE_NOOP("FullscreenUI", "Removes the custom background image.");
TRANSLATE_NOOP("FullscreenUI", "Sets the transparency of the custom background image.");
TRANSLATE_NOOP("FullscreenUI", "Select how to display the background image.");
@ -9281,6 +9417,7 @@ TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when a game is started.");
TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when you minimize the window or switch to another application, and unpauses when you switch back.");
TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when a controller with bindings is disconnected.");
TRANSLATE_NOOP("FullscreenUI", "Pauses the emulator when you open the quick menu, and unpauses when you close it.");
TRANSLATE_NOOP("FullscreenUI", "Display a modal dialog when a save state load/save operation fails.");
TRANSLATE_NOOP("FullscreenUI", "Determines whether a prompt will be displayed to confirm shutting down the emulator/game when the hotkey is pressed.");
TRANSLATE_NOOP("FullscreenUI", "Automatically saves the emulator state when powering down or exiting. You can then resume directly from where you left off next time.");
TRANSLATE_NOOP("FullscreenUI", "Creates a backup copy of a save state if it already exists when the save is created. The backup copy has a .backup suffix");
@ -9290,6 +9427,7 @@ TRANSLATE_NOOP("FullscreenUI", "Game Display");
TRANSLATE_NOOP("FullscreenUI", "Automatically switches to fullscreen mode when a game is started.");
TRANSLATE_NOOP("FullscreenUI", "Switches between full screen and windowed when the window is double-clicked.");
TRANSLATE_NOOP("FullscreenUI", "Hides the mouse pointer/cursor when the emulator is in fullscreen mode.");
TRANSLATE_NOOP("FullscreenUI", "Automatically starts Big Picture Mode instead of the regular Qt interface when PCSX2 launches.");
TRANSLATE_NOOP("FullscreenUI", "On-Screen Display");
TRANSLATE_NOOP("FullscreenUI", "Determines how large the on-screen messages and monitors are.");
TRANSLATE_NOOP("FullscreenUI", "%d%%");
@ -9298,7 +9436,7 @@ TRANSLATE_NOOP("FullscreenUI", "Determines where performance statistics are posi
TRANSLATE_NOOP("FullscreenUI", "Shows the current PCSX2 version.");
TRANSLATE_NOOP("FullscreenUI", "Shows the current emulation speed of the system as a percentage.");
TRANSLATE_NOOP("FullscreenUI", "Shows the number of internal video frames displayed per second by the system.");
TRANSLATE_NOOP("FullscreenUI", "Shows the number of V-syncs performed per second by the system.");
TRANSLATE_NOOP("FullscreenUI", "Shows the number of Vsyncs performed per second by the system.");
TRANSLATE_NOOP("FullscreenUI", "Shows the internal resolution of the game.");
TRANSLATE_NOOP("FullscreenUI", "Shows the current system CPU and GPU information.");
TRANSLATE_NOOP("FullscreenUI", "Shows statistics about the emulated GS such as primitives and draw calls.");
@ -9410,7 +9548,7 @@ TRANSLATE_NOOP("FullscreenUI", "Native Scaling");
TRANSLATE_NOOP("FullscreenUI", "Attempt to do rescaling at native resolution.");
TRANSLATE_NOOP("FullscreenUI", "Round Sprite");
TRANSLATE_NOOP("FullscreenUI", "Adjusts sprite coordinates.");
TRANSLATE_NOOP("FullscreenUI", "Bilinear Upscale");
TRANSLATE_NOOP("FullscreenUI", "Bilinear Dirty Upscale");
TRANSLATE_NOOP("FullscreenUI", "Can smooth out textures due to be bilinear filtered when upscaling. E.g. Brave sun glare.");
TRANSLATE_NOOP("FullscreenUI", "Texture Offset X");
TRANSLATE_NOOP("FullscreenUI", "Adjusts target texture offsets.");
@ -9476,7 +9614,7 @@ TRANSLATE_NOOP("FullscreenUI", "Controls the volume of the audio played on the h
TRANSLATE_NOOP("FullscreenUI", "Controls the volume of the audio played on the host when fast forwarding.");
TRANSLATE_NOOP("FullscreenUI", "Prevents the emulator from producing any audible sound.");
TRANSLATE_NOOP("FullscreenUI", "Backend Settings");
TRANSLATE_NOOP("FullscreenUI", "The audio backend determines how frames produced by the emulator are submitted to the host.");
TRANSLATE_NOOP("FullscreenUI", "Determines how audio frames produced by the emulator are submitted to the host.");
TRANSLATE_NOOP("FullscreenUI", "Determines how audio is expanded from stereo to surround for supported games.");
TRANSLATE_NOOP("FullscreenUI", "Changes when SPU samples are generated relative to system emulation.");
TRANSLATE_NOOP("FullscreenUI", "Determines the amount of audio buffered before being pulled by the host API.");
@ -9570,7 +9708,7 @@ TRANSLATE_NOOP("FullscreenUI", "Runs VU1 instantly. Provides a modest speed impr
TRANSLATE_NOOP("FullscreenUI", "I/O Processor");
TRANSLATE_NOOP("FullscreenUI", "Enable IOP Recompiler");
TRANSLATE_NOOP("FullscreenUI", "Performs just-in-time binary translation of 32-bit MIPS-I machine code to native code.");
TRANSLATE_NOOP("FullscreenUI", "Savestate");
TRANSLATE_NOOP("FullscreenUI", "Save State Management");
TRANSLATE_NOOP("FullscreenUI", "Sets the compression algorithm for savestate.");
TRANSLATE_NOOP("FullscreenUI", "Sets the compression level for savestate.");
TRANSLATE_NOOP("FullscreenUI", "Graphics");
@ -9600,7 +9738,7 @@ TRANSLATE_NOOP("FullscreenUI", "Good for cache emulation problems. Known to affe
TRANSLATE_NOOP("FullscreenUI", "OPH Flag Hack");
TRANSLATE_NOOP("FullscreenUI", "Known to affect following games: Bleach Blade Battlers, Growlanser II and III, Wizardry.");
TRANSLATE_NOOP("FullscreenUI", "Emulate GIF FIFO");
TRANSLATE_NOOP("FullscreenUI", "Correct but slower. Known to affect the following games: Fifa Street 2.");
TRANSLATE_NOOP("FullscreenUI", "Correct but slower. Known to affect the following games: FIFA Street 2.");
TRANSLATE_NOOP("FullscreenUI", "DMA Busy Hack");
TRANSLATE_NOOP("FullscreenUI", "Known to affect following games: Mana Khemia 1, Metal Saga, Pilot Down Behind Enemy Lines.");
TRANSLATE_NOOP("FullscreenUI", "Delay VIF1 Stalls");
@ -9679,7 +9817,7 @@ TRANSLATE_NOOP("FullscreenUI", "Automatic mapping completed for {}.");
TRANSLATE_NOOP("FullscreenUI", "Automatic mapping failed for {}.");
TRANSLATE_NOOP("FullscreenUI", "Game settings initialized with global settings for '{}'.");
TRANSLATE_NOOP("FullscreenUI", "Game settings have been cleared for '{}'.");
TRANSLATE_NOOP("FullscreenUI", "Uses {} as confirm when using a controller");
TRANSLATE_NOOP("FullscreenUI", "Uses {} as confirm when using a controller.");
TRANSLATE_NOOP("FullscreenUI", "Swaps both {}/{} (When Swap OK/Cancel is set to automatic) and {}/{} buttons");
TRANSLATE_NOOP("FullscreenUI", "Slot {}");
TRANSLATE_NOOP("FullscreenUI", "{} (Current)");
@ -9715,6 +9853,9 @@ TRANSLATE_NOOP("FullscreenUI", "Last Played: {}");
TRANSLATE_NOOP("FullscreenUI", "Size: {:.2f} MB");
TRANSLATE_NOOP("FullscreenUI", "Are you sure you want to reset the play time for '{}' ({})?\n\nYour current play time is {}.\n\nThis action cannot be undone.");
TRANSLATE_NOOP("FullscreenUI", "Login failed.\nError: {}\n\nPlease check your username and password, and try again.");
TRANSLATE_NOOP("FullscreenUI", "Failed to Load State From Backup Slot {}");
TRANSLATE_NOOP("FullscreenUI", "Failed to Load State From Slot {}");
TRANSLATE_NOOP("FullscreenUI", "Failed to Save State To Slot {}");
TRANSLATE_NOOP("FullscreenUI", "Left: ");
TRANSLATE_NOOP("FullscreenUI", "Top: ");
TRANSLATE_NOOP("FullscreenUI", "Right: ");
@ -9748,13 +9889,14 @@ TRANSLATE_NOOP("FullscreenUI", "AMOLED");
TRANSLATE_NOOP("FullscreenUI", "Fit");
TRANSLATE_NOOP("FullscreenUI", "Fill");
TRANSLATE_NOOP("FullscreenUI", "Stretch");
TRANSLATE_NOOP("FullscreenUI", "Center");
TRANSLATE_NOOP("FullscreenUI", "Tile");
TRANSLATE_NOOP("FullscreenUI", "Enabled");
TRANSLATE_NOOP("FullscreenUI", "Disabled");
TRANSLATE_NOOP("FullscreenUI", "Top Left");
TRANSLATE_NOOP("FullscreenUI", "Top Center");
TRANSLATE_NOOP("FullscreenUI", "Top Right");
TRANSLATE_NOOP("FullscreenUI", "Center Left");
TRANSLATE_NOOP("FullscreenUI", "Center");
TRANSLATE_NOOP("FullscreenUI", "Center Right");
TRANSLATE_NOOP("FullscreenUI", "Bottom Left");
TRANSLATE_NOOP("FullscreenUI", "Bottom Center");
@ -9854,9 +9996,9 @@ TRANSLATE_NOOP("FullscreenUI", "Accurate (Recommended)");
TRANSLATE_NOOP("FullscreenUI", "Disable Readbacks (Synchronize GS Thread)");
TRANSLATE_NOOP("FullscreenUI", "Unsynchronized (Non-Deterministic)");
TRANSLATE_NOOP("FullscreenUI", "Disabled (Ignore Transfers)");
TRANSLATE_NOOP("FullscreenUI", "Screen Resolution");
TRANSLATE_NOOP("FullscreenUI", "Internal Resolution");
TRANSLATE_NOOP("FullscreenUI", "Internal Resolution (Aspect Uncorrected)");
TRANSLATE_NOOP("FullscreenUI", "Display Resolution (Aspect Corrected)");
TRANSLATE_NOOP("FullscreenUI", "Internal Resolution (Aspect Corrected)");
TRANSLATE_NOOP("FullscreenUI", "Internal Resolution (No Aspect Correction)");
TRANSLATE_NOOP("FullscreenUI", "PNG");
TRANSLATE_NOOP("FullscreenUI", "JPEG");
TRANSLATE_NOOP("FullscreenUI", "WebP");
@ -9974,6 +10116,7 @@ TRANSLATE_NOOP("FullscreenUI", "Pause On Start");
TRANSLATE_NOOP("FullscreenUI", "Pause On Focus Loss");
TRANSLATE_NOOP("FullscreenUI", "Pause On Controller Disconnection");
TRANSLATE_NOOP("FullscreenUI", "Pause On Menu");
TRANSLATE_NOOP("FullscreenUI", "Prompt On State Load/Save Failure");
TRANSLATE_NOOP("FullscreenUI", "Confirm Shutdown");
TRANSLATE_NOOP("FullscreenUI", "Save State On Shutdown");
TRANSLATE_NOOP("FullscreenUI", "Create Save State Backups");
@ -9983,6 +10126,7 @@ TRANSLATE_NOOP("FullscreenUI", "Enable Discord Presence");
TRANSLATE_NOOP("FullscreenUI", "Start Fullscreen");
TRANSLATE_NOOP("FullscreenUI", "Double-Click Toggles Fullscreen");
TRANSLATE_NOOP("FullscreenUI", "Hide Cursor In Fullscreen");
TRANSLATE_NOOP("FullscreenUI", "Start Big Picture UI");
TRANSLATE_NOOP("FullscreenUI", "OSD Scale");
TRANSLATE_NOOP("FullscreenUI", "OSD Messages Position");
TRANSLATE_NOOP("FullscreenUI", "OSD Performance Position");
@ -10037,6 +10181,7 @@ TRANSLATE_NOOP("FullscreenUI", "Integer Upscaling");
TRANSLATE_NOOP("FullscreenUI", "Screen Offsets");
TRANSLATE_NOOP("FullscreenUI", "Show Overscan");
TRANSLATE_NOOP("FullscreenUI", "Anti-Blur");
TRANSLATE_NOOP("FullscreenUI", "Internal Resolution");
TRANSLATE_NOOP("FullscreenUI", "Bilinear Filtering");
TRANSLATE_NOOP("FullscreenUI", "Trilinear Filtering");
TRANSLATE_NOOP("FullscreenUI", "Anisotropic Filtering");
@ -10162,7 +10307,6 @@ TRANSLATE_NOOP("FullscreenUI", "Delete Save");
TRANSLATE_NOOP("FullscreenUI", "Close Menu");
TRANSLATE_NOOP("FullscreenUI", "Default Boot");
TRANSLATE_NOOP("FullscreenUI", "Delete State");
TRANSLATE_NOOP("FullscreenUI", "Failed to Load State");
TRANSLATE_NOOP("FullscreenUI", "Full Boot");
TRANSLATE_NOOP("FullscreenUI", "Reset Play Time");
TRANSLATE_NOOP("FullscreenUI", "Confirm Reset");
@ -10207,7 +10351,7 @@ TRANSLATE_NOOP("FullscreenUI", "Not Logged In");
TRANSLATE_NOOP("FullscreenUI", "Game: {0} ({1})");
TRANSLATE_NOOP("FullscreenUI", "Rich presence inactive or unsupported.");
TRANSLATE_NOOP("FullscreenUI", "Game not loaded or no RetroAchievements available.");
TRANSLATE_NOOP("FullscreenUI", "Card Enabled");
TRANSLATE_NOOP("FullscreenUI", "Memory Card Enabled");
TRANSLATE_NOOP("FullscreenUI", "Card Name");
TRANSLATE_NOOP("FullscreenUI", "Eject Card");
// TRANSLATION-STRING-AREA-END

View File

@ -10,6 +10,7 @@
#include <ctime>
#include <string>
#include <memory>
#include <optional>
struct Pcsx2Config;
@ -26,6 +27,8 @@ namespace FullscreenUI
void OpenPauseMenu();
bool OpenAchievementsWindow();
bool OpenLeaderboardsWindow();
void ReportStateLoadError(const std::string& message, std::optional<s32> slot, bool backup);
void ReportStateSaveError(const std::string& message, std::optional<s32> slot);
// NOTE: Only call from GS thread.
bool IsAchievementsWindowOpen();

View File

@ -2396,6 +2396,7 @@ void ImGuiFullscreen::DrawChoiceDialog()
ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor);
ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor);
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor);
ImGui::PushStyleColor(ImGuiCol_PopupBg, UIPopupBackgroundColor);
const float width = LayoutScale(600.0f);
const float title_height = g_large_font.second + ImGui::GetStyle().FramePadding.y * 2.0f + ImGui::GetStyle().WindowPadding.y * 2.0f;
@ -2464,7 +2465,7 @@ void ImGuiFullscreen::DrawChoiceDialog()
is_open = false;
}
ImGui::PopStyleColor(3);
ImGui::PopStyleColor(4);
ImGui::PopStyleVar(3);
ImGui::PopFont();
@ -2523,7 +2524,7 @@ void ImGuiFullscreen::DrawInputDialog()
ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor);
ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor);
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor);
ImGui::PushStyleColor(ImGuiCol_PopupBg, UIBackgroundColor);
ImGui::PushStyleColor(ImGuiCol_PopupBg, UIPopupBackgroundColor);
bool is_open = true;
if (ImGui::BeginPopupModal(s_input_dialog_title.c_str(), &is_open,
@ -2715,6 +2716,7 @@ void ImGuiFullscreen::DrawMessageDialog()
ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor);
ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor);
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor);
ImGui::PushStyleColor(ImGuiCol_PopupBg, UIPopupBackgroundColor);
bool is_open = true;
const u32 flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
@ -2745,7 +2747,7 @@ void ImGuiFullscreen::DrawMessageDialog()
ImGui::EndPopup();
}
ImGui::PopStyleColor(3);
ImGui::PopStyleColor(4);
ImGui::PopStyleVar(4);
ImGui::PopFont();

View File

@ -1382,8 +1382,7 @@ void SaveStateSelectorUI::LoadCurrentSlot()
Host::RunOnCPUThread([slot = GetCurrentSlot()]() {
Error error;
if (!VMManager::LoadStateFromSlot(slot, false, &error))
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_TRIANGLE_EXCLAMATION,
error.GetDescription(), Host::OSD_INFO_DURATION);
FullscreenUI::ReportStateLoadError(error.GetDescription(), slot, false);
});
Close();
}
@ -1393,8 +1392,7 @@ void SaveStateSelectorUI::LoadCurrentBackupSlot()
Host::RunOnCPUThread([slot = GetCurrentSlot()]() {
Error error;
if (!VMManager::LoadStateFromSlot(slot, true, &error))
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_TRIANGLE_EXCLAMATION,
error.GetDescription(), Host::OSD_INFO_DURATION);
FullscreenUI::ReportStateLoadError(error.GetDescription(), slot, true);
});
Close();
}
@ -1402,7 +1400,9 @@ void SaveStateSelectorUI::LoadCurrentBackupSlot()
void SaveStateSelectorUI::SaveCurrentSlot()
{
Host::RunOnCPUThread([slot = GetCurrentSlot()]() {
VMManager::SaveStateToSlot(slot);
VMManager::SaveStateToSlot(slot, true, [slot](const std::string& error) {
FullscreenUI::ReportStateSaveError(error, slot);
});
});
Close();
}

View File

@ -765,9 +765,9 @@ std::optional<InputBindingKey> SDLInputSource::ParseKeyString(const std::string_
{
shown_prompt = true;
Host::ReportInfoAsync(TRANSLATE("SDLInputSource", "SDL3 Migration"),
TRANSLATE("SDLInputSource", "As part of our upgrade to SDL3, we've had to migrate your binds\n"
"Your controller did not match the Xbox layout and may need rebinding\n"
"Please verify your controller settings and amend if required"));
TRANSLATE("SDLInputSource", "As part of our upgrade to SDL3, we've had to migrate your binds.\n"
"Your controller did not match the Xbox layout and may need rebinding.\n"
"Please verify your controller settings and amend if required."));
// Also apply BPM setting for legacy binds
// We assume this is a Nintendo controller, BPM will check if it is

View File

@ -6,6 +6,7 @@
#include "Host.h"
#include "Memory.h"
#include "Elfheader.h"
#include "SaveState.h"
#include "PINE.h"
#include "VMManager.h"
#include "common/Error.h"
@ -19,7 +20,6 @@
#include <thread>
#include "fmt/format.h"
#include "IconsFontAwesome6.h"
#if defined(_WIN32)
#define read_portable(a, b, c) (recv(a, (char*)b, c, 0))
@ -646,7 +646,11 @@ PINEServer::IPCBuffer PINEServer::ParseCommand(std::span<u8> buf, std::vector<u8
goto error;
if (!SafetyChecks(buf_cnt, 1, ret_cnt, 0, buf_size)) [[unlikely]]
goto error;
Host::RunOnCPUThread([slot = FromSpan<u8>(buf, buf_cnt)] { VMManager::SaveStateToSlot(slot); });
Host::RunOnCPUThread([slot = FromSpan<u8>(buf, buf_cnt)] {
VMManager::SaveStateToSlot(slot, true, [slot](const std::string& error) {
SaveState_ReportSaveErrorOSD(error, slot);
});
});
buf_cnt += 1;
break;
}
@ -659,8 +663,7 @@ PINEServer::IPCBuffer PINEServer::ParseCommand(std::span<u8> buf, std::vector<u8
Host::RunOnCPUThread([slot = FromSpan<u8>(buf, buf_cnt)] {
Error state_error;
if (!VMManager::LoadStateFromSlot(slot, false, &state_error))
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_TRIANGLE_EXCLAMATION,
state_error.GetDescription(), Host::OSD_INFO_DURATION);
SaveState_ReportLoadErrorOSD(state_error.GetDescription(), slot, false);
});
buf_cnt += 1;
break;

View File

@ -51,7 +51,7 @@ namespace Patch
BYTES_T
};
static constexpr std::array<const char*, 3> s_place_to_string = {{"0", "1", "2"}};
static constexpr std::array<const char*, 4> s_place_to_string = {{"0", "1", "2", "3"}};
static constexpr std::array<const char*, 2> s_cpu_to_string = {{"EE", "IOP"}};
static constexpr std::array<const char*, 9> s_type_to_string = {
{"byte", "short", "word", "double", "extended", "beshort", "beword", "bedouble", "bytes"}};
@ -125,10 +125,6 @@ namespace Patch
void (*func)(PatchGroup* group, const std::string_view cmd, const std::string_view param);
};
using PatchList = std::vector<PatchGroup>;
using ActivePatchList = std::vector<const PatchCommand*>;
using EnablePatchList = std::vector<std::string>;
namespace PatchFunc
{
static void patch(PatchGroup* group, const std::string_view cmd, const std::string_view param);
@ -141,23 +137,23 @@ namespace Patch
static int PatchTableExecute(PatchGroup* group, const std::string_view lhs, const std::string_view rhs,
const std::span<const PatchTextTable>& Table);
static void LoadPatchLine(PatchGroup* group, const std::string_view line);
static u32 LoadPatchesFromString(PatchList* patch_list, const std::string& patch_file);
static u32 LoadPatchesFromString(std::vector<PatchGroup>* patch_list, const std::string& patch_file);
static bool OpenPatchesZip();
static std::string GetPnachTemplate(
const std::string_view serial, u32 crc, bool include_serial, bool add_wildcard, bool all_crcs);
static std::vector<std::string> FindPatchFilesOnDisk(
const std::string_view serial, u32 crc, bool cheats, bool all_crcs);
static bool ContainsPatchName(const PatchInfoList& patches, const std::string_view patchName);
static bool ContainsPatchName(const PatchList& patches, const std::string_view patchName);
static bool ContainsPatchName(const std::vector<PatchInfo>& patches, const std::string_view patchName);
static bool ContainsPatchName(const std::vector<PatchGroup>& patches, const std::string_view patchName);
template <typename F>
static void EnumeratePnachFiles(const std::string_view serial, u32 crc, bool cheats, bool for_ui, const F& f);
static bool PatchStringHasUnlabelledPatch(const std::string& pnach_data);
static void ExtractPatchInfo(PatchInfoList* dst, const std::string& pnach_data, u32* num_unlabelled_patches);
static void ExtractPatchInfo(std::vector<PatchInfo>* dst, const std::string& pnach_data, u32* num_unlabelled_patches);
static void ReloadEnabledLists();
static u32 EnablePatches(const PatchList& patches, const EnablePatchList& enable_list, const EnablePatchList& enable_immediately_list);
static u32 EnablePatches(const std::vector<PatchGroup>* patches, const std::vector<std::string>& enable_list, const std::vector<std::string>* enable_immediately_list);
static void ApplyPatch(const PatchCommand* p);
static void ApplyDynaPatch(const DynamicPatch& patch, u32 address);
@ -175,21 +171,21 @@ namespace Patch
const char* PATCH_DISABLE_CONFIG_KEY = "Disable";
static zip_t* s_patches_zip;
static PatchList s_gamedb_patches;
static PatchList s_game_patches;
static PatchList s_cheat_patches;
static std::vector<PatchGroup> s_gamedb_patches;
static std::vector<PatchGroup> s_game_patches;
static std::vector<PatchGroup> s_cheat_patches;
static u32 s_gamedb_counts = 0;
static u32 s_patches_counts = 0;
static u32 s_cheats_counts = 0;
static ActivePatchList s_active_patches;
static std::vector<const PatchCommand*> s_active_patches;
static std::vector<DynamicPatch> s_active_gamedb_dynamic_patches;
static std::vector<DynamicPatch> s_active_pnach_dynamic_patches;
static EnablePatchList s_enabled_cheats;
static EnablePatchList s_enabled_patches;
static EnablePatchList s_just_enabled_cheats;
static EnablePatchList s_just_enabled_patches;
static std::vector<std::string> s_enabled_cheats;
static std::vector<std::string> s_enabled_patches;
static std::vector<std::string> s_just_enabled_cheats;
static std::vector<std::string> s_just_enabled_patches;
static u32 s_patches_crc;
static std::optional<float> s_override_aspect_ratio;
static std::optional<GSInterlaceMode> s_override_interlace_mode;
@ -218,7 +214,7 @@ void Patch::TrimPatchLine(std::string& buffer)
buffer.erase(pos);
}
bool Patch::ContainsPatchName(const PatchList& patch_list, const std::string_view patch_name)
bool Patch::ContainsPatchName(const std::vector<PatchGroup>& patch_list, const std::string_view patch_name)
{
return std::find_if(patch_list.begin(), patch_list.end(), [&patch_name](const PatchGroup& patch) {
return patch.name == patch_name;
@ -253,7 +249,7 @@ void Patch::LoadPatchLine(PatchGroup* group, const std::string_view line)
PatchTableExecute(group, key, value, s_patch_commands);
}
u32 Patch::LoadPatchesFromString(PatchList* patch_list, const std::string& patch_file)
u32 Patch::LoadPatchesFromString(std::vector<PatchGroup>* patch_list, const std::string& patch_file)
{
const size_t before = patch_list->size();
@ -264,7 +260,7 @@ u32 Patch::LoadPatchesFromString(PatchList* patch_list, const std::string& patch
// Ungrouped/legacy patches should merge with other ungrouped patches.
if (current_patch_group.name.empty())
{
const PatchList::iterator ungrouped_patch = std::find_if(patch_list->begin(), patch_list->end(),
const std::vector<PatchGroup>::iterator ungrouped_patch = std::find_if(patch_list->begin(), patch_list->end(),
[](const PatchGroup& pg) { return pg.name.empty(); });
if (ungrouped_patch != patch_list->end())
{
@ -407,7 +403,7 @@ std::vector<std::string> Patch::FindPatchFilesOnDisk(const std::string_view seri
return ret;
}
bool Patch::ContainsPatchName(const PatchInfoList& patches, const std::string_view patchName)
bool Patch::ContainsPatchName(const std::vector<PatchInfo>& patches, const std::string_view patchName)
{
return std::find_if(patches.begin(), patches.end(), [&patchName](const PatchInfo& patch) {
return patch.name == patchName;
@ -491,11 +487,15 @@ bool Patch::PatchStringHasUnlabelledPatch(const std::string& pnach_data)
return false;
}
void Patch::ExtractPatchInfo(PatchInfoList* dst, const std::string& pnach_data, u32* num_unlabelled_patches)
void Patch::ExtractPatchInfo(std::vector<PatchInfo>* dst, const std::string& pnach_data, u32* num_unlabelled_patches)
{
std::istringstream ss(pnach_data);
std::string line;
PatchInfo current_patch;
std::optional<patch_place_type> last_place;
bool unknown_place = false;
while (std::getline(ss, line))
{
TrimPatchLine(line);
@ -522,6 +522,8 @@ void Patch::ExtractPatchInfo(PatchInfoList* dst, const std::string& pnach_data,
}
}
current_patch = {};
last_place = std::nullopt;
unknown_place = false;
}
current_patch.name = line.substr(1, line.length() - 2);
@ -534,13 +536,52 @@ void Patch::ExtractPatchInfo(PatchInfoList* dst, const std::string& pnach_data,
// Just ignore other directives, who knows what rubbish people have in here.
// Use comment for description if it hasn't been otherwise specified.
if (key == "author")
{
current_patch.author = value;
}
else if (key == "description")
{
current_patch.description = value;
}
else if (key == "comment" && current_patch.description.empty())
{
current_patch.description = value;
else if (key == "patch" && !has_patch && num_unlabelled_patches)
}
else if (key == "patch")
{
if (!has_patch && num_unlabelled_patches)
(*num_unlabelled_patches)++;
// Try to extract the place value of the patch lines so we can
// display it in the GUI if they all match. TODO: Don't duplicate
// all this parsing logic twice.
if (unknown_place)
continue;
std::string::size_type comma_pos = value.find(",");
if (comma_pos == std::string::npos)
comma_pos = 0;
const std::string_view padded_place = value.substr(0, comma_pos);
const std::string_view place_string = StringUtil::StripWhitespace(padded_place);
const std::optional<patch_place_type> place = LookupEnumName<patch_place_type>(
place_string, s_place_to_string);
if (!place.has_value() || (last_place.has_value() && place != last_place))
{
// This group contains patch lines with different or invalid
// place values.
current_patch.place = std::nullopt;
unknown_place = true;
continue;
}
current_patch.place = place;
last_place = place;
}
else if (key == "dpatch")
{
current_patch.place = std::nullopt;
unknown_place = true;
}
}
// Last one.
@ -570,9 +611,9 @@ std::string_view Patch::PatchInfo::GetNameParentPart() const
return ret;
}
Patch::PatchInfoList Patch::GetPatchInfo(const std::string_view serial, u32 crc, bool cheats, bool showAllCRCS, u32* num_unlabelled_patches)
std::vector<Patch::PatchInfo> Patch::GetPatchInfo(const std::string_view serial, u32 crc, bool cheats, bool showAllCRCS, u32* num_unlabelled_patches)
{
PatchInfoList ret;
std::vector<PatchInfo> ret;
if (num_unlabelled_patches)
*num_unlabelled_patches = 0;
@ -592,14 +633,14 @@ std::string Patch::GetPnachFilename(const std::string_view serial, u32 crc, bool
void Patch::ReloadEnabledLists()
{
const EnablePatchList prev_enabled_cheats = std::move(s_enabled_cheats);
const std::vector<std::string> prev_enabled_cheats = std::move(s_enabled_cheats);
if (EmuConfig.EnableCheats && !Achievements::IsHardcoreModeActive())
s_enabled_cheats = Host::GetStringListSetting(CHEATS_CONFIG_SECTION, PATCH_ENABLE_CONFIG_KEY);
else
s_enabled_cheats = {};
const EnablePatchList prev_enabled_patches = std::exchange(s_enabled_patches, Host::GetStringListSetting(PATCHES_CONFIG_SECTION, PATCH_ENABLE_CONFIG_KEY));
const EnablePatchList disabled_patches = Host::GetStringListSetting(PATCHES_CONFIG_SECTION, PATCH_DISABLE_CONFIG_KEY);
const std::vector<std::string> prev_enabled_patches = std::exchange(s_enabled_patches, Host::GetStringListSetting(PATCHES_CONFIG_SECTION, PATCH_ENABLE_CONFIG_KEY));
const std::vector<std::string> disabled_patches = Host::GetStringListSetting(PATCHES_CONFIG_SECTION, PATCH_DISABLE_CONFIG_KEY);
// Name based matching for widescreen/NI settings.
if (EmuConfig.EnableWideScreenPatches)
@ -649,12 +690,10 @@ void Patch::ReloadEnabledLists()
}
}
u32 Patch::EnablePatches(const PatchList& patches, const EnablePatchList& enable_list, const EnablePatchList& enable_immediately_list)
u32 Patch::EnablePatches(const std::vector<PatchGroup>* patches, const std::vector<std::string>& enable_list, const std::vector<std::string>* enable_immediately_list)
{
ActivePatchList patches_to_apply_immediately;
u32 count = 0;
for (const PatchGroup& p : patches)
for (const PatchGroup& p : *patches)
{
// For compatibility, we auto enable anything that's not labelled.
// Also for gamedb patches.
@ -664,7 +703,6 @@ u32 Patch::EnablePatches(const PatchList& patches, const EnablePatchList& enable
Console.WriteLn(Color_Green, fmt::format("Enabled patch: {}",
p.name.empty() ? std::string_view("<unknown>") : std::string_view(p.name)));
const bool apply_immediately = std::find(enable_immediately_list.begin(), enable_immediately_list.end(), p.name) != enable_immediately_list.end();
for (const PatchCommand& ip : p.patches)
{
// print the actual patch lines only in verbose mode (even in devel)
@ -672,8 +710,6 @@ u32 Patch::EnablePatches(const PatchList& patches, const EnablePatchList& enable
DevCon.WriteLnFmt(" {}", ip.ToString());
s_active_patches.push_back(&ip);
if (apply_immediately && ip.placetopatch == PPT_ONCE_ON_LOAD)
patches_to_apply_immediately.push_back(&ip);
}
for (const DynamicPatch& dp : p.dpatches)
@ -690,12 +726,28 @@ u32 Patch::EnablePatches(const PatchList& patches, const EnablePatchList& enable
count += p.name.empty() ? (static_cast<u32>(p.patches.size()) + static_cast<u32>(p.dpatches.size())) : 1;
}
if (!patches_to_apply_immediately.empty())
// Apply PPT_ON_LOAD_OR_WHEN_ENABLED patches immediately.
if (enable_immediately_list && !enable_immediately_list->empty())
{
Host::RunOnCPUThread([patches = std::move(patches_to_apply_immediately)]() {
for (const PatchCommand* i : patches)
// Don't pass pointers to patch objects themselves here just in case the
// patches are reloaded twice in a row before this event makes it.
Host::RunOnCPUThread([patches, enable_immediately_list]() {
for (const PatchGroup& group : *patches)
{
ApplyPatch(i);
const bool apply_immediately = std::find(
enable_immediately_list->begin(),
enable_immediately_list->end(),
group.name) != enable_immediately_list->end();
if (!apply_immediately)
continue;
for (const PatchCommand& command : group.patches)
{
if (command.placetopatch != PPT_ON_LOAD_OR_WHEN_ENABLED)
continue;
ApplyPatch(&command);
}
}
});
}
@ -762,19 +814,23 @@ void Patch::UpdateActivePatches(bool reload_enabled_list, bool verbose, bool ver
u32 gp_count = 0;
if (EmuConfig.EnablePatches)
{
gp_count = EnablePatches(s_gamedb_patches, EnablePatchList(), EnablePatchList());
gp_count = EnablePatches(&s_gamedb_patches, std::vector<std::string>(), nullptr);
s_gamedb_counts = gp_count;
if (gp_count > 0)
message.append(TRANSLATE_PLURAL_STR("Patch", "%n GameDB patches are active.", "OSD Message", gp_count));
}
const u32 p_count = EnablePatches(s_game_patches, s_enabled_patches, apply_new_patches ? s_just_enabled_patches : EnablePatchList());
const u32 p_count = EnablePatches(
&s_game_patches, s_enabled_patches, apply_new_patches ? &s_just_enabled_patches : nullptr);
s_patches_counts = p_count;
if (p_count > 0)
message.append_format("{}{}", message.empty() ? "" : "\n",
TRANSLATE_PLURAL_STR("Patch", "%n game patches are active.", "OSD Message", p_count));
const u32 c_count = EmuConfig.EnableCheats ? EnablePatches(s_cheat_patches, s_enabled_cheats, apply_new_patches ? s_just_enabled_cheats : EnablePatchList()) : 0;
u32 c_count = 0;
if (EmuConfig.EnableCheats)
c_count = EnablePatches(
&s_cheat_patches, s_enabled_cheats, apply_new_patches ? &s_just_enabled_cheats : nullptr);
s_cheats_counts = c_count;
if (c_count > 0)
message.append_format("{}{}", message.empty() ? "" : "\n",
@ -892,7 +948,7 @@ void Patch::PatchFunc::patch(PatchGroup* group, const std::string_view cmd, cons
if (!placetopatch.has_value())
{
PATCH_ERROR("Invalid 'place' value '{}' (0 - once on startup, 1: continuously)", pieces[0]);
PATCH_ERROR("Invalid 'place' value '{}' (0: on boot only, 1: continuously, 2: on boot and continuously, 3: on boot and when enabled in the GUI)", pieces[0]);
return;
}
if (!addr.has_value() || !addr_end.empty())
@ -1083,6 +1139,19 @@ void Patch::PatchFunc::dpatch(PatchGroup* group, const std::string_view cmd, con
group->dpatches.push_back(dpatch);
}
void Patch::ApplyBootPatches()
{
ApplyLoadedPatches(PPT_ONCE_ON_LOAD);
ApplyLoadedPatches(PPT_COMBINED_0_1);
ApplyLoadedPatches(PPT_ON_LOAD_OR_WHEN_ENABLED);
}
void Patch::ApplyVsyncPatches()
{
ApplyLoadedPatches(PPT_CONTINUOUSLY);
ApplyLoadedPatches(PPT_COMBINED_0_1);
}
// This is for applying patches directly to memory
void Patch::ApplyLoadedPatches(patch_place_type place)
{
@ -1731,3 +1800,31 @@ void Patch::ApplyDynaPatch(const DynamicPatch& patch, u32 address)
memWrite32(address + replacement.offset, replacement.value);
}
}
const char* Patch::PlaceToString(std::optional<patch_place_type> place)
{
if (!place.has_value())
//: Time when a patch is applied.
return TRANSLATE("Patch", "Unknown");
switch (*place)
{
case Patch::PPT_ONCE_ON_LOAD:
//: Time when a patch is applied.
return TRANSLATE("Patch", "Only On Startup");
case Patch::PPT_CONTINUOUSLY:
//: Time when a patch is applied.
return TRANSLATE("Patch", "Every Frame");
case Patch::PPT_COMBINED_0_1:
//: Time when a patch is applied.
return TRANSLATE("Patch", "On Startup & Every Frame");
case Patch::PPT_ON_LOAD_OR_WHEN_ENABLED:
//: Time when a patch is applied.
return TRANSLATE("Patch", "On Startup & When Enabled");
default:
{
}
}
return "";
}

View File

@ -29,23 +29,22 @@ namespace Patch
// In PCSX2 it indicates how/when/where the patch line should be applied. If
// place is not one of the supported values then the patch line is never applied.
// PCSX2 currently supports the following values:
// 0 - apply the patch line once on game boot/startup
// 0 - apply the patch line once on game boot only
// 1 - apply the patch line continuously (technically - on every vsync)
// 2 - effect of 0 and 1 combined, see below
// 3 - apply the patch line once on game boot or when enabled in the GUI
// Note:
// - while it may seem that a value of 1 does the same as 0, but also later
// continues to apply the patch on every vsync - it's not.
// The current (and past) behavior is that these patches are applied at different
// places at the code, and it's possible, depending on circumstances, that 0 patches
// will get applied before the first vsync and therefore earlier than 1 patches.
// - There's no "place" value which indicates to apply both once on startup
// and then also continuously, however such behavior can be achieved by
// duplicating the line where one has a 0 place and the other has a 1 place.
enum patch_place_type : u8
{
PPT_ONCE_ON_LOAD = 0,
PPT_CONTINUOUSLY = 1,
PPT_COMBINED_0_1 = 2,
PPT_ON_LOAD_OR_WHEN_ENABLED = 3,
PPT_END_MARKER
};
@ -56,12 +55,14 @@ namespace Patch
std::string description;
std::string author;
// This is only populated if all the patch lines in a given group have
// the same place value.
std::optional<patch_place_type> place;
std::string_view GetNamePart() const;
std::string_view GetNameParentPart() const;
};
using PatchInfoList = std::vector<PatchInfo>;
struct DynamicPatchEntry
{
u32 offset;
@ -80,7 +81,7 @@ namespace Patch
extern const char* PATCH_ENABLE_CONFIG_KEY;
extern const char* PATCH_DISABLE_CONFIG_KEY;
extern PatchInfoList GetPatchInfo(const std::string_view serial, u32 crc, bool cheats, bool showAllCRCS, u32* num_unlabelled_patches);
extern std::vector<PatchInfo> GetPatchInfo(const std::string_view serial, u32 crc, bool cheats, bool showAllCRCS, u32* num_unlabelled_patches);
/// Returns the path to a new cheat/patch pnach for the specified serial and CRC.
extern std::string GetPnachFilename(const std::string_view serial, u32 crc, bool cheats);
@ -97,6 +98,13 @@ namespace Patch
extern void LoadDynamicPatches(const std::vector<DynamicPatch>& patches);
extern void ApplyDynamicPatches(u32 pc);
// Apply all loaded patches that should be applied when the entry point is
// being recompiled.
extern void ApplyBootPatches();
// Apply all loaded patches that should be applied during vsync.
extern void ApplyVsyncPatches();
// Patches the emulation memory by applying all the loaded patches with a specific place value.
// Note: unless you know better, there's no need to check whether or not different patch sources
// are enabled (e.g. ws patches, auto game fixes, etc) before calling ApplyLoadedPatches,
@ -112,4 +120,6 @@ namespace Patch
extern u32 GetAllActivePatchesCount();
extern bool IsGloballyToggleablePatch(const PatchInfo& patch_info);
extern const char* PlaceToString(std::optional<patch_place_type> place);
} // namespace Patch

View File

@ -56,8 +56,9 @@ bool InputRecording::create(const std::string& fileName, const bool fromSaveStat
m_initial_load_complete = true;
m_watching_for_rerecords = true;
setStartingFrame(g_FrameCount);
// TODO - error handling
VMManager::SaveState(savestatePath.c_str());
VMManager::SaveState(savestatePath.c_str(), true, false, [](const std::string& error) {
SaveState_ReportSaveErrorOSD(error, std::nullopt);
});
}
else
{
@ -93,7 +94,7 @@ bool InputRecording::play(const std::string& filename)
if (!FileSystem::FileExists(savestatePath.c_str()))
{
InputRec::consoleLog(fmt::format("Could not locate savestate file at location - {}", savestatePath));
InputRec::log(TRANSLATE_STR("InputRecording", "Savestate load failed for input recording"), Host::OSD_ERROR_DURATION);
InputRec::log(TRANSLATE_STR("InputRecording", "Failed to load state for input recording"), Host::OSD_ERROR_DURATION);
m_file.close();
return false;
}
@ -103,7 +104,7 @@ bool InputRecording::play(const std::string& filename)
const auto loaded = VMManager::LoadState(savestatePath.c_str());
if (!loaded)
{
InputRec::log(TRANSLATE_STR("InputRecording", "Savestate load failed for input recording, unsupported version?"), Host::OSD_ERROR_DURATION);
InputRec::log(TRANSLATE_STR("InputRecording", "Failed to load state for input recording, unsupported version?"), Host::OSD_ERROR_DURATION);
m_file.close();
m_is_active = false;
return false;
@ -395,8 +396,7 @@ void InputRecording::InformGSThread()
TinyString frame_data_message = TinyString::from_format(TRANSLATE_FS("InputRecording", "Frame: {}/{} ({})"), g_InputRecording.getFrameCounter(), g_InputRecording.getData().getTotalFrames(), g_InputRecording.getFrameCounterStateless());
TinyString undo_count_message = TinyString::from_format(TRANSLATE_FS("InputRecording", "Undo Count: {}"), g_InputRecording.getData().getUndoCount());
MTGS::RunOnGSThread([recording_active_message, frame_data_message, undo_count_message](bool is_recording = g_InputRecording.getControls().isRecording())
{
MTGS::RunOnGSThread([recording_active_message, frame_data_message, undo_count_message](bool is_recording = g_InputRecording.getControls().isRecording()) {
g_InputRecordingData.is_recording = is_recording;
g_InputRecordingData.recording_active_message = recording_active_message;
g_InputRecordingData.frame_data_message = frame_data_message;

View File

@ -581,9 +581,10 @@ bool Pad::Freeze(StateWrapper& sw)
const auto& [port, slot] = sioConvertPadToPortAndSlot(unifiedSlot);
Host::AddIconOSDMessage(fmt::format("UnfreezePad{}Changed", unifiedSlot), ICON_FA_GAMEPAD,
//: {0} and {1} are the port and multitap slot, {2} and {3} are controller types (e.g. "DualShock 2", "Jogcon")
fmt::format(TRANSLATE_FS("Pad",
"Controller port {0}, slot {1} has a {2} connected, but the save state has a "
"{3}.\nEjecting {3} and replacing it with {2}."),
"{3}.\nEjecting {2} and replacing it with {3}."),
port, slot,
GetControllerTypeName(currentPad ? currentPad->GetType() : Pad::ControllerType::NotConnected),
GetControllerTypeName(statePadType)));

View File

@ -38,6 +38,7 @@
#include "common/StringUtil.h"
#include "common/ZipHelpers.h"
#include "IconsFontAwesome6.h"
#include "fmt/format.h"
#include <csetjmp>
@ -1038,14 +1039,18 @@ static bool SaveState_AddToZip(zip_t* zf, ArchiveEntryList* srclist, SaveStateSc
return true;
}
bool SaveState_ZipToDisk(std::unique_ptr<ArchiveEntryList> srclist, std::unique_ptr<SaveStateScreenshotData> screenshot, const char* filename)
bool SaveState_ZipToDisk(
std::unique_ptr<ArchiveEntryList> srclist, std::unique_ptr<SaveStateScreenshotData> screenshot,
const char* filename, Error* error)
{
zip_error_t ze = {};
zip_source_t* zs = zip_source_file_create(filename, 0, 0, &ze);
zip_t* zf = nullptr;
if (zs && !(zf = zip_open_from_source(zs, ZIP_CREATE | ZIP_TRUNCATE, &ze)))
{
Console.Error("Failed to open zip file '%s' for save state: %s", filename, zip_error_strerror(&ze));
Error::SetStringFmt(error,
TRANSLATE_FS("SaveState", "Failed to open zip file '{}' for save state: {}."),
filename, zip_error_strerror(&ze));
// have to clean up source
zip_source_free(zs);
@ -1055,13 +1060,21 @@ bool SaveState_ZipToDisk(std::unique_ptr<ArchiveEntryList> srclist, std::unique_
// discard zip file if we fail saving something
if (!SaveState_AddToZip(zf, srclist.get(), screenshot.get()))
{
Console.Error("Failed to save state to zip file '%s'", filename);
Error::SetStringFmt(error,
TRANSLATE_FS("SaveState", "Failed to save state to zip file '{}'."), filename);
zip_discard(zf);
return false;
}
// force the zip to close, this is the expensive part with libzip.
zip_close(zf);
if (zip_close(zf) != 0)
{
Error::SetStringFmt(error,
TRANSLATE_FS("SaveState", "Failed to save state to zip file '{}': {}."), filename, zip_strerror(zf));
zip_discard(zf);
return false;
}
return true;
}
@ -1231,3 +1244,37 @@ bool SaveState_UnzipFromDisk(const std::string& filename, Error* error)
PostLoadPrep();
return true;
}
void SaveState_ReportLoadErrorOSD(const std::string& message, std::optional<s32> slot, bool backup)
{
std::string full_message;
if (slot.has_value())
{
if (backup)
full_message = fmt::format(
TRANSLATE_FS("SaveState", "Failed to load state from backup slot {}: {}"), *slot, message);
else
full_message = fmt::format(
TRANSLATE_FS("SaveState", "Failed to load state from slot {}: {}"), *slot, message);
}
else
{
full_message = fmt::format(TRANSLATE_FS("SaveState", "Failed to load state: {}"), message);
}
Host::AddIconOSDMessage("LoadState", ICON_FA_TRIANGLE_EXCLAMATION,
full_message, Host::OSD_WARNING_DURATION);
}
void SaveState_ReportSaveErrorOSD(const std::string& message, std::optional<s32> slot)
{
std::string full_message;
if (slot.has_value())
full_message = fmt::format(
TRANSLATE_FS("SaveState", "Failed to save state to slot {}: {}"), *slot, message);
else
full_message = fmt::format(TRANSLATE_FS("SaveState", "Failed to save state: {}"), message);
Host::AddIconOSDMessage("SaveState", ICON_FA_TRIANGLE_EXCLAMATION,
full_message, Host::OSD_WARNING_DURATION);
}

View File

@ -5,6 +5,7 @@
#include <deque>
#include <memory>
#include <optional>
#include <string>
#include <vector>
@ -53,7 +54,9 @@ class ArchiveEntryList;
// These functions assume that the caller has paused the core thread.
extern std::unique_ptr<ArchiveEntryList> SaveState_DownloadState(Error* error);
extern std::unique_ptr<SaveStateScreenshotData> SaveState_SaveScreenshot();
extern bool SaveState_ZipToDisk(std::unique_ptr<ArchiveEntryList> srclist, std::unique_ptr<SaveStateScreenshotData> screenshot, const char* filename);
extern bool SaveState_ZipToDisk(
std::unique_ptr<ArchiveEntryList> srclist, std::unique_ptr<SaveStateScreenshotData> screenshot,
const char* filename, Error* error);
extern bool SaveState_ReadScreenshot(const std::string& filename, u32* out_width, u32* out_height, std::vector<u32>* out_pixels);
extern bool SaveState_UnzipFromDisk(const std::string& filename, Error* error);
@ -353,3 +356,6 @@ public:
void FreezeMem(void* data, int size) override;
bool IsSaving() const override { return false; }
};
void SaveState_ReportLoadErrorOSD(const std::string& message, std::optional<s32> slot, bool backup);
void SaveState_ReportSaveErrorOSD(const std::string& message, std::optional<s32> slot);

View File

@ -1243,7 +1243,7 @@ namespace usb_msd
{
static constexpr const SettingInfo settings[] = {
{SettingInfo::Type::Path, "ImagePathMsd", TRANSLATE_NOOP("USB", "Image Path"),
TRANSLATE_NOOP("USB", "Sets the path to image which will back the virtual mass storage device.")},
TRANSLATE_NOOP("USB", "Sets the path to the disk image which will back the virtual mass storage device.")},
};
return settings;
}
@ -1251,7 +1251,7 @@ namespace usb_msd
{
static constexpr const SettingInfo settings[] = {
{SettingInfo::Type::Path, "ImagePathMsac", TRANSLATE_NOOP("USB", "Image Path"),
TRANSLATE_NOOP("USB", "Sets the path to image which will back the virtual mass storage device.")},
TRANSLATE_NOOP("USB", "Sets the path to the disk image which will back the virtual mass storage device.")},
};
return settings;
}

View File

@ -115,13 +115,13 @@ namespace VMManager
static std::string GetCurrentSaveStateFileName(s32 slot, bool backup = false);
static bool DoLoadState(const char* filename, Error* error = nullptr);
static bool DoSaveState(const char* filename, s32 slot_for_message, bool zip_on_thread, bool backup_old_state);
static void DoSaveState(const char* filename, s32 slot_for_message, bool zip_on_thread, bool backup_old_state, std::function<void(const std::string&)> error_callback);
static void ZipSaveState(std::unique_ptr<ArchiveEntryList> elist,
std::unique_ptr<SaveStateScreenshotData> screenshot, std::string osd_key, const char* filename,
s32 slot_for_message);
std::unique_ptr<SaveStateScreenshotData> screenshot, const char* filename,
s32 slot_for_message, std::function<void(const std::string&)> error_callback);
static void ZipSaveStateOnThread(std::unique_ptr<ArchiveEntryList> elist,
std::unique_ptr<SaveStateScreenshotData> screenshot, std::string osd_key, std::string filename,
s32 slot_for_message);
std::unique_ptr<SaveStateScreenshotData> screenshot, std::string filename,
s32 slot_for_message, std::function<void(const std::string&)> error_callback);
static void LoadSettings();
static void LoadCoreSettings(SettingsInterface& si);
@ -1617,8 +1617,13 @@ void VMManager::Shutdown(bool save_resume_state)
if (!GSDumpReplayer::IsReplayingDump() && save_resume_state)
{
std::string resume_file_name(GetCurrentSaveStateFileName(-1));
if (!resume_file_name.empty() && !DoSaveState(resume_file_name.c_str(), -1, true, false))
Console.Error("Failed to save resume state");
if (!resume_file_name.empty())
{
DoSaveState(resume_file_name.c_str(), -1, true, false, [](const std::string& error) {
Host::AddIconOSDMessage("SaveResumeState", ICON_FA_TRIANGLE_EXCLAMATION,
fmt::format(TRANSLATE_FS("VMManager", "Failed to save resume state: {}"), error), Host::OSD_QUICK_DURATION);
});
}
}
// end input recording before clearing state
@ -1829,7 +1834,7 @@ bool VMManager::DoLoadState(const char* filename, Error* error)
{
if (GSDumpReplayer::IsReplayingDump())
{
Error::SetString(error, TRANSLATE_STR("VMManager", "Cannot load save state while replaying GS dump."));
Error::SetString(error, TRANSLATE_STR("VMManager", "Cannot load state while replaying a GS dump."));
return false;
}
@ -1849,21 +1854,20 @@ bool VMManager::DoLoadState(const char* filename, Error* error)
return true;
}
bool VMManager::DoSaveState(const char* filename, s32 slot_for_message, bool zip_on_thread, bool backup_old_state)
void VMManager::DoSaveState(const char* filename, s32 slot_for_message, bool zip_on_thread, bool backup_old_state, std::function<void(const std::string&)> error_callback)
{
if (GSDumpReplayer::IsReplayingDump())
return false;
{
error_callback(TRANSLATE_STR("VMManager", "Cannot save state while replaying a GS dump."));
return;
}
std::string osd_key(fmt::format("SaveStateSlot{}", slot_for_message));
Error error;
std::unique_ptr<ArchiveEntryList> elist = SaveState_DownloadState(&error);
if (!elist)
{
Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_TRIANGLE_EXCLAMATION,
fmt::format(TRANSLATE_FS("VMManager", "Failed to save state: {}."), error.GetDescription()),
Host::OSD_ERROR_DURATION);
return false;
error_callback(error.GetDescription());
return;
}
std::unique_ptr<SaveStateScreenshotData> screenshot = SaveState_SaveScreenshot();
@ -1874,10 +1878,10 @@ bool VMManager::DoSaveState(const char* filename, s32 slot_for_message, bool zip
Console.WriteLn(fmt::format("Creating save state backup {}...", backup_filename));
if (!FileSystem::RenamePath(filename, backup_filename.c_str()))
{
Host::AddIconOSDMessage(osd_key, ICON_FA_TRIANGLE_EXCLAMATION,
fmt::format(
TRANSLATE_FS("VMManager", "Failed to back up old save state {}."), Path::GetFileName(filename)),
Host::OSD_ERROR_DURATION);
error_callback(fmt::format(
TRANSLATE_FS("VMManager", "Cannot back up old save state '{}'."),
Path::GetFileName(filename)));
return;
}
}
@ -1886,48 +1890,48 @@ bool VMManager::DoSaveState(const char* filename, s32 slot_for_message, bool zip
// lock order here is important; the thread could exit before we resume here.
std::unique_lock lock(s_save_state_threads_mutex);
s_save_state_threads.emplace_back(&VMManager::ZipSaveStateOnThread, std::move(elist), std::move(screenshot),
std::move(osd_key), std::string(filename), slot_for_message);
std::string(filename), slot_for_message, std::move(error_callback));
}
else
{
ZipSaveState(std::move(elist), std::move(screenshot), std::move(osd_key), filename, slot_for_message);
ZipSaveState(
std::move(elist), std::move(screenshot), filename, slot_for_message, std::move(error_callback));
}
Host::OnSaveStateSaved(filename);
MemcardBusy::CheckSaveStateDependency();
return true;
return;
}
void VMManager::ZipSaveState(std::unique_ptr<ArchiveEntryList> elist,
std::unique_ptr<SaveStateScreenshotData> screenshot, std::string osd_key, const char* filename,
s32 slot_for_message)
std::unique_ptr<SaveStateScreenshotData> screenshot, const char* filename,
s32 slot_for_message, std::function<void(const std::string&)> error_callback)
{
Common::Timer timer;
if (SaveState_ZipToDisk(std::move(elist), std::move(screenshot), filename))
Error error;
if (!SaveState_ZipToDisk(std::move(elist), std::move(screenshot), filename, &error))
{
error_callback(error.GetDescription());
return;
}
if (slot_for_message >= 0 && VMManager::HasValidVM())
{
Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_FLOPPY_DISK,
fmt::format(TRANSLATE_FS("VMManager", "State saved to slot {}."), slot_for_message),
Host::AddIconOSDMessage(fmt::format("SaveStateSlot{}", slot_for_message), ICON_FA_FLOPPY_DISK,
fmt::format(TRANSLATE_FS("VMManager", "Saved state to slot {}."), slot_for_message),
Host::OSD_QUICK_DURATION);
}
}
else
{
Host::AddIconOSDMessage(std::move(osd_key), ICON_FA_TRIANGLE_EXCLAMATION,
fmt::format(TRANSLATE_FS("VMManager", "Failed to save state to slot {}."), slot_for_message,
Host::OSD_ERROR_DURATION));
}
DevCon.WriteLn("Zipping save state to '%s' took %.2f ms", filename, timer.GetTimeMilliseconds());
}
void VMManager::ZipSaveStateOnThread(std::unique_ptr<ArchiveEntryList> elist,
std::unique_ptr<SaveStateScreenshotData> screenshot, std::string osd_key, std::string filename,
s32 slot_for_message)
std::unique_ptr<SaveStateScreenshotData> screenshot, std::string filename,
s32 slot_for_message, std::function<void(const std::string&)> error_callback)
{
ZipSaveState(std::move(elist), std::move(screenshot), std::move(osd_key), filename.c_str(), slot_for_message);
ZipSaveState(
std::move(elist), std::move(screenshot), filename.c_str(), slot_for_message, std::move(error_callback));
// remove ourselves from the thread list. if we're joining, we might not be in there.
const auto this_id = std::this_thread::get_id();
@ -1985,98 +1989,110 @@ bool VMManager::LoadState(const char* filename, Error* error)
if (Achievements::IsHardcoreModeActive())
{
Error::SetString(error,
TRANSLATE_STR("VMManager", "Cannot load save state while RetroAchievements Hardcore Mode is active."));
TRANSLATE_STR("VMManager", "Cannot load state while RetroAchievements Hardcore Mode is active."));
return false;
}
if (MemcardBusy::IsBusy())
{
Error::SetString(error,
TRANSLATE_STR("VMManager", "Memory card is busy."));
TRANSLATE_STR("VMManager", "The memory card is busy, so the state load operation has been cancelled to prevent data loss."));
return false;
}
// TODO: Save the current state so we don't need to reset.
if (DoLoadState(filename, error))
return true;
if (!DoLoadState(filename, error))
{
Reset();
return false;
}
return true;
}
bool VMManager::LoadStateFromSlot(s32 slot, bool backup, Error* error)
{
const std::string filename = GetCurrentSaveStateFileName(slot, backup);
if (filename.empty() || !FileSystem::FileExists(filename.c_str()))
{
if (backup)
Error::SetStringFmt(error,
TRANSLATE_FS("VMManager", "There is no save state in backup slot {}."), slot);
else
Error::SetStringFmt(error,
TRANSLATE_FS("VMManager", "There is no save state in slot {}."), slot);
Error::SetString(error, TRANSLATE_STR("VMManager", "The save slot is empty."));
return false;
}
if (Achievements::IsHardcoreModeActive())
{
if (backup)
Error::SetStringFmt(error,
TRANSLATE_FS("VMManager", "Cannot load save state from backup slot {} while RetroAchievements Hardcore Mode is active."), slot);
else
Error::SetStringFmt(error,
TRANSLATE_FS("VMManager", "Cannot load save state from slot {} while RetroAchievements Hardcore Mode is active."), slot);
Error::SetString(error,
TRANSLATE_STR("VMManager", "Cannot load state while RetroAchievements Hardcore Mode is active."));
return false;
}
if (MemcardBusy::IsBusy())
{
if (backup)
Error::SetStringFmt(error,
TRANSLATE_FS("VMManager", "Failed to load save state from backup slot {} (memory card is busy)."), slot);
else
Error::SetStringFmt(error,
TRANSLATE_FS("VMManager", "Failed to load save state from slot {} (memory card is busy)."), slot);
Error::SetString(error,
TRANSLATE_STR("VMManager",
"The memory card is busy, so the state load operation has been cancelled to prevent data loss."));
return false;
}
if (!DoLoadState(filename.c_str(), error))
return false;
if (backup)
{
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_FOLDER_OPEN,
fmt::format(TRANSLATE_FS("VMManager", "Loading {} from slot {}..."), backup ? TRANSLATE("VMManager", "backup state") : TRANSLATE("VMManager", "state"), slot), Host::OSD_QUICK_DURATION);
return DoLoadState(filename.c_str(), error);
fmt::format(TRANSLATE_FS("VMManager", "Loaded state from backup slot {}."), slot),
Host::OSD_QUICK_DURATION);
}
else
{
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_FOLDER_OPEN,
fmt::format(TRANSLATE_FS("VMManager", "Loaded state from slot {}."), slot),
Host::OSD_QUICK_DURATION);
}
bool VMManager::SaveState(const char* filename, bool zip_on_thread, bool backup_old_state)
return true;
}
void VMManager::SaveState(
const char* filename, bool zip_on_thread, bool backup_old_state, std::function<void(const std::string&)> error_callback)
{
if (MemcardBusy::IsBusy())
{
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_TRIANGLE_EXCLAMATION,
fmt::format(TRANSLATE_FS("VMManager", "Failed to save state (Memory card is busy)")),
Host::OSD_QUICK_DURATION);
return false;
error_callback(TRANSLATE_STR("VMManager",
"The memory card is busy, so the state save operation has been cancelled to prevent data loss."));
return;
}
return DoSaveState(filename, -1, zip_on_thread, backup_old_state);
DoSaveState(filename, -1, zip_on_thread, backup_old_state, std::move(error_callback));
}
bool VMManager::SaveStateToSlot(s32 slot, bool zip_on_thread)
void VMManager::SaveStateToSlot(s32 slot, bool zip_on_thread, std::function<void(const std::string&)> error_callback)
{
const std::string filename(GetCurrentSaveStateFileName(slot));
if (filename.empty())
return false;
{
error_callback(TRANSLATE_STR("VMManager", "Cannot generate filename for save state."));
return;
}
if (MemcardBusy::IsBusy())
{
Host::AddIconOSDMessage("LoadStateFromSlot", ICON_FA_TRIANGLE_EXCLAMATION,
fmt::format(TRANSLATE_FS("VMManager", "Failed to save state to slot {} (Memory card is busy)"), slot),
Host::OSD_QUICK_DURATION);
return false;
error_callback(TRANSLATE_STR("VMManager",
"The memory card is busy, so the state save operation has been cancelled to prevent data loss."));
return;
}
// if it takes more than a minute.. well.. wtf.
Host::AddIconOSDMessage(fmt::format("SaveStateSlot{}", slot), ICON_FA_FLOPPY_DISK,
fmt::format(TRANSLATE_FS("VMManager", "Saving state to slot {}..."), slot), 60.0f);
return DoSaveState(filename.c_str(), slot, zip_on_thread, EmuConfig.BackupSavestate);
auto callback = [error_callback = std::move(error_callback), slot](const std::string& error) {
Host::RemoveKeyedOSDMessage(fmt::format("SaveStateSlot{}", slot));
error_callback(error);
};
return DoSaveState(
filename.c_str(), slot, zip_on_thread, EmuConfig.BackupSavestate, std::move(callback));
}
LimiterModeType VMManager::GetLimiterMode()
@ -2816,8 +2832,8 @@ void VMManager::Internal::EntryPointCompilingOnCPUThread()
HandleELFChange(true);
Patch::ApplyLoadedPatches(Patch::PPT_ONCE_ON_LOAD);
Patch::ApplyLoadedPatches(Patch::PPT_COMBINED_0_1);
Patch::ApplyBootPatches();
// If the config changes at this point, it's a reset, so the game doesn't currently know about the memcard
// so there's no need to leave the eject running.
FileMcd_CancelEject();
@ -2833,8 +2849,7 @@ void VMManager::Internal::VSyncOnCPUThread()
{
Pad::UpdateMacroButtons();
Patch::ApplyLoadedPatches(Patch::PPT_CONTINUOUSLY);
Patch::ApplyLoadedPatches(Patch::PPT_COMBINED_0_1);
Patch::ApplyVsyncPatches();
// Frame advance must be done *before* pumping messages, because otherwise
// we'll immediately reduce the counter we just set.

View File

@ -165,10 +165,11 @@ namespace VMManager
bool LoadStateFromSlot(s32 slot, bool backup = false, Error* error = nullptr);
/// Saves state to the specified filename.
bool SaveState(const char* filename, bool zip_on_thread = true, bool backup_old_state = false);
void SaveState(const char* filename, bool zip_on_thread, bool backup_old_state,
std::function<void(const std::string&)> error_callback);
/// Saves state to the specified slot.
bool SaveStateToSlot(s32 slot, bool zip_on_thread = true);
void SaveStateToSlot(s32 slot, bool zip_on_thread, std::function<void(const std::string&)> error_callback);
/// Waits until all compressing save states have finished saving to disk.
void WaitForSaveStateFlush();