summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.ci/scripts/windows/docker.sh1
-rw-r--r--.ci/scripts/windows/install-vulkan-sdk.ps133
-rw-r--r--.ci/templates/build-msvc.yml9
-rw-r--r--.github/workflows/android-build.yml80
-rw-r--r--.github/workflows/android-merge.js218
-rw-r--r--.github/workflows/android-publish.yml57
-rw-r--r--.github/workflows/verify.yml34
-rw-r--r--CMakeLists.txt4
m---------externals/SDL0
m---------externals/Vulkan-Headers0
-rw-r--r--src/android/app/src/main/AndroidManifest.xml3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt67
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt18
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt410
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt3
-rw-r--r--src/android/app/src/main/res/drawable/button_l3.xml128
-rw-r--r--src/android/app/src/main/res/drawable/button_l3_depressed.xml75
-rw-r--r--src/android/app/src/main/res/drawable/button_r3.xml128
-rw-r--r--src/android/app/src/main/res/drawable/button_r3_depressed.xml75
-rw-r--r--src/android/app/src/main/res/values/arrays.xml2
-rw-r--r--src/android/app/src/main/res/values/integers.xml12
-rw-r--r--src/common/settings.cpp2
-rw-r--r--src/core/arm/arm_interface.cpp2
-rw-r--r--src/core/file_sys/vfs_real.cpp3
-rw-r--r--src/core/hid/emulated_controller.cpp12
-rw-r--r--src/core/hid/emulated_controller.h8
-rw-r--r--src/core/hle/service/nfc/common/device.cpp32
-rw-r--r--src/input_common/drivers/mouse.cpp5
-rw-r--r--src/input_common/drivers/sdl_driver.cpp46
-rw-r--r--src/input_common/drivers/sdl_driver.h7
-rw-r--r--src/video_core/texture_cache/texture_cache.h4
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp43
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h8
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp4
-rw-r--r--src/yuzu/main.cpp12
36 files changed, 1281 insertions, 267 deletions
diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh
index 0be3613aa..45f75c874 100755
--- a/.ci/scripts/windows/docker.sh
+++ b/.ci/scripts/windows/docker.sh
@@ -56,7 +56,6 @@ for i in package/*.exe; do
56 x86_64-w64-mingw32-strip "${i}" 56 x86_64-w64-mingw32-strip "${i}"
57done 57done
58 58
59pip3 install pefile
60python3 .ci/scripts/windows/scan_dll.py package/*.exe package/imageformats/*.dll "package/" 59python3 .ci/scripts/windows/scan_dll.py package/*.exe package/imageformats/*.dll "package/"
61 60
62# copy FFmpeg libraries 61# copy FFmpeg libraries
diff --git a/.ci/scripts/windows/install-vulkan-sdk.ps1 b/.ci/scripts/windows/install-vulkan-sdk.ps1
new file mode 100644
index 000000000..de218d90a
--- /dev/null
+++ b/.ci/scripts/windows/install-vulkan-sdk.ps1
@@ -0,0 +1,33 @@
1# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-3.0-or-later
3
4$ErrorActionPreference = "Stop"
5
6$VulkanSDKVer = "1.3.250.1"
7$ExeFile = "VulkanSDK-$VulkanSDKVer-Installer.exe"
8$Uri = "https://sdk.lunarg.com/sdk/download/$VulkanSDKVer/windows/$ExeFile"
9$Destination = "./$ExeFile"
10
11echo "Downloading Vulkan SDK $VulkanSDKVer from $Uri"
12$WebClient = New-Object System.Net.WebClient
13$WebClient.DownloadFile($Uri, $Destination)
14echo "Finished downloading $ExeFile"
15
16$VULKAN_SDK = "C:/VulkanSDK/$VulkanSDKVer"
17$Arguments = "--root `"$VULKAN_SDK`" --accept-licenses --default-answer --confirm-command install"
18
19echo "Installing Vulkan SDK $VulkanSDKVer"
20$InstallProcess = Start-Process -FilePath $Destination -NoNewWindow -PassThru -Wait -ArgumentList $Arguments
21$ExitCode = $InstallProcess.ExitCode
22
23if ($ExitCode -ne 0) {
24 echo "Error installing Vulkan SDK $VulkanSDKVer (Error: $ExitCode)"
25 Exit $ExitCode
26}
27
28echo "Finished installing Vulkan SDK $VulkanSDKVer"
29
30if ("$env:GITHUB_ACTIONS" -eq "true") {
31 echo "VULKAN_SDK=$VULKAN_SDK" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
32 echo "$VULKAN_SDK/Bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
33}
diff --git a/.ci/templates/build-msvc.yml b/.ci/templates/build-msvc.yml
index ceb7e0c32..d069fa9c3 100644
--- a/.ci/templates/build-msvc.yml
+++ b/.ci/templates/build-msvc.yml
@@ -7,9 +7,12 @@ parameters:
7 version: '' 7 version: ''
8 8
9steps: 9steps:
10- script: choco install vulkan-sdk 10- task: Powershell@2
11 displayName: 'Install vulkan-sdk' 11 displayName: 'Install Vulkan SDK'
12- script: refreshenv && mkdir build && cd build && cmake -E env CXXFLAGS="/Gw /GA /Gr /Ob2" cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_POLICY_DEFAULT_CMP0069=NEW -DYUZU_ENABLE_LTO=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_CRASH_DUMPS=ON .. && cd .. 12 inputs:
13 targetType: 'filePath'
14 filePath: './.ci/scripts/windows/install-vulkan-sdk.ps1'
15- script: refreshenv && glslangValidator --version && mkdir build && cd build && cmake -E env CXXFLAGS="/Gw /GA /Gr /Ob2" cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_POLICY_DEFAULT_CMP0069=NEW -DYUZU_ENABLE_LTO=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_CRASH_DUMPS=ON .. && cd ..
13 displayName: 'Configure CMake' 16 displayName: 'Configure CMake'
14- task: MSBuild@1 17- task: MSBuild@1
15 displayName: 'Build' 18 displayName: 'Build'
diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml
new file mode 100644
index 000000000..5893f860e
--- /dev/null
+++ b/.github/workflows/android-build.yml
@@ -0,0 +1,80 @@
1# SPDX-FileCopyrightText: 2022 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-3.0-or-later
3
4name: 'yuzu-android-build'
5
6on:
7 push:
8 tags: [ "*" ]
9
10jobs:
11 android:
12 runs-on: ubuntu-latest
13 if: ${{ github.repository == 'yuzu-emu/yuzu-android' }}
14 steps:
15 - uses: actions/checkout@v3
16 with:
17 submodules: recursive
18 fetch-depth: 0
19 - name: Set up JDK 17
20 uses: actions/setup-java@v3
21 with:
22 java-version: '17'
23 distribution: 'temurin'
24 - name: Set up cache
25 uses: actions/cache@v3
26 with:
27 path: |
28 ~/.gradle/caches
29 ~/.gradle/wrapper
30 ~/.ccache
31 key: ${{ runner.os }}-android-${{ github.sha }}
32 restore-keys: |
33 ${{ runner.os }}-android-
34 - name: Query tag name
35 uses: olegtarasov/get-tag@v2.1.2
36 id: tagName
37 - name: Install dependencies
38 run: |
39 sudo apt-get update
40 sudo apt-get install -y ccache apksigner glslang-dev glslang-tools
41 - name: Build
42 run: ./.ci/scripts/android/build.sh
43 - name: Copy and sign artifacts
44 env:
45 ANDROID_KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_B64 }}
46 ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
47 ANDROID_KEYSTORE_PASS: ${{ secrets.ANDROID_KEYSTORE_PASS }}
48 run: ./.ci/scripts/android/upload.sh
49 - name: Upload
50 uses: actions/upload-artifact@v3
51 with:
52 name: android
53 path: artifacts/
54 # release steps
55 release-android:
56 runs-on: ubuntu-latest
57 needs: [android]
58 if: ${{ startsWith(github.ref, 'refs/tags/') }}
59 permissions:
60 contents: write
61 steps:
62 - uses: actions/download-artifact@v3
63 - name: Query tag name
64 uses: olegtarasov/get-tag@v2.1.2
65 id: tagName
66 - name: Create release
67 uses: actions/create-release@v1
68 env:
69 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
70 with:
71 tag_name: ${{ steps.tagName.outputs.tag }}
72 release_name: ${{ steps.tagName.outputs.tag }}
73 draft: false
74 prerelease: false
75 - name: Upload artifacts
76 uses: alexellis/upload-assets@0.2.3
77 env:
78 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
79 with:
80 asset_paths: '["./**/*.apk","./**/*.aab"]'
diff --git a/.github/workflows/android-merge.js b/.github/workflows/android-merge.js
new file mode 100644
index 000000000..7e02dc9e5
--- /dev/null
+++ b/.github/workflows/android-merge.js
@@ -0,0 +1,218 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Note: This is a GitHub Actions script
5// It is not meant to be executed directly on your machine without modifications
6
7const fs = require("fs");
8// which label to check for changes
9const CHANGE_LABEL = 'android-merge';
10// how far back in time should we consider the changes are "recent"? (default: 24 hours)
11const DETECTION_TIME_FRAME = (parseInt(process.env.DETECTION_TIME_FRAME)) || (24 * 3600 * 1000);
12
13async function checkBaseChanges(github, context) {
14 // query the commit date of the latest commit on this branch
15 const query = `query($owner:String!, $name:String!, $ref:String!) {
16 repository(name:$name, owner:$owner) {
17 ref(qualifiedName:$ref) {
18 target {
19 ... on Commit { id pushedDate oid }
20 }
21 }
22 }
23 }`;
24 const variables = {
25 owner: context.repo.owner,
26 name: context.repo.repo,
27 ref: 'refs/heads/master',
28 };
29 const result = await github.graphql(query, variables);
30 const pushedAt = result.repository.ref.target.pushedDate;
31 console.log(`Last commit pushed at ${pushedAt}.`);
32 const delta = new Date() - new Date(pushedAt);
33 if (delta <= DETECTION_TIME_FRAME) {
34 console.info('New changes detected, triggering a new build.');
35 return true;
36 }
37 console.info('No new changes detected.');
38 return false;
39}
40
41async function checkAndroidChanges(github, context) {
42 if (checkBaseChanges(github, context)) return true;
43 const query = `query($owner:String!, $name:String!, $label:String!) {
44 repository(name:$name, owner:$owner) {
45 pullRequests(labels: [$label], states: OPEN, first: 100) {
46 nodes { number headRepository { pushedAt } }
47 }
48 }
49 }`;
50 const variables = {
51 owner: context.repo.owner,
52 name: context.repo.repo,
53 label: CHANGE_LABEL,
54 };
55 const result = await github.graphql(query, variables);
56 const pulls = result.repository.pullRequests.nodes;
57 for (let i = 0; i < pulls.length; i++) {
58 let pull = pulls[i];
59 if (new Date() - new Date(pull.headRepository.pushedAt) <= DETECTION_TIME_FRAME) {
60 console.info(`${pull.number} updated at ${pull.headRepository.pushedAt}`);
61 return true;
62 }
63 }
64 console.info("No changes detected in any tagged pull requests.");
65 return false;
66}
67
68async function tagAndPush(github, owner, repo, execa, commit=false) {
69 let altToken = process.env.ALT_GITHUB_TOKEN;
70 if (!altToken) {
71 throw `Please set ALT_GITHUB_TOKEN environment variable. This token should have write access to ${owner}/${repo}.`;
72 }
73 const query = `query ($owner:String!, $name:String!) {
74 repository(name:$name, owner:$owner) {
75 refs(refPrefix: "refs/tags/", orderBy: {field: TAG_COMMIT_DATE, direction: DESC}, first: 10) {
76 nodes { name }
77 }
78 }
79 }`;
80 const variables = {
81 owner: owner,
82 name: repo,
83 };
84 const tags = await github.graphql(query, variables);
85 const tagList = tags.repository.refs.nodes;
86 const lastTag = tagList[0] ? tagList[0].name : 'dummy-0';
87 const tagNumber = /\w+-(\d+)/.exec(lastTag)[1] | 0;
88 const channel = repo.split('-')[1];
89 const newTag = `${channel}-${tagNumber + 1}`;
90 console.log(`New tag: ${newTag}`);
91 if (commit) {
92 let channelName = channel[0].toUpperCase() + channel.slice(1);
93 console.info(`Committing pending commit as ${channelName} #${tagNumber + 1}`);
94 await execa("git", ['commit', '-m', `${channelName} #${tagNumber + 1}`]);
95 }
96 console.info('Pushing tags to GitHub ...');
97 await execa("git", ['tag', newTag]);
98 await execa("git", ['remote', 'add', 'target', `https://${altToken}@github.com/${owner}/${repo}.git`]);
99 await execa("git", ['push', 'target', 'master', '-f']);
100 await execa("git", ['push', 'target', 'master', '--tags']);
101 console.info('Successfully pushed new changes.');
102}
103
104async function generateReadme(pulls, context, mergeResults, execa) {
105 let baseUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/`;
106 let output =
107 "| Pull Request | Commit | Title | Author | Merged? |\n|----|----|----|----|----|\n";
108 for (let pull of pulls) {
109 let pr = pull.number;
110 let result = mergeResults[pr];
111 output += `| [${pr}](${baseUrl}/pull/${pr}) | [\`${result.rev || "N/A"}\`](${baseUrl}/pull/${pr}/files) | ${pull.title} | [${pull.author.login}](https://github.com/${pull.author.login}/) | ${result.success ? "Yes" : "No"} |\n`;
112 }
113 output +=
114 "\n\nEnd of merge log. You can find the original README.md below the break.\n\n-----\n\n";
115 output += fs.readFileSync("./README.md");
116 fs.writeFileSync("./README.md", output);
117 await execa("git", ["add", "README.md"]);
118}
119
120async function fetchPullRequests(pulls, repoUrl, execa) {
121 console.log("::group::Fetch pull requests");
122 for (let pull of pulls) {
123 let pr = pull.number;
124 console.info(`Fetching PR ${pr} ...`);
125 await execa("git", [
126 "fetch",
127 "-f",
128 "--no-recurse-submodules",
129 repoUrl,
130 `pull/${pr}/head:pr-${pr}`,
131 ]);
132 }
133 console.log("::endgroup::");
134}
135
136async function mergePullRequests(pulls, execa) {
137 let mergeResults = {};
138 console.log("::group::Merge pull requests");
139 await execa("git", ["config", "--global", "user.name", "yuzubot"]);
140 await execa("git", [
141 "config",
142 "--global",
143 "user.email",
144 "yuzu\x40yuzu-emu\x2eorg", // prevent email harvesters from scraping the address
145 ]);
146 let hasFailed = false;
147 for (let pull of pulls) {
148 let pr = pull.number;
149 console.info(`Merging PR ${pr} ...`);
150 try {
151 const process1 = execa("git", [
152 "merge",
153 "--squash",
154 "--no-edit",
155 `pr-${pr}`,
156 ]);
157 process1.stdout.pipe(process.stdout);
158 await process1;
159
160 const process2 = execa("git", ["commit", "-m", `Merge PR ${pr}`]);
161 process2.stdout.pipe(process.stdout);
162 await process2;
163
164 const process3 = await execa("git", ["rev-parse", "--short", `pr-${pr}`]);
165 mergeResults[pr] = {
166 success: true,
167 rev: process3.stdout,
168 };
169 } catch (err) {
170 console.log(
171 `::error title=#${pr} not merged::Failed to merge pull request: ${pr}: ${err}`
172 );
173 mergeResults[pr] = { success: false };
174 hasFailed = true;
175 await execa("git", ["reset", "--hard"]);
176 }
177 }
178 console.log("::endgroup::");
179 if (hasFailed) {
180 throw 'There are merge failures. Aborting!';
181 }
182 return mergeResults;
183}
184
185async function mergebot(github, context, execa) {
186 const query = `query ($owner:String!, $name:String!, $label:String!) {
187 repository(name:$name, owner:$owner) {
188 pullRequests(labels: [$label], states: OPEN, first: 100) {
189 nodes {
190 number title author { login }
191 }
192 }
193 }
194 }`;
195 const variables = {
196 owner: context.repo.owner,
197 name: context.repo.repo,
198 label: CHANGE_LABEL,
199 };
200 const result = await github.graphql(query, variables);
201 const pulls = result.repository.pullRequests.nodes;
202 let displayList = [];
203 for (let i = 0; i < pulls.length; i++) {
204 let pull = pulls[i];
205 displayList.push({ PR: pull.number, Title: pull.title });
206 }
207 console.info("The following pull requests will be merged:");
208 console.table(displayList);
209 await fetchPullRequests(pulls, "https://github.com/yuzu-emu/yuzu", execa);
210 const mergeResults = await mergePullRequests(pulls, execa);
211 await generateReadme(pulls, context, mergeResults, execa);
212 await tagAndPush(github, context.repo.owner, `${context.repo.repo}-android`, execa, true);
213}
214
215module.exports.mergebot = mergebot;
216module.exports.checkAndroidChanges = checkAndroidChanges;
217module.exports.tagAndPush = tagAndPush;
218module.exports.checkBaseChanges = checkBaseChanges;
diff --git a/.github/workflows/android-publish.yml b/.github/workflows/android-publish.yml
new file mode 100644
index 000000000..8f46fcf74
--- /dev/null
+++ b/.github/workflows/android-publish.yml
@@ -0,0 +1,57 @@
1# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later
3
4name: yuzu-android-publish
5
6on:
7 schedule:
8 - cron: '37 0 * * *'
9 workflow_dispatch:
10 inputs:
11 android:
12 description: 'Whether to trigger an Android build (true/false/auto)'
13 required: false
14 default: 'true'
15
16jobs:
17 android:
18 runs-on: ubuntu-latest
19 if: ${{ github.event.inputs.android != 'false' && github.repository == 'yuzu-emu/yuzu' }}
20 steps:
21 # this checkout is required to make sure the GitHub Actions scripts are available
22 - uses: actions/checkout@v3
23 name: Pre-checkout
24 with:
25 submodules: false
26 - uses: actions/github-script@v6
27 id: check-changes
28 name: 'Check for new changes'
29 env:
30 # 24 hours
31 DETECTION_TIME_FRAME: 86400000
32 with:
33 script: |
34 if (context.payload.inputs && context.payload.inputs.android === 'true') return true;
35 const checkAndroidChanges = require('./.github/workflows/android-merge.js').checkAndroidChanges;
36 return checkAndroidChanges(github, context);
37 - run: npm install execa@5
38 if: ${{ steps.check-changes.outputs.result == 'true' }}
39 - uses: actions/checkout@v3
40 name: Checkout
41 if: ${{ steps.check-changes.outputs.result == 'true' }}
42 with:
43 path: 'yuzu-merge'
44 fetch-depth: 0
45 submodules: true
46 token: ${{ secrets.ALT_GITHUB_TOKEN }}
47 - uses: actions/github-script@v5
48 name: 'Check and merge Android changes'
49 if: ${{ steps.check-changes.outputs.result == 'true' }}
50 env:
51 ALT_GITHUB_TOKEN: ${{ secrets.ALT_GITHUB_TOKEN }}
52 with:
53 script: |
54 const execa = require("execa");
55 const mergebot = require('./.github/workflows/android-merge.js').mergebot;
56 process.chdir('${{ github.workspace }}/yuzu-merge');
57 mergebot(github, context, execa);
diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml
index bd4141f56..cbe6b0fbd 100644
--- a/.github/workflows/verify.yml
+++ b/.github/workflows/verify.yml
@@ -73,6 +73,10 @@ jobs:
73 needs: format 73 needs: format
74 runs-on: windows-2022 74 runs-on: windows-2022
75 steps: 75 steps:
76 - uses: actions/checkout@v3
77 with:
78 submodules: recursive
79 fetch-depth: 0
76 - name: Set up cache 80 - name: Set up cache
77 uses: actions/cache@v3 81 uses: actions/cache@v3
78 with: 82 with:
@@ -81,22 +85,22 @@ jobs:
81 restore-keys: | 85 restore-keys: |
82 ${{ runner.os }}-msvc- 86 ${{ runner.os }}-msvc-
83 - name: Install dependencies 87 - name: Install dependencies
84 # due to how chocolatey works, only cmd.exe is supported here 88 shell: pwsh
85 shell: cmd
86 run: | 89 run: |
87 choco install vulkan-sdk wget 90 $ErrorActionPreference = "Stop"
88 call refreshenv 91 $BuildCacheVer = "v0.28.4"
89 wget https://github.com/mbitsnbites/buildcache/releases/download/v0.27.6/buildcache-windows.zip 92 $File = "buildcache-windows.zip"
90 7z x buildcache-windows.zip 93 $Uri = "https://github.com/mbitsnbites/buildcache/releases/download/$BuildCacheVer/$File"
91 copy buildcache\bin\buildcache.exe C:\ProgramData\chocolatey\bin 94 $WebClient = New-Object System.Net.WebClient
92 rmdir buildcache 95 $WebClient.DownloadFile($Uri, $File)
93 echo %PATH% >> %GITHUB_PATH% 96 7z x $File
97 $CurrentDir = Convert-Path .
98 echo "$CurrentDir/buildcache/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
99 - name: Install Vulkan SDK
100 shell: pwsh
101 run: .\.ci\scripts\windows\install-vulkan-sdk.ps1
94 - name: Set up MSVC 102 - name: Set up MSVC
95 uses: ilammy/msvc-dev-cmd@v1 103 uses: ilammy/msvc-dev-cmd@v1
96 - uses: actions/checkout@v3
97 with:
98 submodules: recursive
99 fetch-depth: 0
100 - name: Configure 104 - name: Configure
101 env: 105 env:
102 CC: cl.exe 106 CC: cl.exe
@@ -129,11 +133,12 @@ jobs:
129 - uses: actions/checkout@v3 133 - uses: actions/checkout@v3
130 with: 134 with:
131 submodules: recursive 135 submodules: recursive
136 fetch-depth: 0
132 - name: set up JDK 17 137 - name: set up JDK 17
133 uses: actions/setup-java@v3 138 uses: actions/setup-java@v3
134 with: 139 with:
135 java-version: '17' 140 java-version: '17'
136 distribution: 'adopt' 141 distribution: 'temurin'
137 - name: Set up cache 142 - name: Set up cache
138 uses: actions/cache@v3 143 uses: actions/cache@v3
139 with: 144 with:
@@ -151,7 +156,6 @@ jobs:
151 run: | 156 run: |
152 sudo apt-get update 157 sudo apt-get update
153 sudo apt-get install -y ccache apksigner glslang-dev glslang-tools 158 sudo apt-get install -y ccache apksigner glslang-dev glslang-tools
154 git -C ./externals/vcpkg/ fetch --all --unshallow
155 - name: Build 159 - name: Build
156 run: ./.ci/scripts/android/build.sh 160 run: ./.ci/scripts/android/build.sh
157 - name: Copy and sign artifacts 161 - name: Copy and sign artifacts
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f5ef0ef50..7f8febb90 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -285,7 +285,7 @@ find_package(ZLIB 1.2 REQUIRED)
285find_package(zstd 1.5 REQUIRED) 285find_package(zstd 1.5 REQUIRED)
286 286
287if (NOT YUZU_USE_EXTERNAL_VULKAN_HEADERS) 287if (NOT YUZU_USE_EXTERNAL_VULKAN_HEADERS)
288 find_package(Vulkan 1.3.246 REQUIRED) 288 find_package(Vulkan 1.3.256 REQUIRED)
289endif() 289endif()
290 290
291if (ENABLE_LIBUSB) 291if (ENABLE_LIBUSB)
@@ -489,7 +489,7 @@ if (ENABLE_SDL2)
489 if (YUZU_USE_BUNDLED_SDL2) 489 if (YUZU_USE_BUNDLED_SDL2)
490 # Detect toolchain and platform 490 # Detect toolchain and platform
491 if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND ARCHITECTURE_x86_64) 491 if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND ARCHITECTURE_x86_64)
492 set(SDL2_VER "SDL2-2.28.0") 492 set(SDL2_VER "SDL2-2.28.1")
493 else() 493 else()
494 message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.") 494 message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.")
495 endif() 495 endif()
diff --git a/externals/SDL b/externals/SDL
Subproject 491fba1d06a4810645092b2559b9cc94abeb23b Subproject 116a5344ff4e8b8166eac2db540cd6578b4ba02
diff --git a/externals/Vulkan-Headers b/externals/Vulkan-Headers
Subproject 63af1cf1ee906ba4dcd5a324bdd0201d4f4bfd1 Subproject ed857118e243fdc0f3a100f00ac9919e874cfe6
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 51d949d65..6184f3eb6 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -22,7 +22,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
22 android:label="@string/app_name_suffixed" 22 android:label="@string/app_name_suffixed"
23 android:icon="@drawable/ic_launcher" 23 android:icon="@drawable/ic_launcher"
24 android:allowBackup="true" 24 android:allowBackup="true"
25 android:hasFragileUserData="true" 25 android:hasFragileUserData="false"
26 android:supportsRtl="true" 26 android:supportsRtl="true"
27 android:isGame="true" 27 android:isGame="true"
28 android:localeConfig="@xml/locales_config" 28 android:localeConfig="@xml/locales_config"
@@ -54,6 +54,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
54 <activity 54 <activity
55 android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" 55 android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
56 android:theme="@style/Theme.Yuzu.Main" 56 android:theme="@style/Theme.Yuzu.Main"
57 android:launchMode="singleTop"
57 android:screenOrientation="userLandscape" 58 android:screenOrientation="userLandscape"
58 android:supportsPictureInPicture="true" 59 android:supportsPictureInPicture="true"
59 android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode" 60 android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index be6e17e65..a6251bafd 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -112,25 +112,36 @@ class Settings {
112 112
113 const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" 113 const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
114 114
115 const val PREF_OVERLAY_INIT = "OverlayInit" 115 const val PREF_OVERLAY_VERSION = "OverlayVersion"
116 const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
117 const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
118 const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
119 val overlayLayoutPrefs = listOf(
120 PREF_LANDSCAPE_OVERLAY_VERSION,
121 PREF_PORTRAIT_OVERLAY_VERSION,
122 PREF_FOLDABLE_OVERLAY_VERSION
123 )
124
116 const val PREF_CONTROL_SCALE = "controlScale" 125 const val PREF_CONTROL_SCALE = "controlScale"
117 const val PREF_CONTROL_OPACITY = "controlOpacity" 126 const val PREF_CONTROL_OPACITY = "controlOpacity"
118 const val PREF_TOUCH_ENABLED = "isTouchEnabled" 127 const val PREF_TOUCH_ENABLED = "isTouchEnabled"
119 const val PREF_BUTTON_TOGGLE_0 = "buttonToggle0" 128 const val PREF_BUTTON_A = "buttonToggle0"
120 const val PREF_BUTTON_TOGGLE_1 = "buttonToggle1" 129 const val PREF_BUTTON_B = "buttonToggle1"
121 const val PREF_BUTTON_TOGGLE_2 = "buttonToggle2" 130 const val PREF_BUTTON_X = "buttonToggle2"
122 const val PREF_BUTTON_TOGGLE_3 = "buttonToggle3" 131 const val PREF_BUTTON_Y = "buttonToggle3"
123 const val PREF_BUTTON_TOGGLE_4 = "buttonToggle4" 132 const val PREF_BUTTON_L = "buttonToggle4"
124 const val PREF_BUTTON_TOGGLE_5 = "buttonToggle5" 133 const val PREF_BUTTON_R = "buttonToggle5"
125 const val PREF_BUTTON_TOGGLE_6 = "buttonToggle6" 134 const val PREF_BUTTON_ZL = "buttonToggle6"
126 const val PREF_BUTTON_TOGGLE_7 = "buttonToggle7" 135 const val PREF_BUTTON_ZR = "buttonToggle7"
127 const val PREF_BUTTON_TOGGLE_8 = "buttonToggle8" 136 const val PREF_BUTTON_PLUS = "buttonToggle8"
128 const val PREF_BUTTON_TOGGLE_9 = "buttonToggle9" 137 const val PREF_BUTTON_MINUS = "buttonToggle9"
129 const val PREF_BUTTON_TOGGLE_10 = "buttonToggle10" 138 const val PREF_BUTTON_DPAD = "buttonToggle10"
130 const val PREF_BUTTON_TOGGLE_11 = "buttonToggle11" 139 const val PREF_STICK_L = "buttonToggle11"
131 const val PREF_BUTTON_TOGGLE_12 = "buttonToggle12" 140 const val PREF_STICK_R = "buttonToggle12"
132 const val PREF_BUTTON_TOGGLE_13 = "buttonToggle13" 141 const val PREF_BUTTON_STICK_L = "buttonToggle13"
133 const val PREF_BUTTON_TOGGLE_14 = "buttonToggle14" 142 const val PREF_BUTTON_STICK_R = "buttonToggle14"
143 const val PREF_BUTTON_HOME = "buttonToggle15"
144 const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
134 145
135 const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" 146 const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
136 const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" 147 const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
@@ -145,6 +156,30 @@ class Settings {
145 156
146 private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap() 157 private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
147 158
159 val overlayPreferences = listOf(
160 PREF_OVERLAY_VERSION,
161 PREF_CONTROL_SCALE,
162 PREF_CONTROL_OPACITY,
163 PREF_TOUCH_ENABLED,
164 PREF_BUTTON_A,
165 PREF_BUTTON_B,
166 PREF_BUTTON_X,
167 PREF_BUTTON_Y,
168 PREF_BUTTON_L,
169 PREF_BUTTON_R,
170 PREF_BUTTON_ZL,
171 PREF_BUTTON_ZR,
172 PREF_BUTTON_PLUS,
173 PREF_BUTTON_MINUS,
174 PREF_BUTTON_DPAD,
175 PREF_STICK_L,
176 PREF_STICK_R,
177 PREF_BUTTON_HOME,
178 PREF_BUTTON_SCREENSHOT,
179 PREF_BUTTON_STICK_L,
180 PREF_BUTTON_STICK_R
181 )
182
148 const val LayoutOption_Unspecified = 0 183 const val LayoutOption_Unspecified = 0
149 const val LayoutOption_MobilePortrait = 4 184 const val LayoutOption_MobilePortrait = 4
150 const val LayoutOption_MobileLandscape = 5 185 const val LayoutOption_MobileLandscape = 5
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 09976db62..0e7c1ba88 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -212,9 +212,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
212 } 212 }
213 if (!isInFoldableLayout) { 213 if (!isInFoldableLayout) {
214 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { 214 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
215 binding.surfaceInputOverlay.orientation = InputOverlay.PORTRAIT 215 binding.surfaceInputOverlay.layout = InputOverlay.PORTRAIT
216 } else { 216 } else {
217 binding.surfaceInputOverlay.orientation = InputOverlay.LANDSCAPE 217 binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE
218 } 218 }
219 } 219 }
220 if (!binding.surfaceInputOverlay.isInEditMode) { 220 if (!binding.surfaceInputOverlay.isInEditMode) {
@@ -260,7 +260,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
260 .remove(Settings.PREF_CONTROL_SCALE) 260 .remove(Settings.PREF_CONTROL_SCALE)
261 .remove(Settings.PREF_CONTROL_OPACITY) 261 .remove(Settings.PREF_CONTROL_OPACITY)
262 .apply() 262 .apply()
263 binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.resetButtonPlacement() } 263 binding.surfaceInputOverlay.post {
264 binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement()
265 }
264 } 266 }
265 267
266 private fun updateShowFpsOverlay() { 268 private fun updateShowFpsOverlay() {
@@ -337,7 +339,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
337 binding.inGameMenu.layoutParams.height = it.bounds.bottom 339 binding.inGameMenu.layoutParams.height = it.bounds.bottom
338 340
339 isInFoldableLayout = true 341 isInFoldableLayout = true
340 binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE 342 binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE
341 refreshInputOverlay() 343 refreshInputOverlay()
342 } 344 }
343 } 345 }
@@ -410,9 +412,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
410 R.id.menu_toggle_controls -> { 412 R.id.menu_toggle_controls -> {
411 val preferences = 413 val preferences =
412 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 414 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
413 val optionsArray = BooleanArray(15) 415 val optionsArray = BooleanArray(Settings.overlayPreferences.size)
414 for (i in 0..14) { 416 Settings.overlayPreferences.forEachIndexed { i, _ ->
415 optionsArray[i] = preferences.getBoolean("buttonToggle$i", i < 13) 417 optionsArray[i] = preferences.getBoolean("buttonToggle$i", i < 15)
416 } 418 }
417 419
418 val dialog = MaterialAlertDialogBuilder(requireContext()) 420 val dialog = MaterialAlertDialogBuilder(requireContext())
@@ -436,7 +438,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
436 dialog.getButton(AlertDialog.BUTTON_NEUTRAL) 438 dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
437 .setOnClickListener { 439 .setOnClickListener {
438 val isChecked = !optionsArray[0] 440 val isChecked = !optionsArray[0]
439 for (i in 0..14) { 441 Settings.overlayPreferences.forEachIndexed { i, _ ->
440 optionsArray[i] = isChecked 442 optionsArray[i] = isChecked
441 dialog.listView.setItemChecked(i, isChecked) 443 dialog.listView.setItemChecked(i, isChecked)
442 preferences.edit() 444 preferences.edit()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
index 6251ec783..c055c2e35 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
@@ -51,15 +51,23 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
51 51
52 private lateinit var windowInsets: WindowInsets 52 private lateinit var windowInsets: WindowInsets
53 53
54 var orientation = LANDSCAPE 54 var layout = LANDSCAPE
55 55
56 override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { 56 override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
57 super.onLayout(changed, left, top, right, bottom) 57 super.onLayout(changed, left, top, right, bottom)
58 58
59 windowInsets = rootWindowInsets 59 windowInsets = rootWindowInsets
60 60
61 if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) { 61 val overlayVersion = preferences.getInt(Settings.PREF_OVERLAY_VERSION, 0)
62 defaultOverlay() 62 if (overlayVersion != OVERLAY_VERSION) {
63 resetAllLayouts()
64 } else {
65 val layoutIndex = overlayLayouts.indexOf(layout)
66 val currentLayoutVersion =
67 preferences.getInt(Settings.overlayLayoutPrefs[layoutIndex], 0)
68 if (currentLayoutVersion != overlayLayoutVersions[layoutIndex]) {
69 resetCurrentLayout()
70 }
63 } 71 }
64 72
65 // Load the controls. 73 // Load the controls.
@@ -266,10 +274,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
266 MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) { 274 MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) {
267 // Persist button position by saving new place. 275 // Persist button position by saving new place.
268 saveControlPosition( 276 saveControlPosition(
269 buttonBeingConfigured!!.buttonId, 277 buttonBeingConfigured!!.prefId,
270 buttonBeingConfigured!!.bounds.centerX(), 278 buttonBeingConfigured!!.bounds.centerX(),
271 buttonBeingConfigured!!.bounds.centerY(), 279 buttonBeingConfigured!!.bounds.centerY(),
272 orientation 280 layout
273 ) 281 )
274 buttonBeingConfigured = null 282 buttonBeingConfigured = null
275 } 283 }
@@ -299,10 +307,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
299 MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) { 307 MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) {
300 // Persist button position by saving new place. 308 // Persist button position by saving new place.
301 saveControlPosition( 309 saveControlPosition(
302 dpadBeingConfigured!!.upId, 310 Settings.PREF_BUTTON_DPAD,
303 dpadBeingConfigured!!.bounds.centerX(), 311 dpadBeingConfigured!!.bounds.centerX(),
304 dpadBeingConfigured!!.bounds.centerY(), 312 dpadBeingConfigured!!.bounds.centerY(),
305 orientation 313 layout
306 ) 314 )
307 dpadBeingConfigured = null 315 dpadBeingConfigured = null
308 } 316 }
@@ -330,10 +338,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
330 MotionEvent.ACTION_UP, 338 MotionEvent.ACTION_UP,
331 MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) { 339 MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) {
332 saveControlPosition( 340 saveControlPosition(
333 joystickBeingConfigured!!.buttonId, 341 joystickBeingConfigured!!.prefId,
334 joystickBeingConfigured!!.bounds.centerX(), 342 joystickBeingConfigured!!.bounds.centerX(),
335 joystickBeingConfigured!!.bounds.centerY(), 343 joystickBeingConfigured!!.bounds.centerY(),
336 orientation 344 layout
337 ) 345 )
338 joystickBeingConfigured = null 346 joystickBeingConfigured = null
339 } 347 }
@@ -343,9 +351,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
343 return true 351 return true
344 } 352 }
345 353
346 private fun addOverlayControls(orientation: String) { 354 private fun addOverlayControls(layout: String) {
347 val windowSize = getSafeScreenSize(context) 355 val windowSize = getSafeScreenSize(context)
348 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_0, true)) { 356 if (preferences.getBoolean(Settings.PREF_BUTTON_A, true)) {
349 overlayButtons.add( 357 overlayButtons.add(
350 initializeOverlayButton( 358 initializeOverlayButton(
351 context, 359 context,
@@ -353,11 +361,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
353 R.drawable.facebutton_a, 361 R.drawable.facebutton_a,
354 R.drawable.facebutton_a_depressed, 362 R.drawable.facebutton_a_depressed,
355 ButtonType.BUTTON_A, 363 ButtonType.BUTTON_A,
356 orientation 364 Settings.PREF_BUTTON_A,
365 layout
357 ) 366 )
358 ) 367 )
359 } 368 }
360 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_1, true)) { 369 if (preferences.getBoolean(Settings.PREF_BUTTON_B, true)) {
361 overlayButtons.add( 370 overlayButtons.add(
362 initializeOverlayButton( 371 initializeOverlayButton(
363 context, 372 context,
@@ -365,11 +374,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
365 R.drawable.facebutton_b, 374 R.drawable.facebutton_b,
366 R.drawable.facebutton_b_depressed, 375 R.drawable.facebutton_b_depressed,
367 ButtonType.BUTTON_B, 376 ButtonType.BUTTON_B,
368 orientation 377 Settings.PREF_BUTTON_B,
378 layout
369 ) 379 )
370 ) 380 )
371 } 381 }
372 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_2, true)) { 382 if (preferences.getBoolean(Settings.PREF_BUTTON_X, true)) {
373 overlayButtons.add( 383 overlayButtons.add(
374 initializeOverlayButton( 384 initializeOverlayButton(
375 context, 385 context,
@@ -377,11 +387,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
377 R.drawable.facebutton_x, 387 R.drawable.facebutton_x,
378 R.drawable.facebutton_x_depressed, 388 R.drawable.facebutton_x_depressed,
379 ButtonType.BUTTON_X, 389 ButtonType.BUTTON_X,
380 orientation 390 Settings.PREF_BUTTON_X,
391 layout
381 ) 392 )
382 ) 393 )
383 } 394 }
384 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_3, true)) { 395 if (preferences.getBoolean(Settings.PREF_BUTTON_Y, true)) {
385 overlayButtons.add( 396 overlayButtons.add(
386 initializeOverlayButton( 397 initializeOverlayButton(
387 context, 398 context,
@@ -389,11 +400,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
389 R.drawable.facebutton_y, 400 R.drawable.facebutton_y,
390 R.drawable.facebutton_y_depressed, 401 R.drawable.facebutton_y_depressed,
391 ButtonType.BUTTON_Y, 402 ButtonType.BUTTON_Y,
392 orientation 403 Settings.PREF_BUTTON_Y,
404 layout
393 ) 405 )
394 ) 406 )
395 } 407 }
396 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_4, true)) { 408 if (preferences.getBoolean(Settings.PREF_BUTTON_L, true)) {
397 overlayButtons.add( 409 overlayButtons.add(
398 initializeOverlayButton( 410 initializeOverlayButton(
399 context, 411 context,
@@ -401,11 +413,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
401 R.drawable.l_shoulder, 413 R.drawable.l_shoulder,
402 R.drawable.l_shoulder_depressed, 414 R.drawable.l_shoulder_depressed,
403 ButtonType.TRIGGER_L, 415 ButtonType.TRIGGER_L,
404 orientation 416 Settings.PREF_BUTTON_L,
417 layout
405 ) 418 )
406 ) 419 )
407 } 420 }
408 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_5, true)) { 421 if (preferences.getBoolean(Settings.PREF_BUTTON_R, true)) {
409 overlayButtons.add( 422 overlayButtons.add(
410 initializeOverlayButton( 423 initializeOverlayButton(
411 context, 424 context,
@@ -413,11 +426,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
413 R.drawable.r_shoulder, 426 R.drawable.r_shoulder,
414 R.drawable.r_shoulder_depressed, 427 R.drawable.r_shoulder_depressed,
415 ButtonType.TRIGGER_R, 428 ButtonType.TRIGGER_R,
416 orientation 429 Settings.PREF_BUTTON_R,
430 layout
417 ) 431 )
418 ) 432 )
419 } 433 }
420 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_6, true)) { 434 if (preferences.getBoolean(Settings.PREF_BUTTON_ZL, true)) {
421 overlayButtons.add( 435 overlayButtons.add(
422 initializeOverlayButton( 436 initializeOverlayButton(
423 context, 437 context,
@@ -425,11 +439,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
425 R.drawable.zl_trigger, 439 R.drawable.zl_trigger,
426 R.drawable.zl_trigger_depressed, 440 R.drawable.zl_trigger_depressed,
427 ButtonType.TRIGGER_ZL, 441 ButtonType.TRIGGER_ZL,
428 orientation 442 Settings.PREF_BUTTON_ZL,
443 layout
429 ) 444 )
430 ) 445 )
431 } 446 }
432 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_7, true)) { 447 if (preferences.getBoolean(Settings.PREF_BUTTON_ZR, true)) {
433 overlayButtons.add( 448 overlayButtons.add(
434 initializeOverlayButton( 449 initializeOverlayButton(
435 context, 450 context,
@@ -437,11 +452,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
437 R.drawable.zr_trigger, 452 R.drawable.zr_trigger,
438 R.drawable.zr_trigger_depressed, 453 R.drawable.zr_trigger_depressed,
439 ButtonType.TRIGGER_ZR, 454 ButtonType.TRIGGER_ZR,
440 orientation 455 Settings.PREF_BUTTON_ZR,
456 layout
441 ) 457 )
442 ) 458 )
443 } 459 }
444 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_8, true)) { 460 if (preferences.getBoolean(Settings.PREF_BUTTON_PLUS, true)) {
445 overlayButtons.add( 461 overlayButtons.add(
446 initializeOverlayButton( 462 initializeOverlayButton(
447 context, 463 context,
@@ -449,11 +465,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
449 R.drawable.facebutton_plus, 465 R.drawable.facebutton_plus,
450 R.drawable.facebutton_plus_depressed, 466 R.drawable.facebutton_plus_depressed,
451 ButtonType.BUTTON_PLUS, 467 ButtonType.BUTTON_PLUS,
452 orientation 468 Settings.PREF_BUTTON_PLUS,
469 layout
453 ) 470 )
454 ) 471 )
455 } 472 }
456 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_9, true)) { 473 if (preferences.getBoolean(Settings.PREF_BUTTON_MINUS, true)) {
457 overlayButtons.add( 474 overlayButtons.add(
458 initializeOverlayButton( 475 initializeOverlayButton(
459 context, 476 context,
@@ -461,11 +478,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
461 R.drawable.facebutton_minus, 478 R.drawable.facebutton_minus,
462 R.drawable.facebutton_minus_depressed, 479 R.drawable.facebutton_minus_depressed,
463 ButtonType.BUTTON_MINUS, 480 ButtonType.BUTTON_MINUS,
464 orientation 481 Settings.PREF_BUTTON_MINUS,
482 layout
465 ) 483 )
466 ) 484 )
467 } 485 }
468 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_10, true)) { 486 if (preferences.getBoolean(Settings.PREF_BUTTON_DPAD, true)) {
469 overlayDpads.add( 487 overlayDpads.add(
470 initializeOverlayDpad( 488 initializeOverlayDpad(
471 context, 489 context,
@@ -473,11 +491,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
473 R.drawable.dpad_standard, 491 R.drawable.dpad_standard,
474 R.drawable.dpad_standard_cardinal_depressed, 492 R.drawable.dpad_standard_cardinal_depressed,
475 R.drawable.dpad_standard_diagonal_depressed, 493 R.drawable.dpad_standard_diagonal_depressed,
476 orientation 494 layout
477 ) 495 )
478 ) 496 )
479 } 497 }
480 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_11, true)) { 498 if (preferences.getBoolean(Settings.PREF_STICK_L, true)) {
481 overlayJoysticks.add( 499 overlayJoysticks.add(
482 initializeOverlayJoystick( 500 initializeOverlayJoystick(
483 context, 501 context,
@@ -487,11 +505,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
487 R.drawable.joystick_depressed, 505 R.drawable.joystick_depressed,
488 StickType.STICK_L, 506 StickType.STICK_L,
489 ButtonType.STICK_L, 507 ButtonType.STICK_L,
490 orientation 508 Settings.PREF_STICK_L,
509 layout
491 ) 510 )
492 ) 511 )
493 } 512 }
494 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_12, true)) { 513 if (preferences.getBoolean(Settings.PREF_STICK_R, true)) {
495 overlayJoysticks.add( 514 overlayJoysticks.add(
496 initializeOverlayJoystick( 515 initializeOverlayJoystick(
497 context, 516 context,
@@ -501,11 +520,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
501 R.drawable.joystick_depressed, 520 R.drawable.joystick_depressed,
502 StickType.STICK_R, 521 StickType.STICK_R,
503 ButtonType.STICK_R, 522 ButtonType.STICK_R,
504 orientation 523 Settings.PREF_STICK_R,
524 layout
505 ) 525 )
506 ) 526 )
507 } 527 }
508 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_13, false)) { 528 if (preferences.getBoolean(Settings.PREF_BUTTON_HOME, false)) {
509 overlayButtons.add( 529 overlayButtons.add(
510 initializeOverlayButton( 530 initializeOverlayButton(
511 context, 531 context,
@@ -513,11 +533,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
513 R.drawable.facebutton_home, 533 R.drawable.facebutton_home,
514 R.drawable.facebutton_home_depressed, 534 R.drawable.facebutton_home_depressed,
515 ButtonType.BUTTON_HOME, 535 ButtonType.BUTTON_HOME,
516 orientation 536 Settings.PREF_BUTTON_HOME,
537 layout
517 ) 538 )
518 ) 539 )
519 } 540 }
520 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_14, false)) { 541 if (preferences.getBoolean(Settings.PREF_BUTTON_SCREENSHOT, false)) {
521 overlayButtons.add( 542 overlayButtons.add(
522 initializeOverlayButton( 543 initializeOverlayButton(
523 context, 544 context,
@@ -525,7 +546,34 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
525 R.drawable.facebutton_screenshot, 546 R.drawable.facebutton_screenshot,
526 R.drawable.facebutton_screenshot_depressed, 547 R.drawable.facebutton_screenshot_depressed,
527 ButtonType.BUTTON_CAPTURE, 548 ButtonType.BUTTON_CAPTURE,
528 orientation 549 Settings.PREF_BUTTON_SCREENSHOT,
550 layout
551 )
552 )
553 }
554 if (preferences.getBoolean(Settings.PREF_BUTTON_STICK_L, true)) {
555 overlayButtons.add(
556 initializeOverlayButton(
557 context,
558 windowSize,
559 R.drawable.button_l3,
560 R.drawable.button_l3_depressed,
561 ButtonType.STICK_L,
562 Settings.PREF_BUTTON_STICK_L,
563 layout
564 )
565 )
566 }
567 if (preferences.getBoolean(Settings.PREF_BUTTON_STICK_R, true)) {
568 overlayButtons.add(
569 initializeOverlayButton(
570 context,
571 windowSize,
572 R.drawable.button_r3,
573 R.drawable.button_r3_depressed,
574 ButtonType.STICK_R,
575 Settings.PREF_BUTTON_STICK_R,
576 layout
529 ) 577 )
530 ) 578 )
531 } 579 }
@@ -539,18 +587,18 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
539 587
540 // Add all the enabled overlay items back to the HashSet. 588 // Add all the enabled overlay items back to the HashSet.
541 if (EmulationMenuSettings.showOverlay) { 589 if (EmulationMenuSettings.showOverlay) {
542 addOverlayControls(orientation) 590 addOverlayControls(layout)
543 } 591 }
544 invalidate() 592 invalidate()
545 } 593 }
546 594
547 private fun saveControlPosition(sharedPrefsId: Int, x: Int, y: Int, orientation: String) { 595 private fun saveControlPosition(prefId: String, x: Int, y: Int, layout: String) {
548 val windowSize = getSafeScreenSize(context) 596 val windowSize = getSafeScreenSize(context)
549 val min = windowSize.first 597 val min = windowSize.first
550 val max = windowSize.second 598 val max = windowSize.second
551 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() 599 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
552 .putFloat("$sharedPrefsId-X$orientation", (x - min.x).toFloat() / max.x) 600 .putFloat("$prefId-X$layout", (x - min.x).toFloat() / max.x)
553 .putFloat("$sharedPrefsId-Y$orientation", (y - min.y).toFloat() / max.y) 601 .putFloat("$prefId-Y$layout", (y - min.y).toFloat() / max.y)
554 .apply() 602 .apply()
555 } 603 }
556 604
@@ -558,19 +606,31 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
558 inEditMode = editMode 606 inEditMode = editMode
559 } 607 }
560 608
561 private fun defaultOverlay() { 609 private fun resetCurrentLayout() {
562 if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) { 610 defaultOverlayByLayout(layout)
563 defaultOverlayByLayout(orientation) 611 val layoutIndex = overlayLayouts.indexOf(layout)
564 }
565
566 resetButtonPlacement()
567 preferences.edit() 612 preferences.edit()
568 .putBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", true) 613 .putInt(Settings.overlayLayoutPrefs[layoutIndex], overlayLayoutVersions[layoutIndex])
569 .apply() 614 .apply()
570 } 615 }
571 616
572 fun resetButtonPlacement() { 617 private fun resetAllLayouts() {
573 defaultOverlayByLayout(orientation) 618 val editor = preferences.edit()
619 overlayLayouts.forEachIndexed { i, layout ->
620 defaultOverlayByLayout(layout)
621 editor.putInt(Settings.overlayLayoutPrefs[i], overlayLayoutVersions[i])
622 }
623 editor.putInt(Settings.PREF_OVERLAY_VERSION, OVERLAY_VERSION)
624 editor.apply()
625 }
626
627 fun resetLayoutVisibilityAndPlacement() {
628 defaultOverlayByLayout(layout)
629 val editor = preferences.edit()
630 Settings.overlayPreferences.forEachIndexed { _, pref ->
631 editor.remove(pref)
632 }
633 editor.apply()
574 refreshControls() 634 refreshControls()
575 } 635 }
576 636
@@ -604,7 +664,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
604 R.integer.SWITCH_STICK_R_X, 664 R.integer.SWITCH_STICK_R_X,
605 R.integer.SWITCH_STICK_R_Y, 665 R.integer.SWITCH_STICK_R_Y,
606 R.integer.SWITCH_STICK_L_X, 666 R.integer.SWITCH_STICK_L_X,
607 R.integer.SWITCH_STICK_L_Y 667 R.integer.SWITCH_STICK_L_Y,
668 R.integer.SWITCH_BUTTON_STICK_L_X,
669 R.integer.SWITCH_BUTTON_STICK_L_Y,
670 R.integer.SWITCH_BUTTON_STICK_R_X,
671 R.integer.SWITCH_BUTTON_STICK_R_Y
608 ) 672 )
609 673
610 private val portraitResources = arrayOf( 674 private val portraitResources = arrayOf(
@@ -637,7 +701,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
637 R.integer.SWITCH_STICK_R_X_PORTRAIT, 701 R.integer.SWITCH_STICK_R_X_PORTRAIT,
638 R.integer.SWITCH_STICK_R_Y_PORTRAIT, 702 R.integer.SWITCH_STICK_R_Y_PORTRAIT,
639 R.integer.SWITCH_STICK_L_X_PORTRAIT, 703 R.integer.SWITCH_STICK_L_X_PORTRAIT,
640 R.integer.SWITCH_STICK_L_Y_PORTRAIT 704 R.integer.SWITCH_STICK_L_Y_PORTRAIT,
705 R.integer.SWITCH_BUTTON_STICK_L_X_PORTRAIT,
706 R.integer.SWITCH_BUTTON_STICK_L_Y_PORTRAIT,
707 R.integer.SWITCH_BUTTON_STICK_R_X_PORTRAIT,
708 R.integer.SWITCH_BUTTON_STICK_R_Y_PORTRAIT
641 ) 709 )
642 710
643 private val foldableResources = arrayOf( 711 private val foldableResources = arrayOf(
@@ -670,139 +738,159 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
670 R.integer.SWITCH_STICK_R_X_FOLDABLE, 738 R.integer.SWITCH_STICK_R_X_FOLDABLE,
671 R.integer.SWITCH_STICK_R_Y_FOLDABLE, 739 R.integer.SWITCH_STICK_R_Y_FOLDABLE,
672 R.integer.SWITCH_STICK_L_X_FOLDABLE, 740 R.integer.SWITCH_STICK_L_X_FOLDABLE,
673 R.integer.SWITCH_STICK_L_Y_FOLDABLE 741 R.integer.SWITCH_STICK_L_Y_FOLDABLE,
742 R.integer.SWITCH_BUTTON_STICK_L_X_FOLDABLE,
743 R.integer.SWITCH_BUTTON_STICK_L_Y_FOLDABLE,
744 R.integer.SWITCH_BUTTON_STICK_R_X_FOLDABLE,
745 R.integer.SWITCH_BUTTON_STICK_R_Y_FOLDABLE
674 ) 746 )
675 747
676 private fun getResourceValue(orientation: String, position: Int): Float { 748 private fun getResourceValue(layout: String, position: Int): Float {
677 return when (orientation) { 749 return when (layout) {
678 PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000 750 PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000
679 FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000 751 FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000
680 else -> resources.getInteger(landscapeResources[position]).toFloat() / 1000 752 else -> resources.getInteger(landscapeResources[position]).toFloat() / 1000
681 } 753 }
682 } 754 }
683 755
684 private fun defaultOverlayByLayout(orientation: String) { 756 private fun defaultOverlayByLayout(layout: String) {
685 // Each value represents the position of the button in relation to the screen size without insets. 757 // Each value represents the position of the button in relation to the screen size without insets.
686 preferences.edit() 758 preferences.edit()
687 .putFloat( 759 .putFloat(
688 ButtonType.BUTTON_A.toString() + "-X$orientation", 760 "${Settings.PREF_BUTTON_A}-X$layout",
689 getResourceValue(orientation, 0) 761 getResourceValue(layout, 0)
762 )
763 .putFloat(
764 "${Settings.PREF_BUTTON_A}-Y$layout",
765 getResourceValue(layout, 1)
766 )
767 .putFloat(
768 "${Settings.PREF_BUTTON_B}-X$layout",
769 getResourceValue(layout, 2)
770 )
771 .putFloat(
772 "${Settings.PREF_BUTTON_B}-Y$layout",
773 getResourceValue(layout, 3)
774 )
775 .putFloat(
776 "${Settings.PREF_BUTTON_X}-X$layout",
777 getResourceValue(layout, 4)
690 ) 778 )
691 .putFloat( 779 .putFloat(
692 ButtonType.BUTTON_A.toString() + "-Y$orientation", 780 "${Settings.PREF_BUTTON_X}-Y$layout",
693 getResourceValue(orientation, 1) 781 getResourceValue(layout, 5)
694 ) 782 )
695 .putFloat( 783 .putFloat(
696 ButtonType.BUTTON_B.toString() + "-X$orientation", 784 "${Settings.PREF_BUTTON_Y}-X$layout",
697 getResourceValue(orientation, 2) 785 getResourceValue(layout, 6)
698 ) 786 )
699 .putFloat( 787 .putFloat(
700 ButtonType.BUTTON_B.toString() + "-Y$orientation", 788 "${Settings.PREF_BUTTON_Y}-Y$layout",
701 getResourceValue(orientation, 3) 789 getResourceValue(layout, 7)
702 ) 790 )
703 .putFloat( 791 .putFloat(
704 ButtonType.BUTTON_X.toString() + "-X$orientation", 792 "${Settings.PREF_BUTTON_ZL}-X$layout",
705 getResourceValue(orientation, 4) 793 getResourceValue(layout, 8)
706 ) 794 )
707 .putFloat( 795 .putFloat(
708 ButtonType.BUTTON_X.toString() + "-Y$orientation", 796 "${Settings.PREF_BUTTON_ZL}-Y$layout",
709 getResourceValue(orientation, 5) 797 getResourceValue(layout, 9)
710 ) 798 )
711 .putFloat( 799 .putFloat(
712 ButtonType.BUTTON_Y.toString() + "-X$orientation", 800 "${Settings.PREF_BUTTON_ZR}-X$layout",
713 getResourceValue(orientation, 6) 801 getResourceValue(layout, 10)
714 ) 802 )
715 .putFloat( 803 .putFloat(
716 ButtonType.BUTTON_Y.toString() + "-Y$orientation", 804 "${Settings.PREF_BUTTON_ZR}-Y$layout",
717 getResourceValue(orientation, 7) 805 getResourceValue(layout, 11)
718 ) 806 )
719 .putFloat( 807 .putFloat(
720 ButtonType.TRIGGER_ZL.toString() + "-X$orientation", 808 "${Settings.PREF_BUTTON_DPAD}-X$layout",
721 getResourceValue(orientation, 8) 809 getResourceValue(layout, 12)
722 ) 810 )
723 .putFloat( 811 .putFloat(
724 ButtonType.TRIGGER_ZL.toString() + "-Y$orientation", 812 "${Settings.PREF_BUTTON_DPAD}-Y$layout",
725 getResourceValue(orientation, 9) 813 getResourceValue(layout, 13)
726 ) 814 )
727 .putFloat( 815 .putFloat(
728 ButtonType.TRIGGER_ZR.toString() + "-X$orientation", 816 "${Settings.PREF_BUTTON_L}-X$layout",
729 getResourceValue(orientation, 10) 817 getResourceValue(layout, 14)
730 ) 818 )
731 .putFloat( 819 .putFloat(
732 ButtonType.TRIGGER_ZR.toString() + "-Y$orientation", 820 "${Settings.PREF_BUTTON_L}-Y$layout",
733 getResourceValue(orientation, 11) 821 getResourceValue(layout, 15)
734 ) 822 )
735 .putFloat( 823 .putFloat(
736 ButtonType.DPAD_UP.toString() + "-X$orientation", 824 "${Settings.PREF_BUTTON_R}-X$layout",
737 getResourceValue(orientation, 12) 825 getResourceValue(layout, 16)
738 ) 826 )
739 .putFloat( 827 .putFloat(
740 ButtonType.DPAD_UP.toString() + "-Y$orientation", 828 "${Settings.PREF_BUTTON_R}-Y$layout",
741 getResourceValue(orientation, 13) 829 getResourceValue(layout, 17)
742 ) 830 )
743 .putFloat( 831 .putFloat(
744 ButtonType.TRIGGER_L.toString() + "-X$orientation", 832 "${Settings.PREF_BUTTON_PLUS}-X$layout",
745 getResourceValue(orientation, 14) 833 getResourceValue(layout, 18)
746 ) 834 )
747 .putFloat( 835 .putFloat(
748 ButtonType.TRIGGER_L.toString() + "-Y$orientation", 836 "${Settings.PREF_BUTTON_PLUS}-Y$layout",
749 getResourceValue(orientation, 15) 837 getResourceValue(layout, 19)
750 ) 838 )
751 .putFloat( 839 .putFloat(
752 ButtonType.TRIGGER_R.toString() + "-X$orientation", 840 "${Settings.PREF_BUTTON_MINUS}-X$layout",
753 getResourceValue(orientation, 16) 841 getResourceValue(layout, 20)
754 ) 842 )
755 .putFloat( 843 .putFloat(
756 ButtonType.TRIGGER_R.toString() + "-Y$orientation", 844 "${Settings.PREF_BUTTON_MINUS}-Y$layout",
757 getResourceValue(orientation, 17) 845 getResourceValue(layout, 21)
758 ) 846 )
759 .putFloat( 847 .putFloat(
760 ButtonType.BUTTON_PLUS.toString() + "-X$orientation", 848 "${Settings.PREF_BUTTON_HOME}-X$layout",
761 getResourceValue(orientation, 18) 849 getResourceValue(layout, 22)
762 ) 850 )
763 .putFloat( 851 .putFloat(
764 ButtonType.BUTTON_PLUS.toString() + "-Y$orientation", 852 "${Settings.PREF_BUTTON_HOME}-Y$layout",
765 getResourceValue(orientation, 19) 853 getResourceValue(layout, 23)
766 ) 854 )
767 .putFloat( 855 .putFloat(
768 ButtonType.BUTTON_MINUS.toString() + "-X$orientation", 856 "${Settings.PREF_BUTTON_SCREENSHOT}-X$layout",
769 getResourceValue(orientation, 20) 857 getResourceValue(layout, 24)
770 ) 858 )
771 .putFloat( 859 .putFloat(
772 ButtonType.BUTTON_MINUS.toString() + "-Y$orientation", 860 "${Settings.PREF_BUTTON_SCREENSHOT}-Y$layout",
773 getResourceValue(orientation, 21) 861 getResourceValue(layout, 25)
774 ) 862 )
775 .putFloat( 863 .putFloat(
776 ButtonType.BUTTON_HOME.toString() + "-X$orientation", 864 "${Settings.PREF_STICK_R}-X$layout",
777 getResourceValue(orientation, 22) 865 getResourceValue(layout, 26)
778 ) 866 )
779 .putFloat( 867 .putFloat(
780 ButtonType.BUTTON_HOME.toString() + "-Y$orientation", 868 "${Settings.PREF_STICK_R}-Y$layout",
781 getResourceValue(orientation, 23) 869 getResourceValue(layout, 27)
782 ) 870 )
783 .putFloat( 871 .putFloat(
784 ButtonType.BUTTON_CAPTURE.toString() + "-X$orientation", 872 "${Settings.PREF_STICK_L}-X$layout",
785 getResourceValue(orientation, 24) 873 getResourceValue(layout, 28)
786 ) 874 )
787 .putFloat( 875 .putFloat(
788 ButtonType.BUTTON_CAPTURE.toString() + "-Y$orientation", 876 "${Settings.PREF_STICK_L}-Y$layout",
789 getResourceValue(orientation, 25) 877 getResourceValue(layout, 29)
790 ) 878 )
791 .putFloat( 879 .putFloat(
792 ButtonType.STICK_R.toString() + "-X$orientation", 880 "${Settings.PREF_BUTTON_STICK_L}-X$layout",
793 getResourceValue(orientation, 26) 881 getResourceValue(layout, 30)
794 ) 882 )
795 .putFloat( 883 .putFloat(
796 ButtonType.STICK_R.toString() + "-Y$orientation", 884 "${Settings.PREF_BUTTON_STICK_L}-Y$layout",
797 getResourceValue(orientation, 27) 885 getResourceValue(layout, 31)
798 ) 886 )
799 .putFloat( 887 .putFloat(
800 ButtonType.STICK_L.toString() + "-X$orientation", 888 "${Settings.PREF_BUTTON_STICK_R}-X$layout",
801 getResourceValue(orientation, 28) 889 getResourceValue(layout, 32)
802 ) 890 )
803 .putFloat( 891 .putFloat(
804 ButtonType.STICK_L.toString() + "-Y$orientation", 892 "${Settings.PREF_BUTTON_STICK_R}-Y$layout",
805 getResourceValue(orientation, 29) 893 getResourceValue(layout, 33)
806 ) 894 )
807 .apply() 895 .apply()
808 } 896 }
@@ -812,12 +900,30 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
812 } 900 }
813 901
814 companion object { 902 companion object {
903 // Increase this number every time there is a breaking change to every overlay layout
904 const val OVERLAY_VERSION = 1
905
906 // Increase the corresponding layout version number whenever that layout has a breaking change
907 private const val LANDSCAPE_OVERLAY_VERSION = 1
908 private const val PORTRAIT_OVERLAY_VERSION = 1
909 private const val FOLDABLE_OVERLAY_VERSION = 1
910 val overlayLayoutVersions = listOf(
911 LANDSCAPE_OVERLAY_VERSION,
912 PORTRAIT_OVERLAY_VERSION,
913 FOLDABLE_OVERLAY_VERSION
914 )
915
815 private val preferences: SharedPreferences = 916 private val preferences: SharedPreferences =
816 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 917 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
817 918
818 const val LANDSCAPE = "" 919 const val LANDSCAPE = "_Landscape"
819 const val PORTRAIT = "_Portrait" 920 const val PORTRAIT = "_Portrait"
820 const val FOLDABLE = "_Foldable" 921 const val FOLDABLE = "_Foldable"
922 val overlayLayouts = listOf(
923 LANDSCAPE,
924 PORTRAIT,
925 FOLDABLE
926 )
821 927
822 /** 928 /**
823 * Resizes a [Bitmap] by a given scale factor 929 * Resizes a [Bitmap] by a given scale factor
@@ -948,6 +1054,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
948 * @param defaultResId The resource ID of the [Drawable] to get the [Bitmap] of (Default State). 1054 * @param defaultResId The resource ID of the [Drawable] to get the [Bitmap] of (Default State).
949 * @param pressedResId The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State). 1055 * @param pressedResId The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State).
950 * @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents. 1056 * @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents.
1057 * @param prefId Identifier for determining where a button appears on screen.
1058 * @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE].
951 * @return An [InputOverlayDrawableButton] with the correct drawing bounds set. 1059 * @return An [InputOverlayDrawableButton] with the correct drawing bounds set.
952 */ 1060 */
953 private fun initializeOverlayButton( 1061 private fun initializeOverlayButton(
@@ -956,7 +1064,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
956 defaultResId: Int, 1064 defaultResId: Int,
957 pressedResId: Int, 1065 pressedResId: Int,
958 buttonId: Int, 1066 buttonId: Int,
959 orientation: String 1067 prefId: String,
1068 layout: String
960 ): InputOverlayDrawableButton { 1069 ): InputOverlayDrawableButton {
961 // Resources handle for fetching the initial Drawable resource. 1070 // Resources handle for fetching the initial Drawable resource.
962 val res = context.resources 1071 val res = context.resources
@@ -964,17 +1073,20 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
964 // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton. 1073 // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton.
965 val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 1074 val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
966 1075
967 // Decide scale based on button ID and user preference 1076 // Decide scale based on button preference ID and user preference
968 var scale: Float = when (buttonId) { 1077 var scale: Float = when (prefId) {
969 ButtonType.BUTTON_HOME, 1078 Settings.PREF_BUTTON_HOME,
970 ButtonType.BUTTON_CAPTURE, 1079 Settings.PREF_BUTTON_SCREENSHOT,
971 ButtonType.BUTTON_PLUS, 1080 Settings.PREF_BUTTON_PLUS,
972 ButtonType.BUTTON_MINUS -> 0.07f 1081 Settings.PREF_BUTTON_MINUS -> 0.07f
973 1082
974 ButtonType.TRIGGER_L, 1083 Settings.PREF_BUTTON_L,
975 ButtonType.TRIGGER_R, 1084 Settings.PREF_BUTTON_R,
976 ButtonType.TRIGGER_ZL, 1085 Settings.PREF_BUTTON_ZL,
977 ButtonType.TRIGGER_ZR -> 0.26f 1086 Settings.PREF_BUTTON_ZR -> 0.26f
1087
1088 Settings.PREF_BUTTON_STICK_L,
1089 Settings.PREF_BUTTON_STICK_R -> 0.155f
978 1090
979 else -> 0.11f 1091 else -> 0.11f
980 } 1092 }
@@ -984,8 +1096,13 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
984 // Initialize the InputOverlayDrawableButton. 1096 // Initialize the InputOverlayDrawableButton.
985 val defaultStateBitmap = getBitmap(context, defaultResId, scale) 1097 val defaultStateBitmap = getBitmap(context, defaultResId, scale)
986 val pressedStateBitmap = getBitmap(context, pressedResId, scale) 1098 val pressedStateBitmap = getBitmap(context, pressedResId, scale)
987 val overlayDrawable = 1099 val overlayDrawable = InputOverlayDrawableButton(
988 InputOverlayDrawableButton(res, defaultStateBitmap, pressedStateBitmap, buttonId) 1100 res,
1101 defaultStateBitmap,
1102 pressedStateBitmap,
1103 buttonId,
1104 prefId
1105 )
989 1106
990 // Get the minimum and maximum coordinates of the screen where the button can be placed. 1107 // Get the minimum and maximum coordinates of the screen where the button can be placed.
991 val min = windowSize.first 1108 val min = windowSize.first
@@ -993,8 +1110,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
993 1110
994 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. 1111 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
995 // These were set in the input overlay configuration menu. 1112 // These were set in the input overlay configuration menu.
996 val xKey = "$buttonId-X$orientation" 1113 val xKey = "$prefId-X$layout"
997 val yKey = "$buttonId-Y$orientation" 1114 val yKey = "$prefId-Y$layout"
998 val drawableXPercent = sPrefs.getFloat(xKey, 0f) 1115 val drawableXPercent = sPrefs.getFloat(xKey, 0f)
999 val drawableYPercent = sPrefs.getFloat(yKey, 0f) 1116 val drawableYPercent = sPrefs.getFloat(yKey, 0f)
1000 val drawableX = (drawableXPercent * max.x + min.x).toInt() 1117 val drawableX = (drawableXPercent * max.x + min.x).toInt()
@@ -1029,7 +1146,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1029 * @param defaultResId The [Bitmap] resource ID of the default state. 1146 * @param defaultResId The [Bitmap] resource ID of the default state.
1030 * @param pressedOneDirectionResId The [Bitmap] resource ID of the pressed state in one direction. 1147 * @param pressedOneDirectionResId The [Bitmap] resource ID of the pressed state in one direction.
1031 * @param pressedTwoDirectionsResId The [Bitmap] resource ID of the pressed state in two directions. 1148 * @param pressedTwoDirectionsResId The [Bitmap] resource ID of the pressed state in two directions.
1032 * @return the initialized [InputOverlayDrawableDpad] 1149 * @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE].
1150 * @return The initialized [InputOverlayDrawableDpad]
1033 */ 1151 */
1034 private fun initializeOverlayDpad( 1152 private fun initializeOverlayDpad(
1035 context: Context, 1153 context: Context,
@@ -1037,7 +1155,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1037 defaultResId: Int, 1155 defaultResId: Int,
1038 pressedOneDirectionResId: Int, 1156 pressedOneDirectionResId: Int,
1039 pressedTwoDirectionsResId: Int, 1157 pressedTwoDirectionsResId: Int,
1040 orientation: String 1158 layout: String
1041 ): InputOverlayDrawableDpad { 1159 ): InputOverlayDrawableDpad {
1042 // Resources handle for fetching the initial Drawable resource. 1160 // Resources handle for fetching the initial Drawable resource.
1043 val res = context.resources 1161 val res = context.resources
@@ -1074,8 +1192,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1074 1192
1075 // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay. 1193 // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
1076 // These were set in the input overlay configuration menu. 1194 // These were set in the input overlay configuration menu.
1077 val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-X$orientation", 0f) 1195 val drawableXPercent = sPrefs.getFloat("${Settings.PREF_BUTTON_DPAD}-X$layout", 0f)
1078 val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-Y$orientation", 0f) 1196 val drawableYPercent = sPrefs.getFloat("${Settings.PREF_BUTTON_DPAD}-Y$layout", 0f)
1079 val drawableX = (drawableXPercent * max.x + min.x).toInt() 1197 val drawableX = (drawableXPercent * max.x + min.x).toInt()
1080 val drawableY = (drawableYPercent * max.y + min.y).toInt() 1198 val drawableY = (drawableYPercent * max.y + min.y).toInt()
1081 val width = overlayDrawable.width 1199 val width = overlayDrawable.width
@@ -1107,7 +1225,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1107 * @param pressedResInner Resource ID for the pressed inner image of the joystick. 1225 * @param pressedResInner Resource ID for the pressed inner image of the joystick.
1108 * @param joystick Identifier for which joystick this is. 1226 * @param joystick Identifier for which joystick this is.
1109 * @param button Identifier for which joystick button this is. 1227 * @param button Identifier for which joystick button this is.
1110 * @return the initialized [InputOverlayDrawableJoystick]. 1228 * @param prefId Identifier for determining where a button appears on screen.
1229 * @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE].
1230 * @return The initialized [InputOverlayDrawableJoystick].
1111 */ 1231 */
1112 private fun initializeOverlayJoystick( 1232 private fun initializeOverlayJoystick(
1113 context: Context, 1233 context: Context,
@@ -1117,7 +1237,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1117 pressedResInner: Int, 1237 pressedResInner: Int,
1118 joystick: Int, 1238 joystick: Int,
1119 button: Int, 1239 button: Int,
1120 orientation: String 1240 prefId: String,
1241 layout: String
1121 ): InputOverlayDrawableJoystick { 1242 ): InputOverlayDrawableJoystick {
1122 // Resources handle for fetching the initial Drawable resource. 1243 // Resources handle for fetching the initial Drawable resource.
1123 val res = context.resources 1244 val res = context.resources
@@ -1141,8 +1262,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1141 1262
1142 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. 1263 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
1143 // These were set in the input overlay configuration menu. 1264 // These were set in the input overlay configuration menu.
1144 val drawableXPercent = sPrefs.getFloat("$button-X$orientation", 0f) 1265 val drawableXPercent = sPrefs.getFloat("$prefId-X$layout", 0f)
1145 val drawableYPercent = sPrefs.getFloat("$button-Y$orientation", 0f) 1266 val drawableYPercent = sPrefs.getFloat("$prefId-Y$layout", 0f)
1146 val drawableX = (drawableXPercent * max.x + min.x).toInt() 1267 val drawableX = (drawableXPercent * max.x + min.x).toInt()
1147 val drawableY = (drawableYPercent * max.y + min.y).toInt() 1268 val drawableY = (drawableYPercent * max.y + min.y).toInt()
1148 val outerScale = 1.66f 1269 val outerScale = 1.66f
@@ -1168,7 +1289,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1168 outerRect, 1289 outerRect,
1169 innerRect, 1290 innerRect,
1170 joystick, 1291 joystick,
1171 button 1292 button,
1293 prefId
1172 ) 1294 )
1173 1295
1174 // Need to set the image's position 1296 // Need to set the image's position
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
index 4a93e0b14..2c28dda88 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
@@ -24,7 +24,8 @@ class InputOverlayDrawableButton(
24 res: Resources, 24 res: Resources,
25 defaultStateBitmap: Bitmap, 25 defaultStateBitmap: Bitmap,
26 pressedStateBitmap: Bitmap, 26 pressedStateBitmap: Bitmap,
27 val buttonId: Int 27 val buttonId: Int,
28 val prefId: String
28) { 29) {
29 // The ID value what motion event is tracking 30 // The ID value what motion event is tracking
30 var trackId: Int 31 var trackId: Int
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
index fb48f584d..518b1e783 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
@@ -37,7 +37,8 @@ class InputOverlayDrawableJoystick(
37 rectOuter: Rect, 37 rectOuter: Rect,
38 rectInner: Rect, 38 rectInner: Rect,
39 val joystickId: Int, 39 val joystickId: Int,
40 val buttonId: Int 40 val buttonId: Int,
41 val prefId: String
41) { 42) {
42 // The ID value what motion event is tracking 43 // The ID value what motion event is tracking
43 var trackId = -1 44 var trackId = -1
diff --git a/src/android/app/src/main/res/drawable/button_l3.xml b/src/android/app/src/main/res/drawable/button_l3.xml
new file mode 100644
index 000000000..0cb28836e
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_l3.xml
@@ -0,0 +1,128 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:aapt="http://schemas.android.com/aapt"
3 android:width="34.963dp"
4 android:height="37.265dp"
5 android:viewportWidth="34.963"
6 android:viewportHeight="37.265">
7 <path
8 android:fillAlpha="0.5"
9 android:pathData="M19.451,19.024A3.498,3.498 0,0 0,21.165 19.508c1.336,0 1.749,-0.852 1.738,-1.49 0,-1.077 -0.982,-1.537 -1.987,-1.537L20.327,16.481L20.327,15.7L20.901,15.7c0.757,0 1.714,-0.392 1.714,-1.302C22.621,13.785 22.224,13.229 21.271,13.229a2.834,2.834 0,0 0,-1.537 0.529l-0.265,-0.757a3.662,3.662 0,0 1,2.008 -0.59c1.513,0 2.201,0.897 2.201,1.834 0,0.794 -0.474,1.466 -1.421,1.807l0,0.024c0.947,0.19 1.714,0.9 1.714,1.976C23.967,19.27 23.017,20.346 21.165,20.346a3.929,3.929 135,0 1,-1.998 -0.529z"
10 android:strokeAlpha="0.6">
11 <aapt:attr name="android:fillColor">
12 <gradient
13 android:endX="21.568"
14 android:endY="33.938"
15 android:startX="21.568"
16 android:startY="16.14"
17 android:type="linear">
18 <item
19 android:color="#FFC3C4C5"
20 android:offset="0" />
21 <item
22 android:color="#FFC5C6C6"
23 android:offset="0.03" />
24 <item
25 android:color="#FFC7C7C7"
26 android:offset="0.19" />
27 <item
28 android:color="#DBB5B5B5"
29 android:offset="0.44" />
30 <item
31 android:color="#7F878787"
32 android:offset="1" />
33 </gradient>
34 </aapt:attr>
35 </path>
36 <path
37 android:fillAlpha="0.5"
38 android:pathData="M16.062,9.353 L9.624,3.405A1.963,1.963 0,0 1,10.955 0l12.88,0a1.963,1.963 135,0 1,1.323 3.405L18.726,9.353a1.961,1.961 135,0 1,-2.664 0z"
39 android:strokeAlpha="0.6">
40 <aapt:attr name="android:fillColor">
41 <gradient
42 android:endX="17.395"
43 android:endY="18.74"
44 android:startX="17.395"
45 android:startY="-1.296"
46 android:type="linear">
47 <item
48 android:color="#FFC3C4C5"
49 android:offset="0" />
50 <item
51 android:color="#FFC5C6C6"
52 android:offset="0.03" />
53 <item
54 android:color="#FFC7C7C7"
55 android:offset="0.19" />
56 <item
57 android:color="#DBB5B5B5"
58 android:offset="0.44" />
59 <item
60 android:color="#7F878787"
61 android:offset="1" />
62 </gradient>
63 </aapt:attr>
64 </path>
65 <path
66 android:fillAlpha="0.5"
67 android:pathData="m25.79,5.657l0,0a2.09,2.09 45,0 0,0.23 3.262c3.522,2.402 4.762,5.927 4.741,10.52A13.279,13.279 135,0 1,4.206 19.365c0,-4.516 0.931,-7.71 4.374,-10.107a2.098,2.098 0,0 0,0.233 -3.265l0,0a2.101,2.101 135,0 0,-2.646 -0.169C1.433,9.133 -0.266,13.941 0.033,20.233a17.468,17.468 0,0 0,34.925 -0.868c0,-6.006 -1.971,-10.771 -6.585,-13.917a2.088,2.088 45,0 0,-2.582 0.209z"
68 android:strokeAlpha="0.6">
69 <aapt:attr name="android:fillColor">
70 <gradient
71 android:centerX="17.477"
72 android:centerY="19.92"
73 android:gradientRadius="17.201"
74 android:type="radial">
75 <item
76 android:color="#FFC3C4C5"
77 android:offset="0.58" />
78 <item
79 android:color="#FFC6C6C6"
80 android:offset="0.84" />
81 <item
82 android:color="#FFC7C7C7"
83 android:offset="0.88" />
84 <item
85 android:color="#FFC2C2C2"
86 android:offset="0.91" />
87 <item
88 android:color="#FFB5B5B5"
89 android:offset="0.94" />
90 <item
91 android:color="#FF9E9E9E"
92 android:offset="0.98" />
93 <item
94 android:color="#FF8F8F8F"
95 android:offset="1" />
96 </gradient>
97 </aapt:attr>
98 </path>
99 <path
100 android:fillAlpha="0.5"
101 android:pathData="m12.516,12.729l2,0l0,13.822l6.615,0l0,1.68L12.516,28.231Z"
102 android:strokeAlpha="0.6">
103 <aapt:attr name="android:fillColor">
104 <gradient
105 android:endX="16.829"
106 android:endY="46.882"
107 android:startX="16.829"
108 android:startY="20.479"
109 android:type="linear">
110 <item
111 android:color="#FFC3C4C5"
112 android:offset="0" />
113 <item
114 android:color="#FFC5C6C6"
115 android:offset="0.03" />
116 <item
117 android:color="#FFC7C7C7"
118 android:offset="0.19" />
119 <item
120 android:color="#DBB5B5B5"
121 android:offset="0.44" />
122 <item
123 android:color="#7F878787"
124 android:offset="1" />
125 </gradient>
126 </aapt:attr>
127 </path>
128</vector>
diff --git a/src/android/app/src/main/res/drawable/button_l3_depressed.xml b/src/android/app/src/main/res/drawable/button_l3_depressed.xml
new file mode 100644
index 000000000..b078dedc9
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_l3_depressed.xml
@@ -0,0 +1,75 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:aapt="http://schemas.android.com/aapt"
3 android:width="34.963dp"
4 android:height="37.265dp"
5 android:viewportWidth="34.963"
6 android:viewportHeight="37.265">
7 <path
8 android:fillAlpha="0.3"
9 android:fillColor="#151515"
10 android:pathData="M16.062,9.353 L9.624,3.405A1.963,1.963 0,0 1,10.955 0l12.88,0a1.963,1.963 135,0 1,1.323 3.405L18.726,9.353a1.961,1.961 135,0 1,-2.664 0z"
11 android:strokeAlpha="0.3" />
12 <path
13 android:fillAlpha="0.6"
14 android:fillColor="#151515"
15 android:pathData="m25.79,5.657l0,0a2.09,2.09 45,0 0,0.23 3.262c3.522,2.402 4.762,5.927 4.741,10.52A13.279,13.279 135,0 1,4.206 19.365c0,-4.516 0.931,-7.71 4.374,-10.107a2.098,2.098 0,0 0,0.233 -3.265l0,0a2.101,2.101 135,0 0,-2.646 -0.169C1.433,9.133 -0.266,13.941 0.033,20.233a17.468,17.468 0,0 0,34.925 -0.868c0,-6.006 -1.971,-10.771 -6.585,-13.917a2.088,2.088 45,0 0,-2.582 0.209z"
16 android:strokeAlpha="0.6" />
17 <path
18 android:fillAlpha="0.6"
19 android:pathData="M19.451,19.024A3.498,3.498 0,0 0,21.165 19.508c1.336,0 1.749,-0.852 1.738,-1.49 0,-1.077 -0.982,-1.537 -1.987,-1.537L20.327,16.481L20.327,15.7L20.901,15.7c0.757,0 1.714,-0.392 1.714,-1.302C22.621,13.785 22.224,13.229 21.271,13.229a2.834,2.834 0,0 0,-1.537 0.529l-0.265,-0.757a3.662,3.662 0,0 1,2.008 -0.59c1.513,0 2.201,0.897 2.201,1.834 0,0.794 -0.474,1.466 -1.421,1.807l0,0.024c0.947,0.19 1.714,0.9 1.714,1.976C23.967,19.27 23.017,20.346 21.165,20.346a3.929,3.929 135,0 1,-1.998 -0.529z"
20 android:strokeAlpha="0.6">
21 <aapt:attr name="android:fillColor">
22 <gradient
23 android:endX="21.568"
24 android:endY="33.938"
25 android:startX="21.568"
26 android:startY="16.14"
27 android:type="linear">
28 <item
29 android:color="#FFC3C4C5"
30 android:offset="0" />
31 <item
32 android:color="#FFC5C6C6"
33 android:offset="0.03" />
34 <item
35 android:color="#FFC7C7C7"
36 android:offset="0.19" />
37 <item
38 android:color="#DBB5B5B5"
39 android:offset="0.44" />
40 <item
41 android:color="#7F878787"
42 android:offset="1" />
43 </gradient>
44 </aapt:attr>
45 </path>
46 <path
47 android:fillAlpha="0.6"
48 android:pathData="m12.516,12.729l2,0l0,13.822l6.615,0l0,1.68L12.516,28.231Z"
49 android:strokeAlpha="0.6">
50 <aapt:attr name="android:fillColor">
51 <gradient
52 android:endX="16.829"
53 android:endY="46.882"
54 android:startX="16.829"
55 android:startY="20.479"
56 android:type="linear">
57 <item
58 android:color="#FFC3C4C5"
59 android:offset="0" />
60 <item
61 android:color="#FFC5C6C6"
62 android:offset="0.03" />
63 <item
64 android:color="#FFC7C7C7"
65 android:offset="0.19" />
66 <item
67 android:color="#DBB5B5B5"
68 android:offset="0.44" />
69 <item
70 android:color="#7F878787"
71 android:offset="1" />
72 </gradient>
73 </aapt:attr>
74 </path>
75</vector>
diff --git a/src/android/app/src/main/res/drawable/button_r3.xml b/src/android/app/src/main/res/drawable/button_r3.xml
new file mode 100644
index 000000000..5c6864e26
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_r3.xml
@@ -0,0 +1,128 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:aapt="http://schemas.android.com/aapt"
3 android:width="34.963dp"
4 android:height="37.265dp"
5 android:viewportWidth="34.963"
6 android:viewportHeight="37.265">
7 <path
8 android:fillAlpha="0.5"
9 android:pathData="m10.781,12.65a19.579,19.579 0,0 1,3.596 -0.302c2.003,0 3.294,0.368 4.199,1.185a3.622,3.622 0,0 1,1.14 2.757c0,1.916 -1.206,3.175 -2.733,3.704l0,0.063c1.119,0.386 1.786,1.421 2.117,2.929 0.474,2.024 0.818,3.424 1.119,3.982l-1.924,0c-0.238,-0.407 -0.561,-1.656 -0.968,-3.466 -0.431,-2.003 -1.206,-2.757 -2.91,-2.82l-1.762,0l0,6.286l-1.873,0zM12.654,19.264l1.916,0c2.003,0 3.273,-1.098 3.273,-2.757 0,-1.873 -1.357,-2.691 -3.336,-2.712a7.649,7.649 0,0 0,-1.852 0.172z"
10 android:strokeAlpha="0.6">
11 <aapt:attr name="android:fillColor">
12 <gradient
13 android:endX="15.506"
14 android:endY="48.977"
15 android:startX="15.506"
16 android:startY="19.659"
17 android:type="linear">
18 <item
19 android:color="#FFC3C4C5"
20 android:offset="0" />
21 <item
22 android:color="#FFC5C6C6"
23 android:offset="0.03" />
24 <item
25 android:color="#FFC7C7C7"
26 android:offset="0.19" />
27 <item
28 android:color="#DBB5B5B5"
29 android:offset="0.44" />
30 <item
31 android:color="#7F878787"
32 android:offset="1" />
33 </gradient>
34 </aapt:attr>
35 </path>
36 <path
37 android:fillAlpha="0.5"
38 android:pathData="M16.062,9.353 L9.624,3.405A1.963,1.963 0,0 1,10.955 0l12.88,0a1.963,1.963 135,0 1,1.323 3.405L18.726,9.353a1.961,1.961 135,0 1,-2.664 0z"
39 android:strokeAlpha="0.6">
40 <aapt:attr name="android:fillColor">
41 <gradient
42 android:endX="17.395"
43 android:endY="18.74"
44 android:startX="17.395"
45 android:startY="-1.296"
46 android:type="linear">
47 <item
48 android:color="#FFC3C4C5"
49 android:offset="0" />
50 <item
51 android:color="#FFC5C6C6"
52 android:offset="0.03" />
53 <item
54 android:color="#FFC7C7C7"
55 android:offset="0.19" />
56 <item
57 android:color="#DBB5B5B5"
58 android:offset="0.44" />
59 <item
60 android:color="#7F878787"
61 android:offset="1" />
62 </gradient>
63 </aapt:attr>
64 </path>
65 <path
66 android:fillAlpha="0.5"
67 android:pathData="m25.79,5.657l0,0a2.09,2.09 45,0 0,0.23 3.262c3.522,2.402 4.762,5.927 4.741,10.52A13.279,13.279 135,0 1,4.206 19.365c0,-4.516 0.931,-7.71 4.374,-10.107a2.098,2.098 0,0 0,0.233 -3.265l0,0a2.101,2.101 135,0 0,-2.646 -0.169C1.433,9.133 -0.266,13.941 0.033,20.233a17.468,17.468 0,0 0,34.925 -0.868c0,-6.006 -1.971,-10.771 -6.585,-13.917a2.088,2.088 45,0 0,-2.582 0.209z"
68 android:strokeAlpha="0.6">
69 <aapt:attr name="android:fillColor">
70 <gradient
71 android:centerX="17.477"
72 android:centerY="19.92"
73 android:gradientRadius="17.201"
74 android:type="radial">
75 <item
76 android:color="#FFC3C4C5"
77 android:offset="0.58" />
78 <item
79 android:color="#FFC6C6C6"
80 android:offset="0.84" />
81 <item
82 android:color="#FFC7C7C7"
83 android:offset="0.88" />
84 <item
85 android:color="#FFC2C2C2"
86 android:offset="0.91" />
87 <item
88 android:color="#FFB5B5B5"
89 android:offset="0.94" />
90 <item
91 android:color="#FF9E9E9E"
92 android:offset="0.98" />
93 <item
94 android:color="#FF8F8F8F"
95 android:offset="1" />
96 </gradient>
97 </aapt:attr>
98 </path>
99 <path
100 android:fillAlpha="0.5"
101 android:pathData="M21.832,19.024A3.498,3.498 0,0 0,23.547 19.508c1.336,0 1.749,-0.852 1.738,-1.49 0,-1.077 -0.982,-1.537 -1.987,-1.537L22.708,16.481L22.708,15.7L23.282,15.7c0.757,0 1.714,-0.392 1.714,-1.302C25.002,13.785 24.605,13.229 23.652,13.229a2.834,2.834 0,0 0,-1.537 0.529l-0.265,-0.757a3.662,3.662 0,0 1,2.008 -0.59c1.513,0 2.201,0.897 2.201,1.834 0,0.794 -0.474,1.466 -1.421,1.807l0,0.024c0.947,0.19 1.714,0.9 1.714,1.976C26.349,19.27 25.399,20.346 23.547,20.346a3.929,3.929 135,0 1,-1.998 -0.529z"
102 android:strokeAlpha="0.6">
103 <aapt:attr name="android:fillColor">
104 <gradient
105 android:endX="23.949"
106 android:endY="33.938"
107 android:startX="23.949"
108 android:startY="16.14"
109 android:type="linear">
110 <item
111 android:color="#FFC3C4C5"
112 android:offset="0" />
113 <item
114 android:color="#FFC5C6C6"
115 android:offset="0.03" />
116 <item
117 android:color="#FFC7C7C7"
118 android:offset="0.19" />
119 <item
120 android:color="#DBB5B5B5"
121 android:offset="0.44" />
122 <item
123 android:color="#7F878787"
124 android:offset="1" />
125 </gradient>
126 </aapt:attr>
127 </path>
128</vector>
diff --git a/src/android/app/src/main/res/drawable/button_r3_depressed.xml b/src/android/app/src/main/res/drawable/button_r3_depressed.xml
new file mode 100644
index 000000000..20f480179
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_r3_depressed.xml
@@ -0,0 +1,75 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:aapt="http://schemas.android.com/aapt"
3 android:width="34.963dp"
4 android:height="37.265dp"
5 android:viewportWidth="34.963"
6 android:viewportHeight="37.265">
7 <path
8 android:fillAlpha="0.3"
9 android:fillColor="#151515"
10 android:pathData="M16.062,9.353 L9.624,3.405A1.963,1.963 0,0 1,10.955 0l12.88,0a1.963,1.963 135,0 1,1.323 3.405L18.726,9.353a1.961,1.961 135,0 1,-2.664 0z"
11 android:strokeAlpha="0.3" />
12 <path
13 android:fillAlpha="0.6"
14 android:fillColor="#151515"
15 android:pathData="m25.79,5.657l0,0a2.09,2.09 45,0 0,0.23 3.262c3.522,2.402 4.762,5.927 4.741,10.52A13.279,13.279 135,0 1,4.206 19.365c0,-4.516 0.931,-7.71 4.374,-10.107a2.098,2.098 0,0 0,0.233 -3.265l0,0a2.101,2.101 135,0 0,-2.646 -0.169C1.433,9.133 -0.266,13.941 0.033,20.233a17.468,17.468 0,0 0,34.925 -0.868c0,-6.006 -1.971,-10.771 -6.585,-13.917a2.088,2.088 45,0 0,-2.582 0.209z"
16 android:strokeAlpha="0.6" />
17 <path
18 android:fillAlpha="0.6"
19 android:pathData="m10.781,12.65a19.579,19.579 0,0 1,3.596 -0.302c2.003,0 3.294,0.368 4.199,1.185a3.622,3.622 0,0 1,1.14 2.757c0,1.916 -1.206,3.175 -2.733,3.704l0,0.063c1.119,0.386 1.786,1.421 2.117,2.929 0.474,2.024 0.818,3.424 1.119,3.982l-1.924,0c-0.238,-0.407 -0.561,-1.656 -0.968,-3.466 -0.431,-2.003 -1.206,-2.757 -2.91,-2.82l-1.762,0l0,6.286l-1.873,0zM12.654,19.264l1.916,0c2.003,0 3.273,-1.098 3.273,-2.757 0,-1.873 -1.357,-2.691 -3.336,-2.712a7.649,7.649 0,0 0,-1.852 0.172z"
20 android:strokeAlpha="0.6">
21 <aapt:attr name="android:fillColor">
22 <gradient
23 android:endX="15.506"
24 android:endY="48.977"
25 android:startX="15.506"
26 android:startY="19.659"
27 android:type="linear">
28 <item
29 android:color="#FFC3C4C5"
30 android:offset="0" />
31 <item
32 android:color="#FFC5C6C6"
33 android:offset="0.03" />
34 <item
35 android:color="#FFC7C7C7"
36 android:offset="0.19" />
37 <item
38 android:color="#DBB5B5B5"
39 android:offset="0.44" />
40 <item
41 android:color="#7F878787"
42 android:offset="1" />
43 </gradient>
44 </aapt:attr>
45 </path>
46 <path
47 android:fillAlpha="0.6"
48 android:pathData="M21.832,19.024A3.498,3.498 0,0 0,23.547 19.508c1.336,0 1.749,-0.852 1.738,-1.49 0,-1.077 -0.982,-1.537 -1.987,-1.537L22.708,16.481L22.708,15.7L23.282,15.7c0.757,0 1.714,-0.392 1.714,-1.302C25.002,13.785 24.605,13.229 23.652,13.229a2.834,2.834 0,0 0,-1.537 0.529l-0.265,-0.757a3.662,3.662 0,0 1,2.008 -0.59c1.513,0 2.201,0.897 2.201,1.834 0,0.794 -0.474,1.466 -1.421,1.807l0,0.024c0.947,0.19 1.714,0.9 1.714,1.976C26.349,19.27 25.399,20.346 23.547,20.346a3.929,3.929 135,0 1,-1.998 -0.529z"
49 android:strokeAlpha="0.6">
50 <aapt:attr name="android:fillColor">
51 <gradient
52 android:endX="23.949"
53 android:endY="33.938"
54 android:startX="23.949"
55 android:startY="16.14"
56 android:type="linear">
57 <item
58 android:color="#FFC3C4C5"
59 android:offset="0" />
60 <item
61 android:color="#FFC5C6C6"
62 android:offset="0.03" />
63 <item
64 android:color="#FFC7C7C7"
65 android:offset="0.19" />
66 <item
67 android:color="#DBB5B5B5"
68 android:offset="0.44" />
69 <item
70 android:color="#7F878787"
71 android:offset="1" />
72 </gradient>
73 </aapt:attr>
74 </path>
75</vector>
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index 6d092f7a9..200b99185 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -205,6 +205,8 @@
205 <item>@string/gamepad_d_pad</item> 205 <item>@string/gamepad_d_pad</item>
206 <item>@string/gamepad_left_stick</item> 206 <item>@string/gamepad_left_stick</item>
207 <item>@string/gamepad_right_stick</item> 207 <item>@string/gamepad_right_stick</item>
208 <item>L3</item>
209 <item>R3</item>
208 <item>@string/gamepad_home</item> 210 <item>@string/gamepad_home</item>
209 <item>@string/gamepad_screenshot</item> 211 <item>@string/gamepad_screenshot</item>
210 </string-array> 212 </string-array>
diff --git a/src/android/app/src/main/res/values/integers.xml b/src/android/app/src/main/res/values/integers.xml
index 2e93b408c..5e39bc7d9 100644
--- a/src/android/app/src/main/res/values/integers.xml
+++ b/src/android/app/src/main/res/values/integers.xml
@@ -33,6 +33,10 @@
33 <integer name="SWITCH_BUTTON_CAPTURE_Y">950</integer> 33 <integer name="SWITCH_BUTTON_CAPTURE_Y">950</integer>
34 <integer name="SWITCH_BUTTON_DPAD_X">260</integer> 34 <integer name="SWITCH_BUTTON_DPAD_X">260</integer>
35 <integer name="SWITCH_BUTTON_DPAD_Y">790</integer> 35 <integer name="SWITCH_BUTTON_DPAD_Y">790</integer>
36 <integer name="SWITCH_BUTTON_STICK_L_X">870</integer>
37 <integer name="SWITCH_BUTTON_STICK_L_Y">400</integer>
38 <integer name="SWITCH_BUTTON_STICK_R_X">960</integer>
39 <integer name="SWITCH_BUTTON_STICK_R_Y">430</integer>
36 40
37 <!-- Default SWITCH portrait layout --> 41 <!-- Default SWITCH portrait layout -->
38 <integer name="SWITCH_BUTTON_A_X_PORTRAIT">840</integer> 42 <integer name="SWITCH_BUTTON_A_X_PORTRAIT">840</integer>
@@ -65,6 +69,10 @@
65 <integer name="SWITCH_BUTTON_CAPTURE_Y_PORTRAIT">950</integer> 69 <integer name="SWITCH_BUTTON_CAPTURE_Y_PORTRAIT">950</integer>
66 <integer name="SWITCH_BUTTON_DPAD_X_PORTRAIT">240</integer> 70 <integer name="SWITCH_BUTTON_DPAD_X_PORTRAIT">240</integer>
67 <integer name="SWITCH_BUTTON_DPAD_Y_PORTRAIT">840</integer> 71 <integer name="SWITCH_BUTTON_DPAD_Y_PORTRAIT">840</integer>
72 <integer name="SWITCH_BUTTON_STICK_L_X_PORTRAIT">730</integer>
73 <integer name="SWITCH_BUTTON_STICK_L_Y_PORTRAIT">510</integer>
74 <integer name="SWITCH_BUTTON_STICK_R_X_PORTRAIT">900</integer>
75 <integer name="SWITCH_BUTTON_STICK_R_Y_PORTRAIT">540</integer>
68 76
69 <!-- Default SWITCH foldable layout --> 77 <!-- Default SWITCH foldable layout -->
70 <integer name="SWITCH_BUTTON_A_X_FOLDABLE">840</integer> 78 <integer name="SWITCH_BUTTON_A_X_FOLDABLE">840</integer>
@@ -97,5 +105,9 @@
97 <integer name="SWITCH_BUTTON_CAPTURE_Y_FOLDABLE">470</integer> 105 <integer name="SWITCH_BUTTON_CAPTURE_Y_FOLDABLE">470</integer>
98 <integer name="SWITCH_BUTTON_DPAD_X_FOLDABLE">240</integer> 106 <integer name="SWITCH_BUTTON_DPAD_X_FOLDABLE">240</integer>
99 <integer name="SWITCH_BUTTON_DPAD_Y_FOLDABLE">390</integer> 107 <integer name="SWITCH_BUTTON_DPAD_Y_FOLDABLE">390</integer>
108 <integer name="SWITCH_BUTTON_STICK_L_X_FOLDABLE">550</integer>
109 <integer name="SWITCH_BUTTON_STICK_L_Y_FOLDABLE">210</integer>
110 <integer name="SWITCH_BUTTON_STICK_R_X_FOLDABLE">550</integer>
111 <integer name="SWITCH_BUTTON_STICK_R_Y_FOLDABLE">280</integer>
100 112
101</resources> 113</resources>
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 6cbbea1b2..5972480e5 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -27,8 +27,8 @@ std::string GetTimeZoneString() {
27 std::string location_name; 27 std::string location_name;
28 if (time_zone_index == 0) { // Auto 28 if (time_zone_index == 0) { // Auto
29#if __cpp_lib_chrono >= 201907L 29#if __cpp_lib_chrono >= 201907L
30 const struct std::chrono::tzdb& time_zone_data = std::chrono::get_tzdb();
31 try { 30 try {
31 const struct std::chrono::tzdb& time_zone_data = std::chrono::get_tzdb();
32 const std::chrono::time_zone* current_zone = time_zone_data.current_zone(); 32 const std::chrono::time_zone* current_zone = time_zone_data.current_zone();
33 std::string_view current_zone_name = current_zone->name(); 33 std::string_view current_zone_name = current_zone->name();
34 location_name = current_zone_name; 34 location_name = current_zone_name;
diff --git a/src/core/arm/arm_interface.cpp b/src/core/arm/arm_interface.cpp
index beaea64b3..aa0eb9791 100644
--- a/src/core/arm/arm_interface.cpp
+++ b/src/core/arm/arm_interface.cpp
@@ -185,7 +185,7 @@ void ARM_Interface::Run() {
185 // Notify the debugger and go to sleep if a breakpoint was hit, 185 // Notify the debugger and go to sleep if a breakpoint was hit,
186 // or if the thread is unable to continue for any reason. 186 // or if the thread is unable to continue for any reason.
187 if (True(hr & HaltReason::InstructionBreakpoint) || True(hr & HaltReason::PrefetchAbort)) { 187 if (True(hr & HaltReason::InstructionBreakpoint) || True(hr & HaltReason::PrefetchAbort)) {
188 if (!True(hr & HaltReason::InstructionBreakpoint)) { 188 if (!True(hr & HaltReason::PrefetchAbort)) {
189 RewindBreakpointInstruction(); 189 RewindBreakpointInstruction();
190 } 190 }
191 if (system.DebuggerEnabled()) { 191 if (system.DebuggerEnabled()) {
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp
index b0515ec05..1c706e4d8 100644
--- a/src/core/file_sys/vfs_real.cpp
+++ b/src/core/file_sys/vfs_real.cpp
@@ -283,7 +283,8 @@ std::size_t RealVfsFile::GetSize() const {
283 if (size) { 283 if (size) {
284 return *size; 284 return *size;
285 } 285 }
286 return FS::GetSize(path); 286 auto lk = base.RefreshReference(path, perms, *reference);
287 return reference->file ? reference->file->GetSize() : 0;
287} 288}
288 289
289bool RealVfsFile::Resize(std::size_t new_size) { 290bool RealVfsFile::Resize(std::size_t new_size) {
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 1ebc32c1e..94bd656fe 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -1243,10 +1243,12 @@ Common::Input::DriverResult EmulatedController::SetPollingMode(
1243 auto& nfc_output_device = output_devices[3]; 1243 auto& nfc_output_device = output_devices[3];
1244 1244
1245 if (device_index == EmulatedDeviceIndex::LeftIndex) { 1245 if (device_index == EmulatedDeviceIndex::LeftIndex) {
1246 controller.left_polling_mode = polling_mode;
1246 return left_output_device->SetPollingMode(polling_mode); 1247 return left_output_device->SetPollingMode(polling_mode);
1247 } 1248 }
1248 1249
1249 if (device_index == EmulatedDeviceIndex::RightIndex) { 1250 if (device_index == EmulatedDeviceIndex::RightIndex) {
1251 controller.right_polling_mode = polling_mode;
1250 const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode); 1252 const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
1251 const auto mapped_nfc_result = right_output_device->SetPollingMode(polling_mode); 1253 const auto mapped_nfc_result = right_output_device->SetPollingMode(polling_mode);
1252 1254
@@ -1261,12 +1263,22 @@ Common::Input::DriverResult EmulatedController::SetPollingMode(
1261 return mapped_nfc_result; 1263 return mapped_nfc_result;
1262 } 1264 }
1263 1265
1266 controller.left_polling_mode = polling_mode;
1267 controller.right_polling_mode = polling_mode;
1264 left_output_device->SetPollingMode(polling_mode); 1268 left_output_device->SetPollingMode(polling_mode);
1265 right_output_device->SetPollingMode(polling_mode); 1269 right_output_device->SetPollingMode(polling_mode);
1266 nfc_output_device->SetPollingMode(polling_mode); 1270 nfc_output_device->SetPollingMode(polling_mode);
1267 return Common::Input::DriverResult::Success; 1271 return Common::Input::DriverResult::Success;
1268} 1272}
1269 1273
1274Common::Input::PollingMode EmulatedController::GetPollingMode(
1275 EmulatedDeviceIndex device_index) const {
1276 if (device_index == EmulatedDeviceIndex::LeftIndex) {
1277 return controller.left_polling_mode;
1278 }
1279 return controller.right_polling_mode;
1280}
1281
1270bool EmulatedController::SetCameraFormat( 1282bool EmulatedController::SetCameraFormat(
1271 Core::IrSensor::ImageTransferProcessorFormat camera_format) { 1283 Core::IrSensor::ImageTransferProcessorFormat camera_format) {
1272 LOG_INFO(Service_HID, "Set camera format {}", camera_format); 1284 LOG_INFO(Service_HID, "Set camera format {}", camera_format);
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index d511e5fac..88d77db8d 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -143,6 +143,8 @@ struct ControllerStatus {
143 CameraState camera_state{}; 143 CameraState camera_state{};
144 RingSensorForce ring_analog_state{}; 144 RingSensorForce ring_analog_state{};
145 NfcState nfc_state{}; 145 NfcState nfc_state{};
146 Common::Input::PollingMode left_polling_mode{};
147 Common::Input::PollingMode right_polling_mode{};
146}; 148};
147 149
148enum class ControllerTriggerType { 150enum class ControllerTriggerType {
@@ -370,6 +372,12 @@ public:
370 */ 372 */
371 Common::Input::DriverResult SetPollingMode(EmulatedDeviceIndex device_index, 373 Common::Input::DriverResult SetPollingMode(EmulatedDeviceIndex device_index,
372 Common::Input::PollingMode polling_mode); 374 Common::Input::PollingMode polling_mode);
375 /**
376 * Get the current polling mode from a controller
377 * @param device_index index of the controller to set the polling mode
378 * @return current polling mode
379 */
380 Common::Input::PollingMode GetPollingMode(EmulatedDeviceIndex device_index) const;
373 381
374 /** 382 /**
375 * Sets the desired camera format to be polled from a controller 383 * Sets the desired camera format to be polled from a controller
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index 5bf289818..2d633b03f 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -66,10 +66,6 @@ NfcDevice::~NfcDevice() {
66}; 66};
67 67
68void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) { 68void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
69 if (!is_initalized) {
70 return;
71 }
72
73 if (type == Core::HID::ControllerTriggerType::Connected) { 69 if (type == Core::HID::ControllerTriggerType::Connected) {
74 Initialize(); 70 Initialize();
75 availability_change_event->Signal(); 71 availability_change_event->Signal();
@@ -77,12 +73,12 @@ void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
77 } 73 }
78 74
79 if (type == Core::HID::ControllerTriggerType::Disconnected) { 75 if (type == Core::HID::ControllerTriggerType::Disconnected) {
80 device_state = DeviceState::Unavailable; 76 Finalize();
81 availability_change_event->Signal(); 77 availability_change_event->Signal();
82 return; 78 return;
83 } 79 }
84 80
85 if (type != Core::HID::ControllerTriggerType::Nfc) { 81 if (!is_initalized) {
86 return; 82 return;
87 } 83 }
88 84
@@ -90,6 +86,17 @@ void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
90 return; 86 return;
91 } 87 }
92 88
89 // Ensure nfc mode is always active
90 if (npad_device->GetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex) ==
91 Common::Input::PollingMode::Active) {
92 npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
93 Common::Input::PollingMode::NFC);
94 }
95
96 if (type != Core::HID::ControllerTriggerType::Nfc) {
97 return;
98 }
99
93 const auto nfc_status = npad_device->GetNfc(); 100 const auto nfc_status = npad_device->GetNfc();
94 switch (nfc_status.state) { 101 switch (nfc_status.state) {
95 case Common::Input::NfcState::NewAmiibo: 102 case Common::Input::NfcState::NewAmiibo:
@@ -207,11 +214,14 @@ void NfcDevice::Initialize() {
207} 214}
208 215
209void NfcDevice::Finalize() { 216void NfcDevice::Finalize() {
210 if (device_state == DeviceState::TagMounted) { 217 if (npad_device->IsConnected()) {
211 Unmount(); 218 if (device_state == DeviceState::TagMounted) {
212 } 219 Unmount();
213 if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { 220 }
214 StopDetection(); 221 if (device_state == DeviceState::SearchingForTag ||
222 device_state == DeviceState::TagRemoved) {
223 StopDetection();
224 }
215 } 225 }
216 226
217 if (device_state != DeviceState::Unavailable) { 227 if (device_state != DeviceState::Unavailable) {
diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp
index dac29c78f..9fb824baf 100644
--- a/src/input_common/drivers/mouse.cpp
+++ b/src/input_common/drivers/mouse.cpp
@@ -160,8 +160,9 @@ void Mouse::Move(int x, int y, int center_x, int center_y) {
160 last_mouse_change.y += mouse_change.y * y_sensitivity; 160 last_mouse_change.y += mouse_change.y * y_sensitivity;
161 161
162 // Bind the mouse change to [0 <= deadzone_counterweight <= 1.0] 162 // Bind the mouse change to [0 <= deadzone_counterweight <= 1.0]
163 if (last_mouse_change.Length() < deadzone_counterweight) { 163 const float length = last_mouse_change.Length();
164 last_mouse_change /= last_mouse_change.Length(); 164 if (length < deadzone_counterweight && length != 0.0f) {
165 last_mouse_change /= length;
165 last_mouse_change *= deadzone_counterweight; 166 last_mouse_change *= deadzone_counterweight;
166 } 167 }
167 168
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index 9f26392b1..66e3ae9af 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -523,6 +523,8 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
523 } 523 }
524 524
525 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, "1"); 525 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, "1");
526 // Share the same button mapping with non-Nintendo controllers
527 SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0");
526 528
527 // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native 529 // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
528 // driver on Linux. 530 // driver on Linux.
@@ -800,16 +802,9 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p
800 802
801 // This list is missing ZL/ZR since those are not considered buttons in SDL GameController. 803 // This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
802 // We will add those afterwards 804 // We will add those afterwards
803 // This list also excludes Screenshot since there's not really a mapping for that
804 ButtonBindings switch_to_sdl_button; 805 ButtonBindings switch_to_sdl_button;
805 806
806 if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO || 807 switch_to_sdl_button = GetDefaultButtonBinding(joystick);
807 SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT ||
808 SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT) {
809 switch_to_sdl_button = GetNintendoButtonBinding(joystick);
810 } else {
811 switch_to_sdl_button = GetDefaultButtonBinding();
812 }
813 808
814 // Add the missing bindings for ZL/ZR 809 // Add the missing bindings for ZL/ZR
815 static constexpr ZButtonBindings switch_to_sdl_axis{{ 810 static constexpr ZButtonBindings switch_to_sdl_axis{{
@@ -830,32 +825,9 @@ ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& p
830 return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis); 825 return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis);
831} 826}
832 827
833ButtonBindings SDLDriver::GetDefaultButtonBinding() const { 828ButtonBindings SDLDriver::GetDefaultButtonBinding(
834 return {
835 std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
836 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
837 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
838 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
839 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
840 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
841 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
842 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
843 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
844 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
845 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
846 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
847 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
848 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
849 {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
850 {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
851 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
852 {Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1},
853 };
854}
855
856ButtonBindings SDLDriver::GetNintendoButtonBinding(
857 const std::shared_ptr<SDLJoystick>& joystick) const { 829 const std::shared_ptr<SDLJoystick>& joystick) const {
858 // Default SL/SR mapping for pro controllers 830 // Default SL/SR mapping for other controllers
859 auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; 831 auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
860 auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; 832 auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
861 833
@@ -869,10 +841,10 @@ ButtonBindings SDLDriver::GetNintendoButtonBinding(
869 } 841 }
870 842
871 return { 843 return {
872 std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A}, 844 std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
873 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B}, 845 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
874 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X}, 846 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
875 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y}, 847 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
876 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, 848 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
877 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, 849 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
878 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, 850 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h
index ffde169b3..fcba4e3c6 100644
--- a/src/input_common/drivers/sdl_driver.h
+++ b/src/input_common/drivers/sdl_driver.h
@@ -100,11 +100,8 @@ private:
100 int axis_y, float offset_x, 100 int axis_y, float offset_x,
101 float offset_y) const; 101 float offset_y) const;
102 102
103 /// Returns the default button bindings list for generic controllers 103 /// Returns the default button bindings list
104 ButtonBindings GetDefaultButtonBinding() const; 104 ButtonBindings GetDefaultButtonBinding(const std::shared_ptr<SDLJoystick>& joystick) const;
105
106 /// Returns the default button bindings list for nintendo controllers
107 ButtonBindings GetNintendoButtonBinding(const std::shared_ptr<SDLJoystick>& joystick) const;
108 105
109 /// Returns the button mappings from a single controller 106 /// Returns the button mappings from a single controller
110 ButtonMapping GetSingleControllerMapping(const std::shared_ptr<SDLJoystick>& joystick, 107 ButtonMapping GetSingleControllerMapping(const std::shared_ptr<SDLJoystick>& joystick,
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index a1457798a..4457b366f 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -599,6 +599,10 @@ void TextureCache<P>::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t siz
599 [&](ImageId id, Image&) { deleted_images.push_back(id); }); 599 [&](ImageId id, Image&) { deleted_images.push_back(id); });
600 for (const ImageId id : deleted_images) { 600 for (const ImageId id : deleted_images) {
601 Image& image = slot_images[id]; 601 Image& image = slot_images[id];
602 if (True(image.flags & ImageFlagBits::CpuModified)) {
603 continue;
604 }
605 image.flags |= ImageFlagBits::CpuModified;
602 if (True(image.flags & ImageFlagBits::Remapped)) { 606 if (True(image.flags & ImageFlagBits::Remapped)) {
603 continue; 607 continue;
604 } 608 }
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 421e71e5a..e04852e01 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -485,7 +485,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
485 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); 485 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
486 } 486 }
487 } 487 }
488 if (extensions.extended_dynamic_state2 && (is_radv || is_qualcomm)) { 488 if (extensions.extended_dynamic_state2 && is_radv) {
489 const u32 version = (properties.properties.driverVersion << 3) >> 3; 489 const u32 version = (properties.properties.driverVersion << 3) >> 3;
490 if (version < VK_MAKE_API_VERSION(0, 22, 3, 1)) { 490 if (version < VK_MAKE_API_VERSION(0, 22, 3, 1)) {
491 LOG_WARNING( 491 LOG_WARNING(
@@ -498,6 +498,20 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
498 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); 498 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
499 } 499 }
500 } 500 }
501 if (extensions.extended_dynamic_state2 && is_qualcomm) {
502 const u32 version = (properties.properties.driverVersion << 3) >> 3;
503 if (version >= VK_MAKE_API_VERSION(0, 0, 676, 0) &&
504 version < VK_MAKE_API_VERSION(0, 0, 680, 0)) {
505 // Qualcomm Adreno 7xx drivers do not properly support extended_dynamic_state2.
506 LOG_WARNING(Render_Vulkan,
507 "Qualcomm Adreno 7xx drivers have broken VK_EXT_extended_dynamic_state2");
508 features.extended_dynamic_state2.extendedDynamicState2 = false;
509 features.extended_dynamic_state2.extendedDynamicState2LogicOp = false;
510 features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false;
511 extensions.extended_dynamic_state2 = false;
512 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
513 }
514 }
501 if (extensions.extended_dynamic_state3 && is_radv) { 515 if (extensions.extended_dynamic_state3 && is_radv) {
502 LOG_WARNING(Render_Vulkan, "RADV has broken extendedDynamicState3ColorBlendEquation"); 516 LOG_WARNING(Render_Vulkan, "RADV has broken extendedDynamicState3ColorBlendEquation");
503 features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false; 517 features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false;
@@ -512,8 +526,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
512 dynamic_state3_enables = false; 526 dynamic_state3_enables = false;
513 } 527 }
514 } 528 }
515 if (extensions.vertex_input_dynamic_state && (is_radv || is_qualcomm)) { 529 if (extensions.vertex_input_dynamic_state && is_radv) {
516 // Qualcomm S8gen2 drivers do not properly support vertex_input_dynamic_state.
517 // TODO(ameerj): Blacklist only offending driver versions 530 // TODO(ameerj): Blacklist only offending driver versions
518 // TODO(ameerj): Confirm if RDNA1 is affected 531 // TODO(ameerj): Confirm if RDNA1 is affected
519 const bool is_rdna2 = 532 const bool is_rdna2 =
@@ -526,6 +539,19 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
526 loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); 539 loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
527 } 540 }
528 } 541 }
542 if (extensions.vertex_input_dynamic_state && is_qualcomm) {
543 const u32 version = (properties.properties.driverVersion << 3) >> 3;
544 if (version >= VK_MAKE_API_VERSION(0, 0, 676, 0) &&
545 version < VK_MAKE_API_VERSION(0, 0, 680, 0)) {
546 // Qualcomm Adreno 7xx drivers do not properly support vertex_input_dynamic_state.
547 LOG_WARNING(
548 Render_Vulkan,
549 "Qualcomm Adreno 7xx drivers have broken VK_EXT_vertex_input_dynamic_state");
550 features.vertex_input_dynamic_state.vertexInputDynamicState = false;
551 extensions.vertex_input_dynamic_state = false;
552 loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
553 }
554 }
529 555
530 sets_per_pool = 64; 556 sets_per_pool = 64;
531 if (extensions.extended_dynamic_state3 && is_amd_driver && 557 if (extensions.extended_dynamic_state3 && is_amd_driver &&
@@ -774,6 +800,17 @@ bool Device::ShouldBoostClocks() const {
774 return validated_driver && !is_steam_deck && !is_debugging; 800 return validated_driver && !is_steam_deck && !is_debugging;
775} 801}
776 802
803bool Device::HasTimelineSemaphore() const {
804 if (GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
805 GetDriverID() == VK_DRIVER_ID_MESA_TURNIP) {
806 // Timeline semaphores do not work properly on all Qualcomm drivers.
807 // They generally work properly with Turnip drivers, but are problematic on some devices
808 // (e.g. ZTE handsets with Snapdragon 870).
809 return false;
810 }
811 return features.timeline_semaphore.timelineSemaphore;
812}
813
777bool Device::GetSuitability(bool requires_swapchain) { 814bool Device::GetSuitability(bool requires_swapchain) {
778 // Assume we will be suitable. 815 // Assume we will be suitable.
779 bool suitable = true; 816 bool suitable = true;
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 3ace1fb03..be3ed45ff 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -528,13 +528,7 @@ public:
528 return extensions.shader_atomic_int64; 528 return extensions.shader_atomic_int64;
529 } 529 }
530 530
531 bool HasTimelineSemaphore() const { 531 bool HasTimelineSemaphore() const;
532 if (GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) {
533 // Timeline semaphores do not work properly on all Qualcomm drivers.
534 return false;
535 }
536 return features.timeline_semaphore.timelineSemaphore;
537 }
538 532
539 /// Returns the minimum supported version of SPIR-V. 533 /// Returns the minimum supported version of SPIR-V.
540 u32 SupportedSpirvVersion() const { 534 u32 SupportedSpirvVersion() const {
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index a2ef0efa4..42f3ee0b4 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -221,8 +221,8 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
221 const VmaAllocationCreateInfo alloc_ci = { 221 const VmaAllocationCreateInfo alloc_ci = {
222 .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT, 222 .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
223 .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 223 .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
224 .requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 224 .requiredFlags = 0,
225 .preferredFlags = 0, 225 .preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
226 .memoryTypeBits = 0, 226 .memoryTypeBits = 0,
227 .pool = VK_NULL_HANDLE, 227 .pool = VK_NULL_HANDLE,
228 .pUserData = nullptr, 228 .pUserData = nullptr,
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index fea5eb614..6cd557c29 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -178,6 +178,8 @@ constexpr int default_mouse_hide_timeout = 2500;
178constexpr int default_mouse_center_timeout = 10; 178constexpr int default_mouse_center_timeout = 10;
179constexpr int default_input_update_timeout = 1; 179constexpr int default_input_update_timeout = 1;
180 180
181constexpr size_t CopyBufferSize = 1_MiB;
182
181/** 183/**
182 * "Callouts" are one-time instructional messages shown to the user. In the config settings, there 184 * "Callouts" are one-time instructional messages shown to the user. In the config settings, there
183 * is a bitfield "callout_flags" options, used to track if a message has already been shown to the 185 * is a bitfield "callout_flags" options, used to track if a message has already been shown to the
@@ -454,7 +456,7 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
454 // the user through their desktop environment. 456 // the user through their desktop environment.
455 //: TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the 457 //: TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the
456 //: computer from sleeping 458 //: computer from sleeping
457 QByteArray wakelock_reason = tr("Running a game").toLatin1(); 459 QByteArray wakelock_reason = tr("Running a game").toUtf8();
458 SDL_SetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME, wakelock_reason.data()); 460 SDL_SetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME, wakelock_reason.data());
459 461
460 // SDL disables the screen saver by default, and setting the hint 462 // SDL disables the screen saver by default, and setting the hint
@@ -2929,10 +2931,10 @@ void GMainWindow::OnMenuInstallToNAND() {
2929 2931
2930 int remaining = filenames.size(); 2932 int remaining = filenames.size();
2931 2933
2932 // This would only overflow above 2^43 bytes (8.796 TB) 2934 // This would only overflow above 2^51 bytes (2.252 PB)
2933 int total_size = 0; 2935 int total_size = 0;
2934 for (const QString& file : files) { 2936 for (const QString& file : files) {
2935 total_size += static_cast<int>(QFile(file).size() / 0x1000); 2937 total_size += static_cast<int>(QFile(file).size() / CopyBufferSize);
2936 } 2938 }
2937 if (total_size < 0) { 2939 if (total_size < 0) {
2938 LOG_CRITICAL(Frontend, "Attempting to install too many files, aborting."); 2940 LOG_CRITICAL(Frontend, "Attempting to install too many files, aborting.");
@@ -3032,7 +3034,7 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename) {
3032 return false; 3034 return false;
3033 } 3035 }
3034 3036
3035 std::vector<u8> buffer(1_MiB); 3037 std::vector<u8> buffer(CopyBufferSize);
3036 3038
3037 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { 3039 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
3038 if (install_progress->wasCanceled()) { 3040 if (install_progress->wasCanceled()) {
@@ -3088,7 +3090,7 @@ InstallResult GMainWindow::InstallNCA(const QString& filename) {
3088 return false; 3090 return false;
3089 } 3091 }
3090 3092
3091 std::array<u8, 0x1000> buffer{}; 3093 std::vector<u8> buffer(CopyBufferSize);
3092 3094
3093 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { 3095 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
3094 if (install_progress->wasCanceled()) { 3096 if (install_progress->wasCanceled()) {