summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--externals/CMakeLists.txt3
-rw-r--r--externals/renderdoc/renderdoc_app.h744
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt15
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt8
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt23
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt10
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt11
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt4
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt53
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt53
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt45
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt113
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt5
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt32
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt46
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt16
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt44
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt44
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt28
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt57
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt27
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt64
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt23
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt11
-rw-r--r--src/android/app/src/main/jni/config.cpp2
-rw-r--r--src/android/app/src/main/jni/native.cpp20
-rw-r--r--src/android/app/src/main/res/drawable/shortcut.xml11
-rw-r--r--src/android/app/src/main/res/navigation/emulation_navigation.xml2
-rw-r--r--src/android/app/src/main/res/navigation/home_navigation.xml2
-rw-r--r--src/android/app/src/main/res/navigation/settings_navigation.xml2
-rw-r--r--src/android/app/src/main/res/values/dimens.xml1
-rw-r--r--src/common/fs/fs.cpp15
-rw-r--r--src/common/logging/filter.cpp2
-rw-r--r--src/common/logging/types.h2
-rw-r--r--src/common/polyfill_thread.h20
-rw-r--r--src/common/settings.h2
-rw-r--r--src/common/settings_common.h10
-rw-r--r--src/common/settings_setting.h33
-rw-r--r--src/core/CMakeLists.txt9
-rw-r--r--src/core/core.cpp11
-rw-r--r--src/core/core.h6
-rw-r--r--src/core/crypto/key_manager.cpp8
-rw-r--r--src/core/file_sys/ips_layer.cpp4
-rw-r--r--src/core/file_sys/patch_manager.cpp4
-rw-r--r--src/core/file_sys/patch_manager.h2
-rw-r--r--src/core/hle/kernel/k_process.cpp5
-rw-r--r--src/core/hle/kernel/k_process.h8
-rw-r--r--src/core/hle/kernel/svc/svc_debug_string.cpp2
-rw-r--r--src/core/hle/kernel/svc/svc_exception.cpp6
-rw-r--r--src/core/hle/service/am/am.cpp14
-rw-r--r--src/core/hle/service/am/am.h1
-rw-r--r--src/core/hle/service/nfc/common/device.cpp14
-rw-r--r--src/core/hle/service/ngc/ngc.cpp150
-rw-r--r--src/core/hle/service/ngc/ngc.h (renamed from src/core/hle/service/ngct/ngct.h)4
-rw-r--r--src/core/hle/service/ngct/ngct.cpp62
-rw-r--r--src/core/hle/service/service.cpp4
-rw-r--r--src/core/hle/service/sockets/bsd.cpp2
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp8
-rw-r--r--src/core/loader/deconstructed_rom_directory.h4
-rw-r--r--src/core/loader/kip.cpp3
-rw-r--r--src/core/loader/nro.cpp3
-rw-r--r--src/core/loader/nso.cpp5
-rw-r--r--src/core/loader/nsp.cpp3
-rw-r--r--src/core/memory/cheat_engine.cpp2
-rw-r--r--src/core/tools/renderdoc.cpp55
-rw-r--r--src/core/tools/renderdoc.h22
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_image.cpp4
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp3
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.h1
-rw-r--r--src/video_core/texture_cache/texture_cache.h15
-rw-r--r--src/yuzu/configuration/configure_debug.cpp3
-rw-r--r--src/yuzu/configuration/configure_debug.ui99
-rw-r--r--src/yuzu/configuration/configure_ui.cpp7
-rw-r--r--src/yuzu/configuration/shared_widget.cpp171
-rw-r--r--src/yuzu/configuration/shared_widget.h21
-rw-r--r--src/yuzu/hotkeys.h4
-rw-r--r--src/yuzu/main.cpp6
-rw-r--r--src/yuzu_cmd/config.cpp2
-rw-r--r--src/yuzu_cmd/yuzu.cpp5
82 files changed, 1825 insertions, 563 deletions
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index 82a6da9fd..a4c2ffc10 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -174,6 +174,9 @@ target_include_directories(stb PUBLIC ./stb)
174add_library(bc_decoder bc_decoder/bc_decoder.cpp) 174add_library(bc_decoder bc_decoder/bc_decoder.cpp)
175target_include_directories(bc_decoder PUBLIC ./bc_decoder) 175target_include_directories(bc_decoder PUBLIC ./bc_decoder)
176 176
177add_library(renderdoc INTERFACE)
178target_include_directories(renderdoc SYSTEM INTERFACE ./renderdoc)
179
177if (ANDROID) 180if (ANDROID)
178 if (ARCHITECTURE_arm64) 181 if (ARCHITECTURE_arm64)
179 add_subdirectory(libadrenotools) 182 add_subdirectory(libadrenotools)
diff --git a/externals/renderdoc/renderdoc_app.h b/externals/renderdoc/renderdoc_app.h
new file mode 100644
index 000000000..0f4a1f98b
--- /dev/null
+++ b/externals/renderdoc/renderdoc_app.h
@@ -0,0 +1,744 @@
1// SPDX-FileCopyrightText: Baldur Karlsson
2// SPDX-License-Identifier: MIT
3
4/******************************************************************************
5 * The MIT License (MIT)
6 *
7 * Copyright (c) 2019-2023 Baldur Karlsson
8 *
9 * Permission is hereby granted, free of charge, to any person obtaining a copy
10 * of this software and associated documentation files (the "Software"), to deal
11 * in the Software without restriction, including without limitation the rights
12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 * copies of the Software, and to permit persons to whom the Software is
14 * furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 * THE SOFTWARE.
26 ******************************************************************************/
27
28#pragma once
29
30//////////////////////////////////////////////////////////////////////////////////////////////////
31//
32// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html
33//
34
35#if !defined(RENDERDOC_NO_STDINT)
36#include <stdint.h>
37#endif
38
39#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
40#define RENDERDOC_CC __cdecl
41#elif defined(__linux__)
42#define RENDERDOC_CC
43#elif defined(__APPLE__)
44#define RENDERDOC_CC
45#else
46#error "Unknown platform"
47#endif
48
49#ifdef __cplusplus
50extern "C" {
51#endif
52
53//////////////////////////////////////////////////////////////////////////////////////////////////
54// Constants not used directly in below API
55
56// This is a GUID/magic value used for when applications pass a path where shader debug
57// information can be found to match up with a stripped shader.
58// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue =
59// RENDERDOC_ShaderDebugMagicValue_value
60#define RENDERDOC_ShaderDebugMagicValue_struct \
61 { \
62 0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
63 }
64
65// as an alternative when you want a byte array (assuming x86 endianness):
66#define RENDERDOC_ShaderDebugMagicValue_bytearray \
67 { \
68 0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
69 }
70
71// truncated version when only a uint64_t is available (e.g. Vulkan tags):
72#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL
73
74//////////////////////////////////////////////////////////////////////////////////////////////////
75// RenderDoc capture options
76//
77
78typedef enum RENDERDOC_CaptureOption
79{
80 // Allow the application to enable vsync
81 //
82 // Default - enabled
83 //
84 // 1 - The application can enable or disable vsync at will
85 // 0 - vsync is force disabled
86 eRENDERDOC_Option_AllowVSync = 0,
87
88 // Allow the application to enable fullscreen
89 //
90 // Default - enabled
91 //
92 // 1 - The application can enable or disable fullscreen at will
93 // 0 - fullscreen is force disabled
94 eRENDERDOC_Option_AllowFullscreen = 1,
95
96 // Record API debugging events and messages
97 //
98 // Default - disabled
99 //
100 // 1 - Enable built-in API debugging features and records the results into
101 // the capture, which is matched up with events on replay
102 // 0 - no API debugging is forcibly enabled
103 eRENDERDOC_Option_APIValidation = 2,
104 eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum
105
106 // Capture CPU callstacks for API events
107 //
108 // Default - disabled
109 //
110 // 1 - Enables capturing of callstacks
111 // 0 - no callstacks are captured
112 eRENDERDOC_Option_CaptureCallstacks = 3,
113
114 // When capturing CPU callstacks, only capture them from actions.
115 // This option does nothing without the above option being enabled
116 //
117 // Default - disabled
118 //
119 // 1 - Only captures callstacks for actions.
120 // Ignored if CaptureCallstacks is disabled
121 // 0 - Callstacks, if enabled, are captured for every event.
122 eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4,
123 eRENDERDOC_Option_CaptureCallstacksOnlyActions = 4,
124
125 // Specify a delay in seconds to wait for a debugger to attach, after
126 // creating or injecting into a process, before continuing to allow it to run.
127 //
128 // 0 indicates no delay, and the process will run immediately after injection
129 //
130 // Default - 0 seconds
131 //
132 eRENDERDOC_Option_DelayForDebugger = 5,
133
134 // Verify buffer access. This includes checking the memory returned by a Map() call to
135 // detect any out-of-bounds modification, as well as initialising buffers with undefined contents
136 // to a marker value to catch use of uninitialised memory.
137 //
138 // NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do
139 // not do the same kind of interception & checking and undefined contents are really undefined.
140 //
141 // Default - disabled
142 //
143 // 1 - Verify buffer access
144 // 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in
145 // RenderDoc.
146 eRENDERDOC_Option_VerifyBufferAccess = 6,
147
148 // The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites.
149 // This option now controls the filling of uninitialised buffers with 0xdddddddd which was
150 // previously always enabled
151 eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess,
152
153 // Hooks any system API calls that create child processes, and injects
154 // RenderDoc into them recursively with the same options.
155 //
156 // Default - disabled
157 //
158 // 1 - Hooks into spawned child processes
159 // 0 - Child processes are not hooked by RenderDoc
160 eRENDERDOC_Option_HookIntoChildren = 7,
161
162 // By default RenderDoc only includes resources in the final capture necessary
163 // for that frame, this allows you to override that behaviour.
164 //
165 // Default - disabled
166 //
167 // 1 - all live resources at the time of capture are included in the capture
168 // and available for inspection
169 // 0 - only the resources referenced by the captured frame are included
170 eRENDERDOC_Option_RefAllResources = 8,
171
172 // **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or
173 // getting it will be ignored, to allow compatibility with older versions.
174 // In v1.1 the option acts as if it's always enabled.
175 //
176 // By default RenderDoc skips saving initial states for resources where the
177 // previous contents don't appear to be used, assuming that writes before
178 // reads indicate previous contents aren't used.
179 //
180 // Default - disabled
181 //
182 // 1 - initial contents at the start of each captured frame are saved, even if
183 // they are later overwritten or cleared before being used.
184 // 0 - unless a read is detected, initial contents will not be saved and will
185 // appear as black or empty data.
186 eRENDERDOC_Option_SaveAllInitials = 9,
187
188 // In APIs that allow for the recording of command lists to be replayed later,
189 // RenderDoc may choose to not capture command lists before a frame capture is
190 // triggered, to reduce overheads. This means any command lists recorded once
191 // and replayed many times will not be available and may cause a failure to
192 // capture.
193 //
194 // NOTE: This is only true for APIs where multithreading is difficult or
195 // discouraged. Newer APIs like Vulkan and D3D12 will ignore this option
196 // and always capture all command lists since the API is heavily oriented
197 // around it and the overheads have been reduced by API design.
198 //
199 // 1 - All command lists are captured from the start of the application
200 // 0 - Command lists are only captured if their recording begins during
201 // the period when a frame capture is in progress.
202 eRENDERDOC_Option_CaptureAllCmdLists = 10,
203
204 // Mute API debugging output when the API validation mode option is enabled
205 //
206 // Default - enabled
207 //
208 // 1 - Mute any API debug messages from being displayed or passed through
209 // 0 - API debugging is displayed as normal
210 eRENDERDOC_Option_DebugOutputMute = 11,
211
212 // Option to allow vendor extensions to be used even when they may be
213 // incompatible with RenderDoc and cause corrupted replays or crashes.
214 //
215 // Default - inactive
216 //
217 // No values are documented, this option should only be used when absolutely
218 // necessary as directed by a RenderDoc developer.
219 eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12,
220
221 // Define a soft memory limit which some APIs may aim to keep overhead under where
222 // possible. Anything above this limit will where possible be saved directly to disk during
223 // capture.
224 // This will cause increased disk space use (which may cause a capture to fail if disk space is
225 // exhausted) as well as slower capture times.
226 //
227 // Not all memory allocations may be deferred like this so it is not a guarantee of a memory
228 // limit.
229 //
230 // Units are in MBs, suggested values would range from 200MB to 1000MB.
231 //
232 // Default - 0 Megabytes
233 eRENDERDOC_Option_SoftMemoryLimit = 13,
234} RENDERDOC_CaptureOption;
235
236// Sets an option that controls how RenderDoc behaves on capture.
237//
238// Returns 1 if the option and value are valid
239// Returns 0 if either is invalid and the option is unchanged
240typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val);
241typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val);
242
243// Gets the current value of an option as a uint32_t
244//
245// If the option is invalid, 0xffffffff is returned
246typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt);
247
248// Gets the current value of an option as a float
249//
250// If the option is invalid, -FLT_MAX is returned
251typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt);
252
253typedef enum RENDERDOC_InputButton
254{
255 // '0' - '9' matches ASCII values
256 eRENDERDOC_Key_0 = 0x30,
257 eRENDERDOC_Key_1 = 0x31,
258 eRENDERDOC_Key_2 = 0x32,
259 eRENDERDOC_Key_3 = 0x33,
260 eRENDERDOC_Key_4 = 0x34,
261 eRENDERDOC_Key_5 = 0x35,
262 eRENDERDOC_Key_6 = 0x36,
263 eRENDERDOC_Key_7 = 0x37,
264 eRENDERDOC_Key_8 = 0x38,
265 eRENDERDOC_Key_9 = 0x39,
266
267 // 'A' - 'Z' matches ASCII values
268 eRENDERDOC_Key_A = 0x41,
269 eRENDERDOC_Key_B = 0x42,
270 eRENDERDOC_Key_C = 0x43,
271 eRENDERDOC_Key_D = 0x44,
272 eRENDERDOC_Key_E = 0x45,
273 eRENDERDOC_Key_F = 0x46,
274 eRENDERDOC_Key_G = 0x47,
275 eRENDERDOC_Key_H = 0x48,
276 eRENDERDOC_Key_I = 0x49,
277 eRENDERDOC_Key_J = 0x4A,
278 eRENDERDOC_Key_K = 0x4B,
279 eRENDERDOC_Key_L = 0x4C,
280 eRENDERDOC_Key_M = 0x4D,
281 eRENDERDOC_Key_N = 0x4E,
282 eRENDERDOC_Key_O = 0x4F,
283 eRENDERDOC_Key_P = 0x50,
284 eRENDERDOC_Key_Q = 0x51,
285 eRENDERDOC_Key_R = 0x52,
286 eRENDERDOC_Key_S = 0x53,
287 eRENDERDOC_Key_T = 0x54,
288 eRENDERDOC_Key_U = 0x55,
289 eRENDERDOC_Key_V = 0x56,
290 eRENDERDOC_Key_W = 0x57,
291 eRENDERDOC_Key_X = 0x58,
292 eRENDERDOC_Key_Y = 0x59,
293 eRENDERDOC_Key_Z = 0x5A,
294
295 // leave the rest of the ASCII range free
296 // in case we want to use it later
297 eRENDERDOC_Key_NonPrintable = 0x100,
298
299 eRENDERDOC_Key_Divide,
300 eRENDERDOC_Key_Multiply,
301 eRENDERDOC_Key_Subtract,
302 eRENDERDOC_Key_Plus,
303
304 eRENDERDOC_Key_F1,
305 eRENDERDOC_Key_F2,
306 eRENDERDOC_Key_F3,
307 eRENDERDOC_Key_F4,
308 eRENDERDOC_Key_F5,
309 eRENDERDOC_Key_F6,
310 eRENDERDOC_Key_F7,
311 eRENDERDOC_Key_F8,
312 eRENDERDOC_Key_F9,
313 eRENDERDOC_Key_F10,
314 eRENDERDOC_Key_F11,
315 eRENDERDOC_Key_F12,
316
317 eRENDERDOC_Key_Home,
318 eRENDERDOC_Key_End,
319 eRENDERDOC_Key_Insert,
320 eRENDERDOC_Key_Delete,
321 eRENDERDOC_Key_PageUp,
322 eRENDERDOC_Key_PageDn,
323
324 eRENDERDOC_Key_Backspace,
325 eRENDERDOC_Key_Tab,
326 eRENDERDOC_Key_PrtScrn,
327 eRENDERDOC_Key_Pause,
328
329 eRENDERDOC_Key_Max,
330} RENDERDOC_InputButton;
331
332// Sets which key or keys can be used to toggle focus between multiple windows
333//
334// If keys is NULL or num is 0, toggle keys will be disabled
335typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num);
336
337// Sets which key or keys can be used to capture the next frame
338//
339// If keys is NULL or num is 0, captures keys will be disabled
340typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num);
341
342typedef enum RENDERDOC_OverlayBits
343{
344 // This single bit controls whether the overlay is enabled or disabled globally
345 eRENDERDOC_Overlay_Enabled = 0x1,
346
347 // Show the average framerate over several seconds as well as min/max
348 eRENDERDOC_Overlay_FrameRate = 0x2,
349
350 // Show the current frame number
351 eRENDERDOC_Overlay_FrameNumber = 0x4,
352
353 // Show a list of recent captures, and how many captures have been made
354 eRENDERDOC_Overlay_CaptureList = 0x8,
355
356 // Default values for the overlay mask
357 eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate |
358 eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList),
359
360 // Enable all bits
361 eRENDERDOC_Overlay_All = ~0U,
362
363 // Disable all bits
364 eRENDERDOC_Overlay_None = 0,
365} RENDERDOC_OverlayBits;
366
367// returns the overlay bits that have been set
368typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)();
369// sets the overlay bits with an and & or mask
370typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or);
371
372// this function will attempt to remove RenderDoc's hooks in the application.
373//
374// Note: that this can only work correctly if done immediately after
375// the module is loaded, before any API work happens. RenderDoc will remove its
376// injected hooks and shut down. Behaviour is undefined if this is called
377// after any API functions have been called, and there is still no guarantee of
378// success.
379typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)();
380
381// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers.
382typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown;
383
384// This function will unload RenderDoc's crash handler.
385//
386// If you use your own crash handler and don't want RenderDoc's handler to
387// intercede, you can call this function to unload it and any unhandled
388// exceptions will pass to the next handler.
389typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)();
390
391// Sets the capture file path template
392//
393// pathtemplate is a UTF-8 string that gives a template for how captures will be named
394// and where they will be saved.
395//
396// Any extension is stripped off the path, and captures are saved in the directory
397// specified, and named with the filename and the frame number appended. If the
398// directory does not exist it will be created, including any parent directories.
399//
400// If pathtemplate is NULL, the template will remain unchanged
401//
402// Example:
403//
404// SetCaptureFilePathTemplate("my_captures/example");
405//
406// Capture #1 -> my_captures/example_frame123.rdc
407// Capture #2 -> my_captures/example_frame456.rdc
408typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate);
409
410// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string
411typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)();
412
413// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers.
414typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate;
415typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate;
416
417// returns the number of captures that have been made
418typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)();
419
420// This function returns the details of a capture, by index. New captures are added
421// to the end of the list.
422//
423// filename will be filled with the absolute path to the capture file, as a UTF-8 string
424// pathlength will be written with the length in bytes of the filename string
425// timestamp will be written with the time of the capture, in seconds since the Unix epoch
426//
427// Any of the parameters can be NULL and they'll be skipped.
428//
429// The function will return 1 if the capture index is valid, or 0 if the index is invalid
430// If the index is invalid, the values will be unchanged
431//
432// Note: when captures are deleted in the UI they will remain in this list, so the
433// capture path may not exist anymore.
434typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename,
435 uint32_t *pathlength, uint64_t *timestamp);
436
437// Sets the comments associated with a capture file. These comments are displayed in the
438// UI program when opening.
439//
440// filePath should be a path to the capture file to add comments to. If set to NULL or ""
441// the most recent capture file created made will be used instead.
442// comments should be a NULL-terminated UTF-8 string to add as comments.
443//
444// Any existing comments will be overwritten.
445typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath,
446 const char *comments);
447
448// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise
449typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)();
450
451// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers.
452// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for
453// backwards compatibility with old code, it is castable either way since it's ABI compatible
454// as the same function pointer type.
455typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected;
456
457// This function will launch the Replay UI associated with the RenderDoc library injected
458// into the running application.
459//
460// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter
461// to connect to this application
462// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open
463// if cmdline is NULL, the command line will be empty.
464//
465// returns the PID of the replay UI if successful, 0 if not successful.
466typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl,
467 const char *cmdline);
468
469// RenderDoc can return a higher version than requested if it's backwards compatible,
470// this function returns the actual version returned. If a parameter is NULL, it will be
471// ignored and the others will be filled out.
472typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch);
473
474// Requests that the replay UI show itself (if hidden or not the current top window). This can be
475// used in conjunction with IsTargetControlConnected and LaunchReplayUI to intelligently handle
476// showing the UI after making a capture.
477//
478// This will return 1 if the request was successfully passed on, though it's not guaranteed that
479// the UI will be on top in all cases depending on OS rules. It will return 0 if there is no current
480// target control connection to make such a request, or if there was another error
481typedef uint32_t(RENDERDOC_CC *pRENDERDOC_ShowReplayUI)();
482
483//////////////////////////////////////////////////////////////////////////
484// Capturing functions
485//
486
487// A device pointer is a pointer to the API's root handle.
488//
489// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc
490typedef void *RENDERDOC_DevicePointer;
491
492// A window handle is the OS's native window handle
493//
494// This would be an HWND, GLXDrawable, etc
495typedef void *RENDERDOC_WindowHandle;
496
497// A helper macro for Vulkan, where the device handle cannot be used directly.
498//
499// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use.
500//
501// Specifically, the value needed is the dispatch table pointer, which sits as the first
502// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and
503// indirect once.
504#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst)))
505
506// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will
507// respond to keypresses. Neither parameter can be NULL
508typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device,
509 RENDERDOC_WindowHandle wndHandle);
510
511// capture the next frame on whichever window and API is currently considered active
512typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)();
513
514// capture the next N frames on whichever window and API is currently considered active
515typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames);
516
517// When choosing either a device pointer or a window handle to capture, you can pass NULL.
518// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify
519// any API rendering to a specific window, or a specific API instance rendering to any window,
520// or in the simplest case of one window and one API, you can just pass NULL for both.
521//
522// In either case, if there are two or more possible matching (device,window) pairs it
523// is undefined which one will be captured.
524//
525// Note: for headless rendering you can pass NULL for the window handle and either specify
526// a device pointer or leave it NULL as above.
527
528// Immediately starts capturing API calls on the specified device pointer and window handle.
529//
530// If there is no matching thing to capture (e.g. no supported API has been initialised),
531// this will do nothing.
532//
533// The results are undefined (including crashes) if two captures are started overlapping,
534// even on separate devices and/oror windows.
535typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device,
536 RENDERDOC_WindowHandle wndHandle);
537
538// Returns whether or not a frame capture is currently ongoing anywhere.
539//
540// This will return 1 if a capture is ongoing, and 0 if there is no capture running
541typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)();
542
543// Ends capturing immediately.
544//
545// This will return 1 if the capture succeeded, and 0 if there was an error capturing.
546typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device,
547 RENDERDOC_WindowHandle wndHandle);
548
549// Ends capturing immediately and discard any data stored without saving to disk.
550//
551// This will return 1 if the capture was discarded, and 0 if there was an error or no capture
552// was in progress
553typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device,
554 RENDERDOC_WindowHandle wndHandle);
555
556// Only valid to be called between a call to StartFrameCapture and EndFrameCapture. Gives a custom
557// title to the capture produced which will be displayed in the UI.
558//
559// If multiple captures are ongoing, this title will be applied to the first capture to end after
560// this call. The second capture to end will have no title, unless this function is called again.
561//
562// Calling this function has no effect if no capture is currently running, and if it is called
563// multiple times only the last title will be used.
564typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureTitle)(const char *title);
565
566//////////////////////////////////////////////////////////////////////////////////////////////////
567// RenderDoc API versions
568//
569
570// RenderDoc uses semantic versioning (http://semver.org/).
571//
572// MAJOR version is incremented when incompatible API changes happen.
573// MINOR version is incremented when functionality is added in a backwards-compatible manner.
574// PATCH version is incremented when backwards-compatible bug fixes happen.
575//
576// Note that this means the API returned can be higher than the one you might have requested.
577// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned
578// instead of 1.0.0. You can check this with the GetAPIVersion entry point
579typedef enum RENDERDOC_Version
580{
581 eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00
582 eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01
583 eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02
584 eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00
585 eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01
586 eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02
587 eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00
588 eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00
589 eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00
590 eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01
591 eRENDERDOC_API_Version_1_4_2 = 10402, // RENDERDOC_API_1_4_2 = 1 04 02
592 eRENDERDOC_API_Version_1_5_0 = 10500, // RENDERDOC_API_1_5_0 = 1 05 00
593 eRENDERDOC_API_Version_1_6_0 = 10600, // RENDERDOC_API_1_6_0 = 1 06 00
594} RENDERDOC_Version;
595
596// API version changelog:
597//
598// 1.0.0 - initial release
599// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered
600// by keypress or TriggerCapture, instead of Start/EndFrameCapture.
601// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation
602// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new
603// function pointer is added to the end of the struct, the original layout is identical
604// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote
605// replay/remote server concept in replay UI)
606// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these
607// are captures and not debug logging files. This is the first API version in the v1.0
608// branch.
609// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be
610// displayed in the UI program on load.
611// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions
612// which allows users to opt-in to allowing unsupported vendor extensions to function.
613// Should be used at the user's own risk.
614// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to
615// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to
616// 0xdddddddd of uninitialised buffer contents.
617// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop
618// capturing without saving anything to disk.
619// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening
620// 1.4.2 - Refactor: Renamed 'draws' to 'actions' in callstack capture option.
621// 1.5.0 - Added feature: ShowReplayUI() to request that the replay UI show itself if connected
622// 1.6.0 - Added feature: SetCaptureTitle() which can be used to set a title for a
623// capture made with StartFrameCapture() or EndFrameCapture()
624
625typedef struct RENDERDOC_API_1_6_0
626{
627 pRENDERDOC_GetAPIVersion GetAPIVersion;
628
629 pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32;
630 pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32;
631
632 pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32;
633 pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32;
634
635 pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys;
636 pRENDERDOC_SetCaptureKeys SetCaptureKeys;
637
638 pRENDERDOC_GetOverlayBits GetOverlayBits;
639 pRENDERDOC_MaskOverlayBits MaskOverlayBits;
640
641 // Shutdown was renamed to RemoveHooks in 1.4.1.
642 // These unions allow old code to continue compiling without changes
643 union
644 {
645 pRENDERDOC_Shutdown Shutdown;
646 pRENDERDOC_RemoveHooks RemoveHooks;
647 };
648 pRENDERDOC_UnloadCrashHandler UnloadCrashHandler;
649
650 // Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2.
651 // These unions allow old code to continue compiling without changes
652 union
653 {
654 // deprecated name
655 pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate;
656 // current name
657 pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate;
658 };
659 union
660 {
661 // deprecated name
662 pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate;
663 // current name
664 pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate;
665 };
666
667 pRENDERDOC_GetNumCaptures GetNumCaptures;
668 pRENDERDOC_GetCapture GetCapture;
669
670 pRENDERDOC_TriggerCapture TriggerCapture;
671
672 // IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1.
673 // This union allows old code to continue compiling without changes
674 union
675 {
676 // deprecated name
677 pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected;
678 // current name
679 pRENDERDOC_IsTargetControlConnected IsTargetControlConnected;
680 };
681 pRENDERDOC_LaunchReplayUI LaunchReplayUI;
682
683 pRENDERDOC_SetActiveWindow SetActiveWindow;
684
685 pRENDERDOC_StartFrameCapture StartFrameCapture;
686 pRENDERDOC_IsFrameCapturing IsFrameCapturing;
687 pRENDERDOC_EndFrameCapture EndFrameCapture;
688
689 // new function in 1.1.0
690 pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture;
691
692 // new function in 1.2.0
693 pRENDERDOC_SetCaptureFileComments SetCaptureFileComments;
694
695 // new function in 1.4.0
696 pRENDERDOC_DiscardFrameCapture DiscardFrameCapture;
697
698 // new function in 1.5.0
699 pRENDERDOC_ShowReplayUI ShowReplayUI;
700
701 // new function in 1.6.0
702 pRENDERDOC_SetCaptureTitle SetCaptureTitle;
703} RENDERDOC_API_1_6_0;
704
705typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_0;
706typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_1;
707typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_2;
708typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_0;
709typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_1;
710typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_2;
711typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_2_0;
712typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_3_0;
713typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_0;
714typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_1;
715typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_2;
716typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_5_0;
717
718//////////////////////////////////////////////////////////////////////////////////////////////////
719// RenderDoc API entry point
720//
721// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available.
722//
723// The name is the same as the typedef - "RENDERDOC_GetAPI"
724//
725// This function is not thread safe, and should not be called on multiple threads at once.
726// Ideally, call this once as early as possible in your application's startup, before doing
727// any API work, since some configuration functionality etc has to be done also before
728// initialising any APIs.
729//
730// Parameters:
731// version is a single value from the RENDERDOC_Version above.
732//
733// outAPIPointers will be filled out with a pointer to the corresponding struct of function
734// pointers.
735//
736// Returns:
737// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested
738// 0 - if the requested version is not supported or the arguments are invalid.
739//
740typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers);
741
742#ifdef __cplusplus
743} // extern "C"
744#endif
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
index c8706d7a6..21f67f32a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
@@ -308,21 +308,6 @@ object NativeLibrary {
308 external fun isPaused(): Boolean 308 external fun isPaused(): Boolean
309 309
310 /** 310 /**
311 * Mutes emulation sound
312 */
313 external fun muteAudio(): Boolean
314
315 /**
316 * Unmutes emulation sound
317 */
318 external fun unmuteAudio(): Boolean
319
320 /**
321 * Returns true if emulation audio is muted.
322 */
323 external fun isMuted(): Boolean
324
325 /**
326 * Returns the performance stats for the current game 311 * Returns the performance stats for the current game
327 */ 312 */
328 external fun getPerfStats(): DoubleArray 313 external fun getPerfStats(): DoubleArray
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index bbd328c71..d4ae39661 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -332,7 +332,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
332 pictureInPictureActions.add(pauseRemoteAction) 332 pictureInPictureActions.add(pauseRemoteAction)
333 } 333 }
334 334
335 if (NativeLibrary.isMuted()) { 335 if (BooleanSetting.AUDIO_MUTED.boolean) {
336 val unmuteIcon = Icon.createWithResource( 336 val unmuteIcon = Icon.createWithResource(
337 this@EmulationActivity, 337 this@EmulationActivity,
338 R.drawable.ic_pip_unmute 338 R.drawable.ic_pip_unmute
@@ -389,9 +389,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
389 if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation() 389 if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()
390 } 390 }
391 if (intent.action == actionUnmute) { 391 if (intent.action == actionUnmute) {
392 if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio() 392 if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
393 } else if (intent.action == actionMute) { 393 } else if (intent.action == actionMute) {
394 if (!NativeLibrary.isMuted()) NativeLibrary.muteAudio() 394 if (!BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(true)
395 } 395 }
396 buildPictureInPictureParams() 396 buildPictureInPictureParams()
397 } 397 }
@@ -417,7 +417,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
417 } catch (ignored: Exception) { 417 } catch (ignored: Exception) {
418 } 418 }
419 // Always resume audio, since there is no UI button 419 // Always resume audio, since there is no UI button
420 if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio() 420 if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
421 } 421 }
422 } 422 }
423 423
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
index 0013e8512..f9f88a1d2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
@@ -4,7 +4,8 @@
4package org.yuzu.yuzu_emu.adapters 4package org.yuzu.yuzu_emu.adapters
5 5
6import android.content.Intent 6import android.content.Intent
7import android.graphics.drawable.BitmapDrawable 7import android.graphics.Bitmap
8import android.graphics.drawable.LayerDrawable
8import android.net.Uri 9import android.net.Uri
9import android.text.TextUtils 10import android.text.TextUtils
10import android.view.LayoutInflater 11import android.view.LayoutInflater
@@ -15,7 +16,10 @@ import android.widget.Toast
15import androidx.appcompat.app.AppCompatActivity 16import androidx.appcompat.app.AppCompatActivity
16import androidx.core.content.pm.ShortcutInfoCompat 17import androidx.core.content.pm.ShortcutInfoCompat
17import androidx.core.content.pm.ShortcutManagerCompat 18import androidx.core.content.pm.ShortcutManagerCompat
19import androidx.core.content.res.ResourcesCompat
18import androidx.core.graphics.drawable.IconCompat 20import androidx.core.graphics.drawable.IconCompat
21import androidx.core.graphics.drawable.toBitmap
22import androidx.core.graphics.drawable.toDrawable
19import androidx.documentfile.provider.DocumentFile 23import androidx.documentfile.provider.DocumentFile
20import androidx.lifecycle.ViewModelProvider 24import androidx.lifecycle.ViewModelProvider
21import androidx.navigation.findNavController 25import androidx.navigation.findNavController
@@ -87,11 +91,24 @@ class GameAdapter(private val activity: AppCompatActivity) :
87 action = Intent.ACTION_VIEW 91 action = Intent.ACTION_VIEW
88 data = Uri.parse(holder.game.path) 92 data = Uri.parse(holder.game.path)
89 } 93 }
94
95 val layerDrawable = ResourcesCompat.getDrawable(
96 YuzuApplication.appContext.resources,
97 R.drawable.shortcut,
98 null
99 ) as LayerDrawable
100 layerDrawable.setDrawableByLayerId(
101 R.id.shortcut_foreground,
102 GameIconUtils.getGameIcon(holder.game).toDrawable(YuzuApplication.appContext.resources)
103 )
104 val inset = YuzuApplication.appContext.resources
105 .getDimensionPixelSize(R.dimen.icon_inset)
106 layerDrawable.setLayerInset(1, inset, inset, inset, inset)
90 val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path) 107 val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path)
91 .setShortLabel(holder.game.title) 108 .setShortLabel(holder.game.title)
92 .setIcon( 109 .setIcon(
93 IconCompat.createWithBitmap( 110 IconCompat.createWithAdaptiveBitmap(
94 (holder.binding.imageGameScreen.drawable as BitmapDrawable).bitmap 111 layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)
95 ) 112 )
96 ) 113 )
97 .setIntent(openIntent) 114 .setIntent(openIntent)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
index 8d87d3bd7..1675627a1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/HomeSettingAdapter.kt
@@ -10,8 +10,12 @@ import android.view.ViewGroup
10import androidx.appcompat.app.AppCompatActivity 10import androidx.appcompat.app.AppCompatActivity
11import androidx.core.content.ContextCompat 11import androidx.core.content.ContextCompat
12import androidx.core.content.res.ResourcesCompat 12import androidx.core.content.res.ResourcesCompat
13import androidx.lifecycle.Lifecycle
13import androidx.lifecycle.LifecycleOwner 14import androidx.lifecycle.LifecycleOwner
15import androidx.lifecycle.lifecycleScope
16import androidx.lifecycle.repeatOnLifecycle
14import androidx.recyclerview.widget.RecyclerView 17import androidx.recyclerview.widget.RecyclerView
18import kotlinx.coroutines.launch
15import org.yuzu.yuzu_emu.R 19import org.yuzu.yuzu_emu.R
16import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding 20import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
17import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 21import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
@@ -86,7 +90,11 @@ class HomeSettingAdapter(
86 binding.optionIcon.alpha = 0.5f 90 binding.optionIcon.alpha = 0.5f
87 } 91 }
88 92
89 option.details.observe(viewLifecycle) { updateOptionDetails(it) } 93 viewLifecycle.lifecycleScope.launch {
94 viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
95 option.details.collect { updateOptionDetails(it) }
96 }
97 }
90 binding.optionDetail.postDelayed( 98 binding.optionDetail.postDelayed(
91 { 99 {
92 binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE 100 binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
index e0c0538c7..8476ce867 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
@@ -10,6 +10,7 @@ enum class BooleanSetting(
10 override val category: Settings.Category, 10 override val category: Settings.Category,
11 override val androidDefault: Boolean? = null 11 override val androidDefault: Boolean? = null
12) : AbstractBooleanSetting { 12) : AbstractBooleanSetting {
13 AUDIO_MUTED("audio_muted", Settings.Category.Audio),
13 CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu), 14 CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
14 FASTMEM("cpuopt_fastmem", Settings.Category.Cpu), 15 FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
15 FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu), 16 FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
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 0702236e8..08e2a973d 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
@@ -80,6 +80,17 @@ object Settings {
80 const val SECTION_THEME = "Theme" 80 const val SECTION_THEME = "Theme"
81 const val SECTION_DEBUG = "Debug" 81 const val SECTION_DEBUG = "Debug"
82 82
83 enum class MenuTag(val titleId: Int) {
84 SECTION_ROOT(R.string.advanced_settings),
85 SECTION_GENERAL(R.string.preferences_general),
86 SECTION_SYSTEM(R.string.preferences_system),
87 SECTION_RENDERER(R.string.preferences_graphics),
88 SECTION_AUDIO(R.string.preferences_audio),
89 SECTION_CPU(R.string.cpu),
90 SECTION_THEME(R.string.preferences_theme),
91 SECTION_DEBUG(R.string.preferences_debug);
92 }
93
83 const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown" 94 const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
84 95
85 const val PREF_OVERLAY_VERSION = "OverlayVersion" 96 const val PREF_OVERLAY_VERSION = "OverlayVersion"
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
index 91c273964..b343e527e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SubmenuSetting.kt
@@ -3,10 +3,12 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.model.view 4package org.yuzu.yuzu_emu.features.settings.model.view
5 5
6import org.yuzu.yuzu_emu.features.settings.model.Settings
7
6class SubmenuSetting( 8class SubmenuSetting(
7 titleId: Int, 9 titleId: Int,
8 descriptionId: Int, 10 descriptionId: Int,
9 val menuKey: String 11 val menuKey: Settings.MenuTag
10) : SettingsItem(emptySetting, titleId, descriptionId) { 12) : SettingsItem(emptySetting, titleId, descriptionId) {
11 override val type = TYPE_SUBMENU 13 override val type = TYPE_SUBMENU
12} 14}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
index 908c01265..4d2f2f604 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
@@ -13,9 +13,14 @@ import androidx.appcompat.app.AppCompatActivity
13import androidx.core.view.ViewCompat 13import androidx.core.view.ViewCompat
14import androidx.core.view.WindowCompat 14import androidx.core.view.WindowCompat
15import androidx.core.view.WindowInsetsCompat 15import androidx.core.view.WindowInsetsCompat
16import androidx.lifecycle.Lifecycle
17import androidx.lifecycle.lifecycleScope
18import androidx.lifecycle.repeatOnLifecycle
16import androidx.navigation.fragment.NavHostFragment 19import androidx.navigation.fragment.NavHostFragment
17import androidx.navigation.navArgs 20import androidx.navigation.navArgs
18import com.google.android.material.color.MaterialColors 21import com.google.android.material.color.MaterialColors
22import kotlinx.coroutines.flow.collectLatest
23import kotlinx.coroutines.launch
19import java.io.IOException 24import java.io.IOException
20import org.yuzu.yuzu_emu.R 25import org.yuzu.yuzu_emu.R
21import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding 26import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
@@ -66,25 +71,39 @@ class SettingsActivity : AppCompatActivity() {
66 ) 71 )
67 } 72 }
68 73
69 settingsViewModel.shouldRecreate.observe(this) { 74 lifecycleScope.apply {
70 if (it) { 75 launch {
71 settingsViewModel.setShouldRecreate(false) 76 repeatOnLifecycle(Lifecycle.State.CREATED) {
72 recreate() 77 settingsViewModel.shouldRecreate.collectLatest {
78 if (it) {
79 settingsViewModel.setShouldRecreate(false)
80 recreate()
81 }
82 }
83 }
73 } 84 }
74 } 85 launch {
75 settingsViewModel.shouldNavigateBack.observe(this) { 86 repeatOnLifecycle(Lifecycle.State.CREATED) {
76 if (it) { 87 settingsViewModel.shouldNavigateBack.collectLatest {
77 settingsViewModel.setShouldNavigateBack(false) 88 if (it) {
78 navigateBack() 89 settingsViewModel.setShouldNavigateBack(false)
90 navigateBack()
91 }
92 }
93 }
79 } 94 }
80 } 95 launch {
81 settingsViewModel.shouldShowResetSettingsDialog.observe(this) { 96 repeatOnLifecycle(Lifecycle.State.CREATED) {
82 if (it) { 97 settingsViewModel.shouldShowResetSettingsDialog.collectLatest {
83 settingsViewModel.setShouldShowResetSettingsDialog(false) 98 if (it) {
84 ResetSettingsDialogFragment().show( 99 settingsViewModel.setShouldShowResetSettingsDialog(false)
85 supportFragmentManager, 100 ResetSettingsDialogFragment().show(
86 ResetSettingsDialogFragment.TAG 101 supportFragmentManager,
87 ) 102 ResetSettingsDialogFragment.TAG
103 )
104 }
105 }
106 }
88 } 107 }
89 } 108 }
90 109
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
index bc319714c..70d8ec14b 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragment.kt
@@ -3,6 +3,7 @@
3 3
4package org.yuzu.yuzu_emu.features.settings.ui 4package org.yuzu.yuzu_emu.features.settings.ui
5 5
6import android.annotation.SuppressLint
6import android.os.Bundle 7import android.os.Bundle
7import android.view.LayoutInflater 8import android.view.LayoutInflater
8import android.view.View 9import android.view.View
@@ -13,14 +14,19 @@ import androidx.core.view.WindowInsetsCompat
13import androidx.core.view.updatePadding 14import androidx.core.view.updatePadding
14import androidx.fragment.app.Fragment 15import androidx.fragment.app.Fragment
15import androidx.fragment.app.activityViewModels 16import androidx.fragment.app.activityViewModels
17import androidx.lifecycle.Lifecycle
18import androidx.lifecycle.lifecycleScope
19import androidx.lifecycle.repeatOnLifecycle
16import androidx.navigation.findNavController 20import androidx.navigation.findNavController
17import androidx.navigation.fragment.navArgs 21import androidx.navigation.fragment.navArgs
18import androidx.recyclerview.widget.LinearLayoutManager 22import androidx.recyclerview.widget.LinearLayoutManager
19import com.google.android.material.divider.MaterialDividerItemDecoration 23import com.google.android.material.divider.MaterialDividerItemDecoration
20import com.google.android.material.transition.MaterialSharedAxis 24import com.google.android.material.transition.MaterialSharedAxis
25import kotlinx.coroutines.flow.collectLatest
26import kotlinx.coroutines.launch
21import org.yuzu.yuzu_emu.R 27import org.yuzu.yuzu_emu.R
22import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding 28import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
23import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile 29import org.yuzu.yuzu_emu.features.settings.model.Settings
24import org.yuzu.yuzu_emu.model.SettingsViewModel 30import org.yuzu.yuzu_emu.model.SettingsViewModel
25 31
26class SettingsFragment : Fragment() { 32class SettingsFragment : Fragment() {
@@ -51,15 +57,17 @@ class SettingsFragment : Fragment() {
51 return binding.root 57 return binding.root
52 } 58 }
53 59
60 // This is using the correct scope, lint is just acting up
61 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
54 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 62 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
55 settingsAdapter = SettingsAdapter(this, requireContext()) 63 settingsAdapter = SettingsAdapter(this, requireContext())
56 presenter = SettingsFragmentPresenter( 64 presenter = SettingsFragmentPresenter(
57 settingsViewModel, 65 settingsViewModel,
58 settingsAdapter!!, 66 settingsAdapter!!,
59 args.menuTag, 67 args.menuTag
60 args.game?.gameId ?: ""
61 ) 68 )
62 69
70 binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
63 val dividerDecoration = MaterialDividerItemDecoration( 71 val dividerDecoration = MaterialDividerItemDecoration(
64 requireContext(), 72 requireContext(),
65 LinearLayoutManager.VERTICAL 73 LinearLayoutManager.VERTICAL
@@ -75,28 +83,31 @@ class SettingsFragment : Fragment() {
75 settingsViewModel.setShouldNavigateBack(true) 83 settingsViewModel.setShouldNavigateBack(true)
76 } 84 }
77 85
78 settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) { 86 viewLifecycleOwner.lifecycleScope.apply {
79 if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it 87 launch {
80 } 88 repeatOnLifecycle(Lifecycle.State.CREATED) {
81 89 settingsViewModel.shouldReloadSettingsList.collectLatest {
82 settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { 90 if (it) {
83 if (it) { 91 settingsViewModel.setShouldReloadSettingsList(false)
84 settingsViewModel.setShouldReloadSettingsList(false) 92 presenter.loadSettingsList()
85 presenter.loadSettingsList() 93 }
94 }
95 }
86 } 96 }
87 } 97 launch {
88 98 settingsViewModel.isUsingSearch.collectLatest {
89 settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) { 99 if (it) {
90 if (it) { 100 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
91 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) 101 exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
92 exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) 102 } else {
93 } else { 103 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
94 reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) 104 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
95 exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) 105 }
106 }
96 } 107 }
97 } 108 }
98 109
99 if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) { 110 if (args.menuTag == Settings.MenuTag.SECTION_ROOT) {
100 binding.toolbarSettings.inflateMenu(R.menu.menu_settings) 111 binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
101 binding.toolbarSettings.setOnMenuItemClickListener { 112 binding.toolbarSettings.setOnMenuItemClickListener {
102 when (it.itemId) { 113 when (it.itemId) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
index 22a529b1b..766414a6c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.features.settings.ui
6import android.content.Context 6import android.content.Context
7import android.content.SharedPreferences 7import android.content.SharedPreferences
8import android.os.Build 8import android.os.Build
9import android.text.TextUtils
10import android.widget.Toast 9import android.widget.Toast
11import androidx.preference.PreferenceManager 10import androidx.preference.PreferenceManager
12import org.yuzu.yuzu_emu.R 11import org.yuzu.yuzu_emu.R
@@ -20,15 +19,13 @@ import org.yuzu.yuzu_emu.features.settings.model.LongSetting
20import org.yuzu.yuzu_emu.features.settings.model.Settings 19import org.yuzu.yuzu_emu.features.settings.model.Settings
21import org.yuzu.yuzu_emu.features.settings.model.ShortSetting 20import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
22import org.yuzu.yuzu_emu.features.settings.model.view.* 21import org.yuzu.yuzu_emu.features.settings.model.view.*
23import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
24import org.yuzu.yuzu_emu.model.SettingsViewModel 22import org.yuzu.yuzu_emu.model.SettingsViewModel
25import org.yuzu.yuzu_emu.utils.NativeConfig 23import org.yuzu.yuzu_emu.utils.NativeConfig
26 24
27class SettingsFragmentPresenter( 25class SettingsFragmentPresenter(
28 private val settingsViewModel: SettingsViewModel, 26 private val settingsViewModel: SettingsViewModel,
29 private val adapter: SettingsAdapter, 27 private val adapter: SettingsAdapter,
30 private var menuTag: String, 28 private var menuTag: Settings.MenuTag
31 private var gameId: String
32) { 29) {
33 private var settingsList = ArrayList<SettingsItem>() 30 private var settingsList = ArrayList<SettingsItem>()
34 31
@@ -53,24 +50,15 @@ class SettingsFragmentPresenter(
53 } 50 }
54 51
55 fun loadSettingsList() { 52 fun loadSettingsList() {
56 if (!TextUtils.isEmpty(gameId)) {
57 settingsViewModel.setToolbarTitle(
58 context.getString(
59 R.string.advanced_settings_game,
60 gameId
61 )
62 )
63 }
64
65 val sl = ArrayList<SettingsItem>() 53 val sl = ArrayList<SettingsItem>()
66 when (menuTag) { 54 when (menuTag) {
67 SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl) 55 Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl)
68 Settings.SECTION_GENERAL -> addGeneralSettings(sl) 56 Settings.MenuTag.SECTION_GENERAL -> addGeneralSettings(sl)
69 Settings.SECTION_SYSTEM -> addSystemSettings(sl) 57 Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
70 Settings.SECTION_RENDERER -> addGraphicsSettings(sl) 58 Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
71 Settings.SECTION_AUDIO -> addAudioSettings(sl) 59 Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
72 Settings.SECTION_THEME -> addThemeSettings(sl) 60 Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl)
73 Settings.SECTION_DEBUG -> addDebugSettings(sl) 61 Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
74 else -> { 62 else -> {
75 val context = YuzuApplication.appContext 63 val context = YuzuApplication.appContext
76 Toast.makeText( 64 Toast.makeText(
@@ -86,13 +74,12 @@ class SettingsFragmentPresenter(
86 } 74 }
87 75
88 private fun addConfigSettings(sl: ArrayList<SettingsItem>) { 76 private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
89 settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings))
90 sl.apply { 77 sl.apply {
91 add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL)) 78 add(SubmenuSetting(R.string.preferences_general, 0, Settings.MenuTag.SECTION_GENERAL))
92 add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM)) 79 add(SubmenuSetting(R.string.preferences_system, 0, Settings.MenuTag.SECTION_SYSTEM))
93 add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER)) 80 add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.MenuTag.SECTION_RENDERER))
94 add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO)) 81 add(SubmenuSetting(R.string.preferences_audio, 0, Settings.MenuTag.SECTION_AUDIO))
95 add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG)) 82 add(SubmenuSetting(R.string.preferences_debug, 0, Settings.MenuTag.SECTION_DEBUG))
96 add( 83 add(
97 RunnableSetting(R.string.reset_to_default, 0, false) { 84 RunnableSetting(R.string.reset_to_default, 0, false) {
98 settingsViewModel.setShouldShowResetSettingsDialog(true) 85 settingsViewModel.setShouldShowResetSettingsDialog(true)
@@ -102,7 +89,6 @@ class SettingsFragmentPresenter(
102 } 89 }
103 90
104 private fun addGeneralSettings(sl: ArrayList<SettingsItem>) { 91 private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
105 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))
106 sl.apply { 92 sl.apply {
107 add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key) 93 add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
108 add(ShortSetting.RENDERER_SPEED_LIMIT.key) 94 add(ShortSetting.RENDERER_SPEED_LIMIT.key)
@@ -112,7 +98,6 @@ class SettingsFragmentPresenter(
112 } 98 }
113 99
114 private fun addSystemSettings(sl: ArrayList<SettingsItem>) { 100 private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
115 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))
116 sl.apply { 101 sl.apply {
117 add(BooleanSetting.USE_DOCKED_MODE.key) 102 add(BooleanSetting.USE_DOCKED_MODE.key)
118 add(IntSetting.REGION_INDEX.key) 103 add(IntSetting.REGION_INDEX.key)
@@ -123,7 +108,6 @@ class SettingsFragmentPresenter(
123 } 108 }
124 109
125 private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) { 110 private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
126 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))
127 sl.apply { 111 sl.apply {
128 add(IntSetting.RENDERER_ACCURACY.key) 112 add(IntSetting.RENDERER_ACCURACY.key)
129 add(IntSetting.RENDERER_RESOLUTION.key) 113 add(IntSetting.RENDERER_RESOLUTION.key)
@@ -140,7 +124,6 @@ class SettingsFragmentPresenter(
140 } 124 }
141 125
142 private fun addAudioSettings(sl: ArrayList<SettingsItem>) { 126 private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
143 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))
144 sl.apply { 127 sl.apply {
145 add(IntSetting.AUDIO_OUTPUT_ENGINE.key) 128 add(IntSetting.AUDIO_OUTPUT_ENGINE.key)
146 add(ByteSetting.AUDIO_VOLUME.key) 129 add(ByteSetting.AUDIO_VOLUME.key)
@@ -148,7 +131,6 @@ class SettingsFragmentPresenter(
148 } 131 }
149 132
150 private fun addThemeSettings(sl: ArrayList<SettingsItem>) { 133 private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
151 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme))
152 sl.apply { 134 sl.apply {
153 val theme: AbstractIntSetting = object : AbstractIntSetting { 135 val theme: AbstractIntSetting = object : AbstractIntSetting {
154 override val int: Int 136 override val int: Int
@@ -261,7 +243,6 @@ class SettingsFragmentPresenter(
261 } 243 }
262 244
263 private fun addDebugSettings(sl: ArrayList<SettingsItem>) { 245 private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
264 settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))
265 sl.apply { 246 sl.apply {
266 add(HeaderSetting(R.string.gpu)) 247 add(HeaderSetting(R.string.gpu))
267 add(IntSetting.RENDERER_BACKEND.key) 248 add(IntSetting.RENDERER_BACKEND.key)
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 944ae652e..3e6c157c7 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
@@ -39,6 +39,7 @@ import androidx.window.layout.WindowLayoutInfo
39import com.google.android.material.dialog.MaterialAlertDialogBuilder 39import com.google.android.material.dialog.MaterialAlertDialogBuilder
40import com.google.android.material.slider.Slider 40import com.google.android.material.slider.Slider
41import kotlinx.coroutines.Dispatchers 41import kotlinx.coroutines.Dispatchers
42import kotlinx.coroutines.flow.collectLatest
42import kotlinx.coroutines.launch 43import kotlinx.coroutines.launch
43import org.yuzu.yuzu_emu.HomeNavigationDirections 44import org.yuzu.yuzu_emu.HomeNavigationDirections
44import org.yuzu.yuzu_emu.NativeLibrary 45import org.yuzu.yuzu_emu.NativeLibrary
@@ -49,7 +50,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
49import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding 50import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
50import org.yuzu.yuzu_emu.features.settings.model.IntSetting 51import org.yuzu.yuzu_emu.features.settings.model.IntSetting
51import org.yuzu.yuzu_emu.features.settings.model.Settings 52import org.yuzu.yuzu_emu.features.settings.model.Settings
52import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
53import org.yuzu.yuzu_emu.model.Game 53import org.yuzu.yuzu_emu.model.Game
54import org.yuzu.yuzu_emu.model.EmulationViewModel 54import org.yuzu.yuzu_emu.model.EmulationViewModel
55import org.yuzu.yuzu_emu.overlay.InputOverlay 55import org.yuzu.yuzu_emu.overlay.InputOverlay
@@ -129,6 +129,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
129 return binding.root 129 return binding.root
130 } 130 }
131 131
132 // This is using the correct scope, lint is just acting up
133 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
132 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 134 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
133 binding.surfaceEmulation.holder.addCallback(this) 135 binding.surfaceEmulation.holder.addCallback(this)
134 binding.showFpsText.setTextColor(Color.YELLOW) 136 binding.showFpsText.setTextColor(Color.YELLOW)
@@ -163,7 +165,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
163 R.id.menu_settings -> { 165 R.id.menu_settings -> {
164 val action = HomeNavigationDirections.actionGlobalSettingsActivity( 166 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
165 null, 167 null,
166 SettingsFile.FILE_NAME_CONFIG 168 Settings.MenuTag.SECTION_ROOT
167 ) 169 )
168 binding.root.findNavController().navigate(action) 170 binding.root.findNavController().navigate(action)
169 true 171 true
@@ -205,59 +207,80 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
205 } 207 }
206 ) 208 )
207 209
208 viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
209 lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
210 WindowInfoTracker.getOrCreate(requireContext())
211 .windowLayoutInfo(requireActivity())
212 .collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
213 }
214 }
215
216 GameIconUtils.loadGameIcon(game, binding.loadingImage) 210 GameIconUtils.loadGameIcon(game, binding.loadingImage)
217 binding.loadingTitle.text = game.title 211 binding.loadingTitle.text = game.title
218 binding.loadingTitle.isSelected = true 212 binding.loadingTitle.isSelected = true
219 binding.loadingText.isSelected = true 213 binding.loadingText.isSelected = true
220 214
221 emulationViewModel.shaderProgress.observe(viewLifecycleOwner) { 215 viewLifecycleOwner.lifecycleScope.apply {
222 if (it > 0 && it != emulationViewModel.totalShaders.value!!) { 216 launch {
223 binding.loadingProgressIndicator.isIndeterminate = false 217 repeatOnLifecycle(Lifecycle.State.STARTED) {
224 218 WindowInfoTracker.getOrCreate(requireContext())
225 if (it < binding.loadingProgressIndicator.max) { 219 .windowLayoutInfo(requireActivity())
226 binding.loadingProgressIndicator.progress = it 220 .collect {
221 updateFoldableLayout(requireActivity() as EmulationActivity, it)
222 }
227 } 223 }
228 } 224 }
225 launch {
226 repeatOnLifecycle(Lifecycle.State.CREATED) {
227 emulationViewModel.shaderProgress.collectLatest {
228 if (it > 0 && it != emulationViewModel.totalShaders.value) {
229 binding.loadingProgressIndicator.isIndeterminate = false
230
231 if (it < binding.loadingProgressIndicator.max) {
232 binding.loadingProgressIndicator.progress = it
233 }
234 }
229 235
230 if (it == emulationViewModel.totalShaders.value!!) { 236 if (it == emulationViewModel.totalShaders.value) {
231 binding.loadingText.setText(R.string.loading) 237 binding.loadingText.setText(R.string.loading)
232 binding.loadingProgressIndicator.isIndeterminate = true 238 binding.loadingProgressIndicator.isIndeterminate = true
239 }
240 }
241 }
233 } 242 }
234 } 243 launch {
235 emulationViewModel.totalShaders.observe(viewLifecycleOwner) { 244 repeatOnLifecycle(Lifecycle.State.CREATED) {
236 binding.loadingProgressIndicator.max = it 245 emulationViewModel.totalShaders.collectLatest {
237 } 246 binding.loadingProgressIndicator.max = it
238 emulationViewModel.shaderMessage.observe(viewLifecycleOwner) { 247 }
239 if (it.isNotEmpty()) { 248 }
240 binding.loadingText.text = it
241 } 249 }
242 } 250 launch {
243 251 repeatOnLifecycle(Lifecycle.State.CREATED) {
244 emulationViewModel.emulationStarted.observe(viewLifecycleOwner) { started -> 252 emulationViewModel.shaderMessage.collectLatest {
245 if (started) { 253 if (it.isNotEmpty()) {
246 binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) 254 binding.loadingText.text = it
247 ViewUtils.showView(binding.surfaceInputOverlay) 255 }
248 ViewUtils.hideView(binding.loadingIndicator) 256 }
249 257 }
250 // Setup overlay
251 updateShowFpsOverlay()
252 } 258 }
253 } 259 launch {
260 repeatOnLifecycle(Lifecycle.State.CREATED) {
261 emulationViewModel.emulationStarted.collectLatest {
262 if (it) {
263 binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
264 ViewUtils.showView(binding.surfaceInputOverlay)
265 ViewUtils.hideView(binding.loadingIndicator)
254 266
255 emulationViewModel.isEmulationStopping.observe(viewLifecycleOwner) { 267 // Setup overlay
256 if (it) { 268 updateShowFpsOverlay()
257 binding.loadingText.setText(R.string.shutting_down) 269 }
258 ViewUtils.showView(binding.loadingIndicator) 270 }
259 ViewUtils.hideView(binding.inputContainer) 271 }
260 ViewUtils.hideView(binding.showFpsText) 272 }
273 launch {
274 repeatOnLifecycle(Lifecycle.State.CREATED) {
275 emulationViewModel.isEmulationStopping.collectLatest {
276 if (it) {
277 binding.loadingText.setText(R.string.shutting_down)
278 ViewUtils.showView(binding.loadingIndicator)
279 ViewUtils.hideView(binding.inputContainer)
280 ViewUtils.hideView(binding.showFpsText)
281 }
282 }
283 }
261 } 284 }
262 } 285 }
263 } 286 }
@@ -274,9 +297,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
274 } 297 }
275 } 298 }
276 } else { 299 } else {
277 if (EmulationMenuSettings.showOverlay && 300 if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
278 emulationViewModel.emulationStarted.value == true
279 ) {
280 binding.surfaceInputOverlay.post { 301 binding.surfaceInputOverlay.post {
281 binding.surfaceInputOverlay.visibility = View.VISIBLE 302 binding.surfaceInputOverlay.visibility = View.VISIBLE
282 } 303 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
index cbbe14d22..c119e69c9 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
@@ -37,7 +37,6 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
37import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding 37import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
38import org.yuzu.yuzu_emu.features.DocumentProvider 38import org.yuzu.yuzu_emu.features.DocumentProvider
39import org.yuzu.yuzu_emu.features.settings.model.Settings 39import org.yuzu.yuzu_emu.features.settings.model.Settings
40import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
41import org.yuzu.yuzu_emu.model.HomeSetting 40import org.yuzu.yuzu_emu.model.HomeSetting
42import org.yuzu.yuzu_emu.model.HomeViewModel 41import org.yuzu.yuzu_emu.model.HomeViewModel
43import org.yuzu.yuzu_emu.ui.main.MainActivity 42import org.yuzu.yuzu_emu.ui.main.MainActivity
@@ -78,7 +77,7 @@ class HomeSettingsFragment : Fragment() {
78 { 77 {
79 val action = HomeNavigationDirections.actionGlobalSettingsActivity( 78 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
80 null, 79 null,
81 SettingsFile.FILE_NAME_CONFIG 80 Settings.MenuTag.SECTION_ROOT
82 ) 81 )
83 binding.root.findNavController().navigate(action) 82 binding.root.findNavController().navigate(action)
84 } 83 }
@@ -100,7 +99,7 @@ class HomeSettingsFragment : Fragment() {
100 { 99 {
101 val action = HomeNavigationDirections.actionGlobalSettingsActivity( 100 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
102 null, 101 null,
103 Settings.SECTION_THEME 102 Settings.MenuTag.SECTION_THEME
104 ) 103 )
105 binding.root.findNavController().navigate(action) 104 binding.root.findNavController().navigate(action)
106 } 105 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
index 181bd983a..ea8eb073a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
@@ -9,8 +9,12 @@ import android.widget.Toast
9import androidx.appcompat.app.AppCompatActivity 9import androidx.appcompat.app.AppCompatActivity
10import androidx.fragment.app.DialogFragment 10import androidx.fragment.app.DialogFragment
11import androidx.fragment.app.activityViewModels 11import androidx.fragment.app.activityViewModels
12import androidx.lifecycle.Lifecycle
12import androidx.lifecycle.ViewModelProvider 13import androidx.lifecycle.ViewModelProvider
14import androidx.lifecycle.lifecycleScope
15import androidx.lifecycle.repeatOnLifecycle
13import com.google.android.material.dialog.MaterialAlertDialogBuilder 16import com.google.android.material.dialog.MaterialAlertDialogBuilder
17import kotlinx.coroutines.launch
14import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 18import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
15import org.yuzu.yuzu_emu.model.TaskViewModel 19import org.yuzu.yuzu_emu.model.TaskViewModel
16 20
@@ -28,21 +32,27 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
28 .create() 32 .create()
29 dialog.setCanceledOnTouchOutside(false) 33 dialog.setCanceledOnTouchOutside(false)
30 34
31 taskViewModel.isComplete.observe(this) { complete -> 35 viewLifecycleOwner.lifecycleScope.launch {
32 if (complete) { 36 repeatOnLifecycle(Lifecycle.State.CREATED) {
33 dialog.dismiss() 37 taskViewModel.isComplete.collect {
34 when (val result = taskViewModel.result.value) { 38 if (it) {
35 is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show() 39 dialog.dismiss()
36 is MessageDialogFragment -> result.show( 40 when (val result = taskViewModel.result.value) {
37 requireActivity().supportFragmentManager, 41 is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG)
38 MessageDialogFragment.TAG 42 .show()
39 ) 43
44 is MessageDialogFragment -> result.show(
45 requireActivity().supportFragmentManager,
46 MessageDialogFragment.TAG
47 )
48 }
49 taskViewModel.clear()
50 }
40 } 51 }
41 taskViewModel.clear()
42 } 52 }
43 } 53 }
44 54
45 if (taskViewModel.isRunning.value == false) { 55 if (!taskViewModel.isRunning.value) {
46 taskViewModel.runTask() 56 taskViewModel.runTask()
47 } 57 }
48 return dialog 58 return dialog
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
index f54dccc69..2dbca76a5 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SearchFragment.kt
@@ -3,6 +3,7 @@
3 3
4package org.yuzu.yuzu_emu.fragments 4package org.yuzu.yuzu_emu.fragments
5 5
6import android.annotation.SuppressLint
6import android.content.Context 7import android.content.Context
7import android.content.SharedPreferences 8import android.content.SharedPreferences
8import android.os.Bundle 9import android.os.Bundle
@@ -17,9 +18,13 @@ import androidx.core.view.updatePadding
17import androidx.core.widget.doOnTextChanged 18import androidx.core.widget.doOnTextChanged
18import androidx.fragment.app.Fragment 19import androidx.fragment.app.Fragment
19import androidx.fragment.app.activityViewModels 20import androidx.fragment.app.activityViewModels
21import androidx.lifecycle.Lifecycle
22import androidx.lifecycle.lifecycleScope
23import androidx.lifecycle.repeatOnLifecycle
20import androidx.preference.PreferenceManager 24import androidx.preference.PreferenceManager
21import info.debatty.java.stringsimilarity.Jaccard 25import info.debatty.java.stringsimilarity.Jaccard
22import info.debatty.java.stringsimilarity.JaroWinkler 26import info.debatty.java.stringsimilarity.JaroWinkler
27import kotlinx.coroutines.launch
23import java.util.Locale 28import java.util.Locale
24import org.yuzu.yuzu_emu.R 29import org.yuzu.yuzu_emu.R
25import org.yuzu.yuzu_emu.YuzuApplication 30import org.yuzu.yuzu_emu.YuzuApplication
@@ -52,6 +57,8 @@ class SearchFragment : Fragment() {
52 return binding.root 57 return binding.root
53 } 58 }
54 59
60 // This is using the correct scope, lint is just acting up
61 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
55 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 62 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
56 homeViewModel.setNavigationVisibility(visible = true, animated = false) 63 homeViewModel.setNavigationVisibility(visible = true, animated = false)
57 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 64 preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
@@ -79,21 +86,32 @@ class SearchFragment : Fragment() {
79 filterAndSearch() 86 filterAndSearch()
80 } 87 }
81 88
82 gamesViewModel.apply { 89 viewLifecycleOwner.lifecycleScope.apply {
83 searchFocused.observe(viewLifecycleOwner) { searchFocused -> 90 launch {
84 if (searchFocused) { 91 repeatOnLifecycle(Lifecycle.State.CREATED) {
85 focusSearch() 92 gamesViewModel.searchFocused.collect {
86 gamesViewModel.setSearchFocused(false) 93 if (it) {
94 focusSearch()
95 gamesViewModel.setSearchFocused(false)
96 }
97 }
87 } 98 }
88 } 99 }
89 100 launch {
90 games.observe(viewLifecycleOwner) { filterAndSearch() } 101 repeatOnLifecycle(Lifecycle.State.CREATED) {
91 searchedGames.observe(viewLifecycleOwner) { 102 gamesViewModel.games.collect { filterAndSearch() }
92 (binding.gridGamesSearch.adapter as GameAdapter).submitList(it) 103 }
93 if (it.isEmpty()) { 104 }
94 binding.noResultsView.visibility = View.VISIBLE 105 launch {
95 } else { 106 repeatOnLifecycle(Lifecycle.State.CREATED) {
96 binding.noResultsView.visibility = View.GONE 107 gamesViewModel.searchedGames.collect {
108 (binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
109 if (it.isEmpty()) {
110 binding.noResultsView.visibility = View.VISIBLE
111 } else {
112 binding.noResultsView.visibility = View.GONE
113 }
114 }
97 } 115 }
98 } 116 }
99 } 117 }
@@ -109,7 +127,7 @@ class SearchFragment : Fragment() {
109 private inner class ScoredGame(val score: Double, val item: Game) 127 private inner class ScoredGame(val score: Double, val item: Game)
110 128
111 private fun filterAndSearch() { 129 private fun filterAndSearch() {
112 val baseList = gamesViewModel.games.value!! 130 val baseList = gamesViewModel.games.value
113 val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) { 131 val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) {
114 R.id.chip_recently_played -> { 132 R.id.chip_recently_played -> {
115 baseList.filter { 133 baseList.filter {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
index 55b6a0367..9d0594c6e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SettingsSearchFragment.kt
@@ -15,10 +15,14 @@ import androidx.core.view.updatePadding
15import androidx.core.widget.doOnTextChanged 15import androidx.core.widget.doOnTextChanged
16import androidx.fragment.app.Fragment 16import androidx.fragment.app.Fragment
17import androidx.fragment.app.activityViewModels 17import androidx.fragment.app.activityViewModels
18import androidx.lifecycle.Lifecycle
19import androidx.lifecycle.lifecycleScope
20import androidx.lifecycle.repeatOnLifecycle
18import androidx.recyclerview.widget.LinearLayoutManager 21import androidx.recyclerview.widget.LinearLayoutManager
19import com.google.android.material.divider.MaterialDividerItemDecoration 22import com.google.android.material.divider.MaterialDividerItemDecoration
20import com.google.android.material.transition.MaterialSharedAxis 23import com.google.android.material.transition.MaterialSharedAxis
21import info.debatty.java.stringsimilarity.Cosine 24import info.debatty.java.stringsimilarity.Cosine
25import kotlinx.coroutines.launch
22import org.yuzu.yuzu_emu.R 26import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding 27import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
24import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 28import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
@@ -79,10 +83,14 @@ class SettingsSearchFragment : Fragment() {
79 search() 83 search()
80 binding.settingsList.smoothScrollToPosition(0) 84 binding.settingsList.smoothScrollToPosition(0)
81 } 85 }
82 settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) { 86 viewLifecycleOwner.lifecycleScope.launch {
83 if (it) { 87 repeatOnLifecycle(Lifecycle.State.CREATED) {
84 settingsViewModel.setShouldReloadSettingsList(false) 88 settingsViewModel.shouldReloadSettingsList.collect {
85 search() 89 if (it) {
90 settingsViewModel.setShouldReloadSettingsList(false)
91 search()
92 }
93 }
86 } 94 }
87 } 95 }
88 96
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
index d50c421a0..fbb2f6e18 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
@@ -22,10 +22,14 @@ import androidx.core.view.isVisible
22import androidx.core.view.updatePadding 22import androidx.core.view.updatePadding
23import androidx.fragment.app.Fragment 23import androidx.fragment.app.Fragment
24import androidx.fragment.app.activityViewModels 24import androidx.fragment.app.activityViewModels
25import androidx.lifecycle.Lifecycle
26import androidx.lifecycle.lifecycleScope
27import androidx.lifecycle.repeatOnLifecycle
25import androidx.navigation.findNavController 28import androidx.navigation.findNavController
26import androidx.preference.PreferenceManager 29import androidx.preference.PreferenceManager
27import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback 30import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
28import com.google.android.material.transition.MaterialFadeThrough 31import com.google.android.material.transition.MaterialFadeThrough
32import kotlinx.coroutines.launch
29import java.io.File 33import java.io.File
30import org.yuzu.yuzu_emu.R 34import org.yuzu.yuzu_emu.R
31import org.yuzu.yuzu_emu.YuzuApplication 35import org.yuzu.yuzu_emu.YuzuApplication
@@ -206,10 +210,14 @@ class SetupFragment : Fragment() {
206 ) 210 )
207 } 211 }
208 212
209 homeViewModel.shouldPageForward.observe(viewLifecycleOwner) { 213 viewLifecycleOwner.lifecycleScope.launch {
210 if (it) { 214 repeatOnLifecycle(Lifecycle.State.CREATED) {
211 pageForward() 215 homeViewModel.shouldPageForward.collect {
212 homeViewModel.setShouldPageForward(false) 216 if (it) {
217 pageForward()
218 homeViewModel.setShouldPageForward(false)
219 }
220 }
213 } 221 }
214 } 222 }
215 223
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt
index e35f51bc3..f34870c2d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/EmulationViewModel.kt
@@ -3,28 +3,28 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.ViewModel 6import androidx.lifecycle.ViewModel
7import kotlinx.coroutines.flow.MutableStateFlow
8import kotlinx.coroutines.flow.StateFlow
9 9
10class EmulationViewModel : ViewModel() { 10class EmulationViewModel : ViewModel() {
11 private val _emulationStarted = MutableLiveData(false) 11 val emulationStarted: StateFlow<Boolean> get() = _emulationStarted
12 val emulationStarted: LiveData<Boolean> get() = _emulationStarted 12 private val _emulationStarted = MutableStateFlow(false)
13 13
14 private val _isEmulationStopping = MutableLiveData(false) 14 val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping
15 val isEmulationStopping: LiveData<Boolean> get() = _isEmulationStopping 15 private val _isEmulationStopping = MutableStateFlow(false)
16 16
17 private val _shaderProgress = MutableLiveData(0) 17 val shaderProgress: StateFlow<Int> get() = _shaderProgress
18 val shaderProgress: LiveData<Int> get() = _shaderProgress 18 private val _shaderProgress = MutableStateFlow(0)
19 19
20 private val _totalShaders = MutableLiveData(0) 20 val totalShaders: StateFlow<Int> get() = _totalShaders
21 val totalShaders: LiveData<Int> get() = _totalShaders 21 private val _totalShaders = MutableStateFlow(0)
22 22
23 private val _shaderMessage = MutableLiveData("") 23 val shaderMessage: StateFlow<String> get() = _shaderMessage
24 val shaderMessage: LiveData<String> get() = _shaderMessage 24 private val _shaderMessage = MutableStateFlow("")
25 25
26 fun setEmulationStarted(started: Boolean) { 26 fun setEmulationStarted(started: Boolean) {
27 _emulationStarted.postValue(started) 27 _emulationStarted.value = started
28 } 28 }
29 29
30 fun setIsEmulationStopping(value: Boolean) { 30 fun setIsEmulationStopping(value: Boolean) {
@@ -50,10 +50,18 @@ class EmulationViewModel : ViewModel() {
50 } 50 }
51 51
52 fun clear() { 52 fun clear() {
53 _emulationStarted.value = false 53 setEmulationStarted(false)
54 _isEmulationStopping.value = false 54 setIsEmulationStopping(false)
55 _shaderProgress.value = 0 55 setShaderProgress(0)
56 _totalShaders.value = 0 56 setTotalShaders(0)
57 _shaderMessage.value = "" 57 setShaderMessage("")
58 }
59
60 companion object {
61 const val KEY_EMULATION_STARTED = "EmulationStarted"
62 const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting"
63 const val KEY_SHADER_PROGRESS = "ShaderProgress"
64 const val KEY_TOTAL_SHADERS = "TotalShaders"
65 const val KEY_SHADER_MESSAGE = "ShaderMessage"
58 } 66 }
59} 67}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
index 1fe42f922..6e09fa81d 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
@@ -5,13 +5,13 @@ package org.yuzu.yuzu_emu.model
5 5
6import android.net.Uri 6import android.net.Uri
7import androidx.documentfile.provider.DocumentFile 7import androidx.documentfile.provider.DocumentFile
8import androidx.lifecycle.LiveData
9import androidx.lifecycle.MutableLiveData
10import androidx.lifecycle.ViewModel 8import androidx.lifecycle.ViewModel
11import androidx.lifecycle.viewModelScope 9import androidx.lifecycle.viewModelScope
12import androidx.preference.PreferenceManager 10import androidx.preference.PreferenceManager
13import java.util.Locale 11import java.util.Locale
14import kotlinx.coroutines.Dispatchers 12import kotlinx.coroutines.Dispatchers
13import kotlinx.coroutines.flow.MutableStateFlow
14import kotlinx.coroutines.flow.StateFlow
15import kotlinx.coroutines.launch 15import kotlinx.coroutines.launch
16import kotlinx.coroutines.withContext 16import kotlinx.coroutines.withContext
17import kotlinx.serialization.ExperimentalSerializationApi 17import kotlinx.serialization.ExperimentalSerializationApi
@@ -24,23 +24,23 @@ import org.yuzu.yuzu_emu.utils.GameHelper
24 24
25@OptIn(ExperimentalSerializationApi::class) 25@OptIn(ExperimentalSerializationApi::class)
26class GamesViewModel : ViewModel() { 26class GamesViewModel : ViewModel() {
27 private val _games = MutableLiveData<List<Game>>(emptyList()) 27 val games: StateFlow<List<Game>> get() = _games
28 val games: LiveData<List<Game>> get() = _games 28 private val _games = MutableStateFlow(emptyList<Game>())
29 29
30 private val _searchedGames = MutableLiveData<List<Game>>(emptyList()) 30 val searchedGames: StateFlow<List<Game>> get() = _searchedGames
31 val searchedGames: LiveData<List<Game>> get() = _searchedGames 31 private val _searchedGames = MutableStateFlow(emptyList<Game>())
32 32
33 private val _isReloading = MutableLiveData(false) 33 val isReloading: StateFlow<Boolean> get() = _isReloading
34 val isReloading: LiveData<Boolean> get() = _isReloading 34 private val _isReloading = MutableStateFlow(false)
35 35
36 private val _shouldSwapData = MutableLiveData(false) 36 val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData
37 val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData 37 private val _shouldSwapData = MutableStateFlow(false)
38 38
39 private val _shouldScrollToTop = MutableLiveData(false) 39 val shouldScrollToTop: StateFlow<Boolean> get() = _shouldScrollToTop
40 val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop 40 private val _shouldScrollToTop = MutableStateFlow(false)
41 41
42 private val _searchFocused = MutableLiveData(false) 42 val searchFocused: StateFlow<Boolean> get() = _searchFocused
43 val searchFocused: LiveData<Boolean> get() = _searchFocused 43 private val _searchFocused = MutableStateFlow(false)
44 44
45 init { 45 init {
46 // Ensure keys are loaded so that ROM metadata can be decrypted. 46 // Ensure keys are loaded so that ROM metadata can be decrypted.
@@ -79,36 +79,36 @@ class GamesViewModel : ViewModel() {
79 ) 79 )
80 ) 80 )
81 81
82 _games.postValue(sortedList) 82 _games.value = sortedList
83 } 83 }
84 84
85 fun setSearchedGames(games: List<Game>) { 85 fun setSearchedGames(games: List<Game>) {
86 _searchedGames.postValue(games) 86 _searchedGames.value = games
87 } 87 }
88 88
89 fun setShouldSwapData(shouldSwap: Boolean) { 89 fun setShouldSwapData(shouldSwap: Boolean) {
90 _shouldSwapData.postValue(shouldSwap) 90 _shouldSwapData.value = shouldSwap
91 } 91 }
92 92
93 fun setShouldScrollToTop(shouldScroll: Boolean) { 93 fun setShouldScrollToTop(shouldScroll: Boolean) {
94 _shouldScrollToTop.postValue(shouldScroll) 94 _shouldScrollToTop.value = shouldScroll
95 } 95 }
96 96
97 fun setSearchFocused(searchFocused: Boolean) { 97 fun setSearchFocused(searchFocused: Boolean) {
98 _searchFocused.postValue(searchFocused) 98 _searchFocused.value = searchFocused
99 } 99 }
100 100
101 fun reloadGames(directoryChanged: Boolean) { 101 fun reloadGames(directoryChanged: Boolean) {
102 if (isReloading.value == true) { 102 if (isReloading.value) {
103 return 103 return
104 } 104 }
105 _isReloading.postValue(true) 105 _isReloading.value = true
106 106
107 viewModelScope.launch { 107 viewModelScope.launch {
108 withContext(Dispatchers.IO) { 108 withContext(Dispatchers.IO) {
109 NativeLibrary.resetRomMetadata() 109 NativeLibrary.resetRomMetadata()
110 setGames(GameHelper.getGames()) 110 setGames(GameHelper.getGames())
111 _isReloading.postValue(false) 111 _isReloading.value = false
112 112
113 if (directoryChanged) { 113 if (directoryChanged) {
114 setShouldSwapData(true) 114 setShouldSwapData(true)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
index 498c222fa..b32e19373 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeSetting.kt
@@ -3,8 +3,8 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData 6import kotlinx.coroutines.flow.MutableStateFlow
7import androidx.lifecycle.MutableLiveData 7import kotlinx.coroutines.flow.StateFlow
8 8
9data class HomeSetting( 9data class HomeSetting(
10 val titleId: Int, 10 val titleId: Int,
@@ -14,5 +14,5 @@ data class HomeSetting(
14 val isEnabled: () -> Boolean = { true }, 14 val isEnabled: () -> Boolean = { true },
15 val disabledTitleId: Int = 0, 15 val disabledTitleId: Int = 0,
16 val disabledMessageId: Int = 0, 16 val disabledMessageId: Int = 0,
17 val details: LiveData<String> = MutableLiveData("") 17 val details: StateFlow<String> = MutableStateFlow("")
18) 18)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
index a48ef7a88..756f76721 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
@@ -5,47 +5,43 @@ package org.yuzu.yuzu_emu.model
5 5
6import android.net.Uri 6import android.net.Uri
7import androidx.fragment.app.FragmentActivity 7import androidx.fragment.app.FragmentActivity
8import androidx.lifecycle.LiveData
9import androidx.lifecycle.MutableLiveData
10import androidx.lifecycle.ViewModel 8import androidx.lifecycle.ViewModel
11import androidx.lifecycle.ViewModelProvider 9import androidx.lifecycle.ViewModelProvider
12import androidx.preference.PreferenceManager 10import androidx.preference.PreferenceManager
11import kotlinx.coroutines.flow.MutableStateFlow
12import kotlinx.coroutines.flow.StateFlow
13import org.yuzu.yuzu_emu.YuzuApplication 13import org.yuzu.yuzu_emu.YuzuApplication
14import org.yuzu.yuzu_emu.utils.GameHelper 14import org.yuzu.yuzu_emu.utils.GameHelper
15 15
16class HomeViewModel : ViewModel() { 16class HomeViewModel : ViewModel() {
17 private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>() 17 val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible
18 val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible 18 private val _navigationVisible = MutableStateFlow(Pair(false, false))
19 19
20 private val _statusBarShadeVisible = MutableLiveData(true) 20 val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible
21 val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible 21 private val _statusBarShadeVisible = MutableStateFlow(true)
22 22
23 private val _shouldPageForward = MutableLiveData(false) 23 val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward
24 val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward 24 private val _shouldPageForward = MutableStateFlow(false)
25 25
26 private val _gamesDir = MutableLiveData( 26 val gamesDir: StateFlow<String> get() = _gamesDir
27 private val _gamesDir = MutableStateFlow(
27 Uri.parse( 28 Uri.parse(
28 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 29 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
29 .getString(GameHelper.KEY_GAME_PATH, "") 30 .getString(GameHelper.KEY_GAME_PATH, "")
30 ).path ?: "" 31 ).path ?: ""
31 ) 32 )
32 val gamesDir: LiveData<String> get() = _gamesDir
33 33
34 var navigatedToSetup = false 34 var navigatedToSetup = false
35 35
36 init {
37 _navigationVisible.value = Pair(false, false)
38 }
39
40 fun setNavigationVisibility(visible: Boolean, animated: Boolean) { 36 fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
41 if (_navigationVisible.value?.first == visible) { 37 if (navigationVisible.value.first == visible) {
42 return 38 return
43 } 39 }
44 _navigationVisible.value = Pair(visible, animated) 40 _navigationVisible.value = Pair(visible, animated)
45 } 41 }
46 42
47 fun setStatusBarShadeVisibility(visible: Boolean) { 43 fun setStatusBarShadeVisibility(visible: Boolean) {
48 if (_statusBarShadeVisible.value == visible) { 44 if (statusBarShadeVisible.value == visible) {
49 return 45 return
50 } 46 }
51 _statusBarShadeVisible.value = visible 47 _statusBarShadeVisible.value = visible
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
index d16d15fa6..53fa7a8de 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SettingsViewModel.kt
@@ -3,48 +3,43 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.SavedStateHandle
9import androidx.lifecycle.ViewModel 6import androidx.lifecycle.ViewModel
7import kotlinx.coroutines.flow.MutableStateFlow
8import kotlinx.coroutines.flow.StateFlow
10import org.yuzu.yuzu_emu.R 9import org.yuzu.yuzu_emu.R
11import org.yuzu.yuzu_emu.YuzuApplication 10import org.yuzu.yuzu_emu.YuzuApplication
12import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem 11import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
13 12
14class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { 13class SettingsViewModel : ViewModel() {
15 var game: Game? = null 14 var game: Game? = null
16 15
17 var shouldSave = false 16 var shouldSave = false
18 17
19 var clickedItem: SettingsItem? = null 18 var clickedItem: SettingsItem? = null
20 19
21 private val _toolbarTitle = MutableLiveData("") 20 val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
22 val toolbarTitle: LiveData<String> get() = _toolbarTitle 21 private val _shouldRecreate = MutableStateFlow(false)
23 22
24 private val _shouldRecreate = MutableLiveData(false) 23 val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack
25 val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate 24 private val _shouldNavigateBack = MutableStateFlow(false)
26 25
27 private val _shouldNavigateBack = MutableLiveData(false) 26 val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog
28 val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack 27 private val _shouldShowResetSettingsDialog = MutableStateFlow(false)
29 28
30 private val _shouldShowResetSettingsDialog = MutableLiveData(false) 29 val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
31 val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog 30 private val _shouldReloadSettingsList = MutableStateFlow(false)
32 31
33 private val _shouldReloadSettingsList = MutableLiveData(false) 32 val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch
34 val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList 33 private val _isUsingSearch = MutableStateFlow(false)
35 34
36 private val _isUsingSearch = MutableLiveData(false) 35 val sliderProgress: StateFlow<Int> get() = _sliderProgress
37 val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch 36 private val _sliderProgress = MutableStateFlow(-1)
38 37
39 val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1) 38 val sliderTextValue: StateFlow<String> get() = _sliderTextValue
39 private val _sliderTextValue = MutableStateFlow("")
40 40
41 val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "") 41 val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged
42 42 private val _adapterItemChanged = MutableStateFlow(-1)
43 val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1)
44
45 fun setToolbarTitle(value: String) {
46 _toolbarTitle.value = value
47 }
48 43
49 fun setShouldRecreate(value: Boolean) { 44 fun setShouldRecreate(value: Boolean) {
50 _shouldRecreate.value = value 45 _shouldRecreate.value = value
@@ -67,8 +62,8 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
67 } 62 }
68 63
69 fun setSliderTextValue(value: Float, units: String) { 64 fun setSliderTextValue(value: Float, units: String) {
70 savedStateHandle[KEY_SLIDER_PROGRESS] = value 65 _sliderProgress.value = value.toInt()
71 savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format( 66 _sliderTextValue.value = String.format(
72 YuzuApplication.appContext.getString(R.string.value_with_units), 67 YuzuApplication.appContext.getString(R.string.value_with_units),
73 value.toInt().toString(), 68 value.toInt().toString(),
74 units 69 units
@@ -76,21 +71,15 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
76 } 71 }
77 72
78 fun setSliderProgress(value: Float) { 73 fun setSliderProgress(value: Float) {
79 savedStateHandle[KEY_SLIDER_PROGRESS] = value 74 _sliderProgress.value = value.toInt()
80 } 75 }
81 76
82 fun setAdapterItemChanged(value: Int) { 77 fun setAdapterItemChanged(value: Int) {
83 savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value 78 _adapterItemChanged.value = value
84 } 79 }
85 80
86 fun clear() { 81 fun clear() {
87 game = null 82 game = null
88 shouldSave = false 83 shouldSave = false
89 } 84 }
90
91 companion object {
92 const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
93 const val KEY_SLIDER_PROGRESS = "SliderProgress"
94 const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
95 }
96} 85}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
index 27ea725a5..531c2aaf0 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/TaskViewModel.kt
@@ -3,29 +3,25 @@
3 3
4package org.yuzu.yuzu_emu.model 4package org.yuzu.yuzu_emu.model
5 5
6import androidx.lifecycle.LiveData
7import androidx.lifecycle.MutableLiveData
8import androidx.lifecycle.ViewModel 6import androidx.lifecycle.ViewModel
9import androidx.lifecycle.viewModelScope 7import androidx.lifecycle.viewModelScope
10import kotlinx.coroutines.Dispatchers 8import kotlinx.coroutines.Dispatchers
9import kotlinx.coroutines.flow.MutableStateFlow
10import kotlinx.coroutines.flow.StateFlow
11import kotlinx.coroutines.launch 11import kotlinx.coroutines.launch
12 12
13class TaskViewModel : ViewModel() { 13class TaskViewModel : ViewModel() {
14 private val _result = MutableLiveData<Any>() 14 val result: StateFlow<Any> get() = _result
15 val result: LiveData<Any> = _result 15 private val _result = MutableStateFlow(Any())
16 16
17 private val _isComplete = MutableLiveData<Boolean>() 17 val isComplete: StateFlow<Boolean> get() = _isComplete
18 val isComplete: LiveData<Boolean> = _isComplete 18 private val _isComplete = MutableStateFlow(false)
19 19
20 private val _isRunning = MutableLiveData<Boolean>() 20 val isRunning: StateFlow<Boolean> get() = _isRunning
21 val isRunning: LiveData<Boolean> = _isRunning 21 private val _isRunning = MutableStateFlow(false)
22 22
23 lateinit var task: () -> Any 23 lateinit var task: () -> Any
24 24
25 init {
26 clear()
27 }
28
29 fun clear() { 25 fun clear() {
30 _result.value = Any() 26 _result.value = Any()
31 _isComplete.value = false 27 _isComplete.value = false
@@ -33,15 +29,16 @@ class TaskViewModel : ViewModel() {
33 } 29 }
34 30
35 fun runTask() { 31 fun runTask() {
36 if (_isRunning.value == true) { 32 if (isRunning.value) {
37 return 33 return
38 } 34 }
39 _isRunning.value = true 35 _isRunning.value = true
40 36
41 viewModelScope.launch(Dispatchers.IO) { 37 viewModelScope.launch(Dispatchers.IO) {
42 val res = task() 38 val res = task()
43 _result.postValue(res) 39 _result.value = res
44 _isComplete.postValue(true) 40 _isComplete.value = true
41 _isRunning.value = false
45 } 42 }
46 } 43 }
47} 44}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
index b0156dca5..805b89b31 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/GamesFragment.kt
@@ -3,6 +3,7 @@
3 3
4package org.yuzu.yuzu_emu.ui 4package org.yuzu.yuzu_emu.ui
5 5
6import android.annotation.SuppressLint
6import android.os.Bundle 7import android.os.Bundle
7import android.view.LayoutInflater 8import android.view.LayoutInflater
8import android.view.View 9import android.view.View
@@ -14,8 +15,12 @@ import androidx.core.view.WindowInsetsCompat
14import androidx.core.view.updatePadding 15import androidx.core.view.updatePadding
15import androidx.fragment.app.Fragment 16import androidx.fragment.app.Fragment
16import androidx.fragment.app.activityViewModels 17import androidx.fragment.app.activityViewModels
18import androidx.lifecycle.Lifecycle
19import androidx.lifecycle.lifecycleScope
20import androidx.lifecycle.repeatOnLifecycle
17import com.google.android.material.color.MaterialColors 21import com.google.android.material.color.MaterialColors
18import com.google.android.material.transition.MaterialFadeThrough 22import com.google.android.material.transition.MaterialFadeThrough
23import kotlinx.coroutines.launch
19import org.yuzu.yuzu_emu.R 24import org.yuzu.yuzu_emu.R
20import org.yuzu.yuzu_emu.adapters.GameAdapter 25import org.yuzu.yuzu_emu.adapters.GameAdapter
21import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding 26import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
@@ -44,6 +49,8 @@ class GamesFragment : Fragment() {
44 return binding.root 49 return binding.root
45 } 50 }
46 51
52 // This is using the correct scope, lint is just acting up
53 @SuppressLint("UnsafeRepeatOnLifecycleDetector")
47 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 54 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
48 homeViewModel.setNavigationVisibility(visible = true, animated = false) 55 homeViewModel.setNavigationVisibility(visible = true, animated = false)
49 56
@@ -80,37 +87,48 @@ class GamesFragment : Fragment() {
80 if (_binding == null) { 87 if (_binding == null) {
81 return@post 88 return@post
82 } 89 }
83 binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!! 90 binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value
84 } 91 }
85 } 92 }
86 93
87 gamesViewModel.apply { 94 viewLifecycleOwner.lifecycleScope.apply {
88 // Watch for when we get updates to any of our games lists 95 launch {
89 isReloading.observe(viewLifecycleOwner) { isReloading -> 96 repeatOnLifecycle(Lifecycle.State.RESUMED) {
90 binding.swipeRefresh.isRefreshing = isReloading 97 gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it }
98 }
91 } 99 }
92 games.observe(viewLifecycleOwner) { 100 launch {
93 (binding.gridGames.adapter as GameAdapter).submitList(it) 101 repeatOnLifecycle(Lifecycle.State.RESUMED) {
94 if (it.isEmpty()) { 102 gamesViewModel.games.collect {
95 binding.noticeText.visibility = View.VISIBLE 103 (binding.gridGames.adapter as GameAdapter).submitList(it)
96 } else { 104 if (it.isEmpty()) {
97 binding.noticeText.visibility = View.GONE 105 binding.noticeText.visibility = View.VISIBLE
106 } else {
107 binding.noticeText.visibility = View.GONE
108 }
109 }
98 } 110 }
99 } 111 }
100 shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData -> 112 launch {
101 if (shouldSwapData) { 113 repeatOnLifecycle(Lifecycle.State.RESUMED) {
102 (binding.gridGames.adapter as GameAdapter).submitList( 114 gamesViewModel.shouldSwapData.collect {
103 gamesViewModel.games.value!! 115 if (it) {
104 ) 116 (binding.gridGames.adapter as GameAdapter).submitList(
105 gamesViewModel.setShouldSwapData(false) 117 gamesViewModel.games.value
118 )
119 gamesViewModel.setShouldSwapData(false)
120 }
121 }
106 } 122 }
107 } 123 }
108 124 launch {
109 // Check if the user reselected the games menu item and then scroll to top of the list 125 repeatOnLifecycle(Lifecycle.State.RESUMED) {
110 shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll -> 126 gamesViewModel.shouldScrollToTop.collect {
111 if (shouldScroll) { 127 if (it) {
112 scrollToTop() 128 scrollToTop()
113 gamesViewModel.setShouldScrollToTop(false) 129 gamesViewModel.setShouldScrollToTop(false)
130 }
131 }
114 } 132 }
115 } 133 }
116 } 134 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index 7d8e06ad8..b6b6c6c17 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -19,7 +19,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
19import androidx.core.view.ViewCompat 19import androidx.core.view.ViewCompat
20import androidx.core.view.WindowCompat 20import androidx.core.view.WindowCompat
21import androidx.core.view.WindowInsetsCompat 21import androidx.core.view.WindowInsetsCompat
22import androidx.lifecycle.Lifecycle
22import androidx.lifecycle.lifecycleScope 23import androidx.lifecycle.lifecycleScope
24import androidx.lifecycle.repeatOnLifecycle
23import androidx.navigation.NavController 25import androidx.navigation.NavController
24import androidx.navigation.fragment.NavHostFragment 26import androidx.navigation.fragment.NavHostFragment
25import androidx.navigation.ui.setupWithNavController 27import androidx.navigation.ui.setupWithNavController
@@ -40,7 +42,6 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity
40import org.yuzu.yuzu_emu.databinding.ActivityMainBinding 42import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
41import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding 43import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
42import org.yuzu.yuzu_emu.features.settings.model.Settings 44import org.yuzu.yuzu_emu.features.settings.model.Settings
43import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
44import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment 45import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
45import org.yuzu.yuzu_emu.fragments.MessageDialogFragment 46import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
46import org.yuzu.yuzu_emu.model.GamesViewModel 47import org.yuzu.yuzu_emu.model.GamesViewModel
@@ -107,7 +108,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
107 R.id.homeSettingsFragment -> { 108 R.id.homeSettingsFragment -> {
108 val action = HomeNavigationDirections.actionGlobalSettingsActivity( 109 val action = HomeNavigationDirections.actionGlobalSettingsActivity(
109 null, 110 null,
110 SettingsFile.FILE_NAME_CONFIG 111 Settings.MenuTag.SECTION_ROOT
111 ) 112 )
112 navHostFragment.navController.navigate(action) 113 navHostFragment.navController.navigate(action)
113 } 114 }
@@ -115,16 +116,22 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
115 } 116 }
116 117
117 // Prevents navigation from being drawn for a short time on recreation if set to hidden 118 // Prevents navigation from being drawn for a short time on recreation if set to hidden
118 if (!homeViewModel.navigationVisible.value?.first!!) { 119 if (!homeViewModel.navigationVisible.value.first) {
119 binding.navigationView.visibility = View.INVISIBLE 120 binding.navigationView.visibility = View.INVISIBLE
120 binding.statusBarShade.visibility = View.INVISIBLE 121 binding.statusBarShade.visibility = View.INVISIBLE
121 } 122 }
122 123
123 homeViewModel.navigationVisible.observe(this) { 124 lifecycleScope.apply {
124 showNavigation(it.first, it.second) 125 launch {
125 } 126 repeatOnLifecycle(Lifecycle.State.CREATED) {
126 homeViewModel.statusBarShadeVisible.observe(this) { visible -> 127 homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) }
127 showStatusBarShade(visible) 128 }
129 }
130 launch {
131 repeatOnLifecycle(Lifecycle.State.CREATED) {
132 homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
133 }
134 }
128 } 135 }
129 136
130 // Dismiss previous notifications (should not happen unless a crash occurred) 137 // Dismiss previous notifications (should not happen unless a crash occurred)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
index c0fe596d7..9fe99fab1 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
@@ -6,9 +6,11 @@ package org.yuzu.yuzu_emu.utils
6import android.graphics.Bitmap 6import android.graphics.Bitmap
7import android.graphics.BitmapFactory 7import android.graphics.BitmapFactory
8import android.widget.ImageView 8import android.widget.ImageView
9import androidx.core.graphics.drawable.toBitmap
9import androidx.core.graphics.drawable.toDrawable 10import androidx.core.graphics.drawable.toDrawable
10import coil.ImageLoader 11import coil.ImageLoader
11import coil.decode.DataSource 12import coil.decode.DataSource
13import coil.executeBlocking
12import coil.fetch.DrawableResult 14import coil.fetch.DrawableResult
13import coil.fetch.FetchResult 15import coil.fetch.FetchResult
14import coil.fetch.Fetcher 16import coil.fetch.Fetcher
@@ -74,4 +76,13 @@ object GameIconUtils {
74 .build() 76 .build()
75 imageLoader.enqueue(request) 77 imageLoader.enqueue(request)
76 } 78 }
79
80 fun getGameIcon(game: Game): Bitmap {
81 val request = ImageRequest.Builder(YuzuApplication.appContext)
82 .data(game)
83 .error(R.drawable.default_icon)
84 .build()
85 return imageLoader.executeBlocking(request)
86 .drawable!!.toBitmap(config = Bitmap.Config.ARGB_8888)
87 }
77} 88}
diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp
index 34b425cb4..81120ab0f 100644
--- a/src/android/app/src/main/jni/config.cpp
+++ b/src/android/app/src/main/jni/config.cpp
@@ -282,7 +282,7 @@ void Config::ReadValues() {
282 std::stringstream ss(title_list); 282 std::stringstream ss(title_list);
283 std::string line; 283 std::string line;
284 while (std::getline(ss, line, '|')) { 284 while (std::getline(ss, line, '|')) {
285 const auto title_id = std::stoul(line, nullptr, 16); 285 const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
286 const auto disabled_list = config->Get("AddOns", "disabled_" + line, ""); 286 const auto disabled_list = config->Get("AddOns", "disabled_" + line, "");
287 287
288 std::stringstream inner_ss(disabled_list); 288 std::stringstream inner_ss(disabled_list);
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index b9ecefa74..f31fe054b 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -262,9 +262,6 @@ public:
262 Core::SystemResultStatus InitializeEmulation(const std::string& filepath) { 262 Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
263 std::scoped_lock lock(m_mutex); 263 std::scoped_lock lock(m_mutex);
264 264
265 // Loads the configuration.
266 Config{};
267
268 // Create the render window. 265 // Create the render window.
269 m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, 266 m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window,
270 m_vulkan_library); 267 m_vulkan_library);
@@ -330,12 +327,13 @@ public:
330 m_system.ShutdownMainProcess(); 327 m_system.ShutdownMainProcess();
331 m_detached_tasks.WaitForAllTasks(); 328 m_detached_tasks.WaitForAllTasks();
332 m_load_result = Core::SystemResultStatus::ErrorNotInitialized; 329 m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
330 m_window.reset();
331 OnEmulationStopped(Core::SystemResultStatus::Success);
332 return;
333 } 333 }
334 334
335 // Tear down the render window. 335 // Tear down the render window.
336 m_window.reset(); 336 m_window.reset();
337
338 OnEmulationStopped(m_load_result);
339 } 337 }
340 338
341 void PauseEmulation() { 339 void PauseEmulation() {
@@ -672,18 +670,6 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
672 return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused()); 670 return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
673} 671}
674 672
675void Java_org_yuzu_yuzu_1emu_NativeLibrary_muteAduio(JNIEnv* env, jclass clazz) {
676 Settings::values.audio_muted = true;
677}
678
679void Java_org_yuzu_yuzu_1emu_NativeLibrary_unmuteAudio(JNIEnv* env, jclass clazz) {
680 Settings::values.audio_muted = false;
681}
682
683jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isMuted(JNIEnv* env, jclass clazz) {
684 return static_cast<jboolean>(Settings::values.audio_muted.GetValue());
685}
686
687jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) { 673jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) {
688 return EmulationSession::GetInstance().IsHandheldOnly(); 674 return EmulationSession::GetInstance().IsHandheldOnly();
689} 675}
diff --git a/src/android/app/src/main/res/drawable/shortcut.xml b/src/android/app/src/main/res/drawable/shortcut.xml
new file mode 100644
index 000000000..c749e5d72
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/shortcut.xml
@@ -0,0 +1,11 @@
1<?xml version="1.0" encoding="utf-8"?>
2<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
3
4 <item>
5 <color android:color="@android:color/white" />
6 </item>
7 <item android:id="@+id/shortcut_foreground">
8 <bitmap android:src="@drawable/default_icon" />
9 </item>
10
11</layer-list>
diff --git a/src/android/app/src/main/res/navigation/emulation_navigation.xml b/src/android/app/src/main/res/navigation/emulation_navigation.xml
index c7be37f9b..cfc494b3f 100644
--- a/src/android/app/src/main/res/navigation/emulation_navigation.xml
+++ b/src/android/app/src/main/res/navigation/emulation_navigation.xml
@@ -27,7 +27,7 @@
27 app:nullable="true" /> 27 app:nullable="true" />
28 <argument 28 <argument
29 android:name="menuTag" 29 android:name="menuTag"
30 app:argType="string" /> 30 app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
31 </activity> 31 </activity>
32 32
33 <action 33 <action
diff --git a/src/android/app/src/main/res/navigation/home_navigation.xml b/src/android/app/src/main/res/navigation/home_navigation.xml
index 2085430bf..2e0ce7a3d 100644
--- a/src/android/app/src/main/res/navigation/home_navigation.xml
+++ b/src/android/app/src/main/res/navigation/home_navigation.xml
@@ -82,7 +82,7 @@
82 app:nullable="true" /> 82 app:nullable="true" />
83 <argument 83 <argument
84 android:name="menuTag" 84 android:name="menuTag"
85 app:argType="string" /> 85 app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
86 </activity> 86 </activity>
87 87
88 <action 88 <action
diff --git a/src/android/app/src/main/res/navigation/settings_navigation.xml b/src/android/app/src/main/res/navigation/settings_navigation.xml
index 88e1b4587..1d87d36b3 100644
--- a/src/android/app/src/main/res/navigation/settings_navigation.xml
+++ b/src/android/app/src/main/res/navigation/settings_navigation.xml
@@ -10,7 +10,7 @@
10 android:label="SettingsFragment"> 10 android:label="SettingsFragment">
11 <argument 11 <argument
12 android:name="menuTag" 12 android:name="menuTag"
13 app:argType="string" /> 13 app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
14 <argument 14 <argument
15 android:name="game" 15 android:name="game"
16 app:argType="org.yuzu.yuzu_emu.model.Game" 16 app:argType="org.yuzu.yuzu_emu.model.Game"
diff --git a/src/android/app/src/main/res/values/dimens.xml b/src/android/app/src/main/res/values/dimens.xml
index 00757e5e8..7b2296d95 100644
--- a/src/android/app/src/main/res/values/dimens.xml
+++ b/src/android/app/src/main/res/values/dimens.xml
@@ -12,6 +12,7 @@
12 <dimen name="spacing_refresh_end">72dp</dimen> 12 <dimen name="spacing_refresh_end">72dp</dimen>
13 <dimen name="menu_width">256dp</dimen> 13 <dimen name="menu_width">256dp</dimen>
14 <dimen name="card_width">165dp</dimen> 14 <dimen name="card_width">165dp</dimen>
15 <dimen name="icon_inset">24dp</dimen>
15 16
16 <dimen name="dialog_margin">20dp</dimen> 17 <dimen name="dialog_margin">20dp</dimen>
17 <dimen name="elevated_app_bar">3dp</dimen> 18 <dimen name="elevated_app_bar">3dp</dimen>
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp
index 36e67c145..174aed49b 100644
--- a/src/common/fs/fs.cpp
+++ b/src/common/fs/fs.cpp
@@ -528,38 +528,41 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
528// Generic Filesystem Operations 528// Generic Filesystem Operations
529 529
530bool Exists(const fs::path& path) { 530bool Exists(const fs::path& path) {
531 std::error_code ec;
531#ifdef ANDROID 532#ifdef ANDROID
532 if (Android::IsContentUri(path)) { 533 if (Android::IsContentUri(path)) {
533 return Android::Exists(path); 534 return Android::Exists(path);
534 } else { 535 } else {
535 return fs::exists(path); 536 return fs::exists(path, ec);
536 } 537 }
537#else 538#else
538 return fs::exists(path); 539 return fs::exists(path, ec);
539#endif 540#endif
540} 541}
541 542
542bool IsFile(const fs::path& path) { 543bool IsFile(const fs::path& path) {
544 std::error_code ec;
543#ifdef ANDROID 545#ifdef ANDROID
544 if (Android::IsContentUri(path)) { 546 if (Android::IsContentUri(path)) {
545 return !Android::IsDirectory(path); 547 return !Android::IsDirectory(path);
546 } else { 548 } else {
547 return fs::is_regular_file(path); 549 return fs::is_regular_file(path, ec);
548 } 550 }
549#else 551#else
550 return fs::is_regular_file(path); 552 return fs::is_regular_file(path, ec);
551#endif 553#endif
552} 554}
553 555
554bool IsDir(const fs::path& path) { 556bool IsDir(const fs::path& path) {
557 std::error_code ec;
555#ifdef ANDROID 558#ifdef ANDROID
556 if (Android::IsContentUri(path)) { 559 if (Android::IsContentUri(path)) {
557 return Android::IsDirectory(path); 560 return Android::IsDirectory(path);
558 } else { 561 } else {
559 return fs::is_directory(path); 562 return fs::is_directory(path, ec);
560 } 563 }
561#else 564#else
562 return fs::is_directory(path); 565 return fs::is_directory(path, ec);
563#endif 566#endif
564} 567}
565 568
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp
index c95909561..4e3a614a4 100644
--- a/src/common/logging/filter.cpp
+++ b/src/common/logging/filter.cpp
@@ -112,7 +112,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
112 SUB(Service, NCM) \ 112 SUB(Service, NCM) \
113 SUB(Service, NFC) \ 113 SUB(Service, NFC) \
114 SUB(Service, NFP) \ 114 SUB(Service, NFP) \
115 SUB(Service, NGCT) \ 115 SUB(Service, NGC) \
116 SUB(Service, NIFM) \ 116 SUB(Service, NIFM) \
117 SUB(Service, NIM) \ 117 SUB(Service, NIM) \
118 SUB(Service, NOTIF) \ 118 SUB(Service, NOTIF) \
diff --git a/src/common/logging/types.h b/src/common/logging/types.h
index 8356e3183..08af50ee0 100644
--- a/src/common/logging/types.h
+++ b/src/common/logging/types.h
@@ -80,7 +80,7 @@ enum class Class : u8 {
80 Service_NCM, ///< The NCM service 80 Service_NCM, ///< The NCM service
81 Service_NFC, ///< The NFC (Near-field communication) service 81 Service_NFC, ///< The NFC (Near-field communication) service
82 Service_NFP, ///< The NFP service 82 Service_NFP, ///< The NFP service
83 Service_NGCT, ///< The NGCT (No Good Content for Terra) service 83 Service_NGC, ///< The NGC (No Good Content) service
84 Service_NIFM, ///< The NIFM (Network interface) service 84 Service_NIFM, ///< The NIFM (Network interface) service
85 Service_NIM, ///< The NIM service 85 Service_NIM, ///< The NIM service
86 Service_NOTIF, ///< The NOTIF (Notification) service 86 Service_NOTIF, ///< The NOTIF (Notification) service
diff --git a/src/common/polyfill_thread.h b/src/common/polyfill_thread.h
index b5ef055db..41cbb9ed5 100644
--- a/src/common/polyfill_thread.h
+++ b/src/common/polyfill_thread.h
@@ -19,8 +19,8 @@
19namespace Common { 19namespace Common {
20 20
21template <typename Condvar, typename Lock, typename Pred> 21template <typename Condvar, typename Lock, typename Pred>
22void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) { 22void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
23 cv.wait(lock, token, std::move(pred)); 23 cv.wait(lk, token, std::move(pred));
24} 24}
25 25
26template <typename Rep, typename Period> 26template <typename Rep, typename Period>
@@ -332,13 +332,17 @@ private:
332namespace Common { 332namespace Common {
333 333
334template <typename Condvar, typename Lock, typename Pred> 334template <typename Condvar, typename Lock, typename Pred>
335void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) { 335void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred pred) {
336 if (token.stop_requested()) { 336 if (token.stop_requested()) {
337 return; 337 return;
338 } 338 }
339 339
340 std::stop_callback callback(token, [&] { cv.notify_all(); }); 340 std::stop_callback callback(token, [&] {
341 cv.wait(lock, [&] { return pred() || token.stop_requested(); }); 341 { std::scoped_lock lk2{*lk.mutex()}; }
342 cv.notify_all();
343 });
344
345 cv.wait(lk, [&] { return pred() || token.stop_requested(); });
342} 346}
343 347
344template <typename Rep, typename Period> 348template <typename Rep, typename Period>
@@ -353,8 +357,10 @@ bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep,
353 357
354 std::stop_callback cb(token, [&] { 358 std::stop_callback cb(token, [&] {
355 // Wake up the waiting thread. 359 // Wake up the waiting thread.
356 std::unique_lock lk{m}; 360 {
357 stop_requested = true; 361 std::scoped_lock lk{m};
362 stop_requested = true;
363 }
358 cv.notify_one(); 364 cv.notify_one();
359 }); 365 });
360 366
diff --git a/src/common/settings.h b/src/common/settings.h
index b15213bd7..82ec9077e 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -348,6 +348,8 @@ struct Values {
348 Category::RendererDebug}; 348 Category::RendererDebug};
349 Setting<bool> disable_shader_loop_safety_checks{ 349 Setting<bool> disable_shader_loop_safety_checks{
350 linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug}; 350 linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug};
351 Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey",
352 Category::RendererDebug};
351 353
352 // System 354 // System
353 SwitchableSetting<Language, true> language_index{linkage, 355 SwitchableSetting<Language, true> language_index{linkage,
diff --git a/src/common/settings_common.h b/src/common/settings_common.h
index 5b170dfd5..1800ab10d 100644
--- a/src/common/settings_common.h
+++ b/src/common/settings_common.h
@@ -225,6 +225,16 @@ public:
225 */ 225 */
226 [[nodiscard]] virtual constexpr u32 EnumIndex() const = 0; 226 [[nodiscard]] virtual constexpr u32 EnumIndex() const = 0;
227 227
228 /**
229 * @returns True if the underlying type is a floating point storage
230 */
231 [[nodiscard]] virtual constexpr bool IsFloatingPoint() const = 0;
232
233 /**
234 * @returns True if the underlying type is an integer storage
235 */
236 [[nodiscard]] virtual constexpr bool IsIntegral() const = 0;
237
228 /* 238 /*
229 * Switchable settings 239 * Switchable settings
230 */ 240 */
diff --git a/src/common/settings_setting.h b/src/common/settings_setting.h
index e10843c73..7be6f26f7 100644
--- a/src/common/settings_setting.h
+++ b/src/common/settings_setting.h
@@ -10,6 +10,7 @@
10#include <string> 10#include <string>
11#include <typeindex> 11#include <typeindex>
12#include <typeinfo> 12#include <typeinfo>
13#include <fmt/core.h>
13#include "common/common_types.h" 14#include "common/common_types.h"
14#include "common/settings_common.h" 15#include "common/settings_common.h"
15#include "common/settings_enums.h" 16#include "common/settings_enums.h"
@@ -115,8 +116,12 @@ protected:
115 } else if constexpr (std::is_same_v<Type, AudioEngine>) { 116 } else if constexpr (std::is_same_v<Type, AudioEngine>) {
116 // Compatibility with old AudioEngine setting being a string 117 // Compatibility with old AudioEngine setting being a string
117 return CanonicalizeEnum(value_); 118 return CanonicalizeEnum(value_);
119 } else if constexpr (std::is_floating_point_v<Type>) {
120 return fmt::format("{:f}", value_);
121 } else if constexpr (std::is_enum_v<Type>) {
122 return std::to_string(static_cast<u32>(value_));
118 } else { 123 } else {
119 return std::to_string(static_cast<u64>(value_)); 124 return std::to_string(value_);
120 } 125 }
121 } 126 }
122 127
@@ -180,13 +185,15 @@ public:
180 this->SetValue(static_cast<u32>(std::stoul(input))); 185 this->SetValue(static_cast<u32>(std::stoul(input)));
181 } else if constexpr (std::is_same_v<Type, bool>) { 186 } else if constexpr (std::is_same_v<Type, bool>) {
182 this->SetValue(input == "true"); 187 this->SetValue(input == "true");
183 } else if constexpr (std::is_same_v<Type, AudioEngine>) { 188 } else if constexpr (std::is_same_v<Type, float>) {
184 this->SetValue(ToEnum<Type>(input)); 189 this->SetValue(std::stof(input));
185 } else { 190 } else {
186 this->SetValue(static_cast<Type>(std::stoll(input))); 191 this->SetValue(static_cast<Type>(std::stoll(input)));
187 } 192 }
188 } catch (std::invalid_argument&) { 193 } catch (std::invalid_argument&) {
189 this->SetValue(this->GetDefault()); 194 this->SetValue(this->GetDefault());
195 } catch (std::out_of_range&) {
196 this->SetValue(this->GetDefault());
190 } 197 }
191 } 198 }
192 199
@@ -215,11 +222,27 @@ public:
215 } 222 }
216 } 223 }
217 224
225 [[nodiscard]] constexpr bool IsFloatingPoint() const final {
226 return std::is_floating_point_v<Type>;
227 }
228
229 [[nodiscard]] constexpr bool IsIntegral() const final {
230 return std::is_integral_v<Type>;
231 }
232
218 [[nodiscard]] std::string MinVal() const override final { 233 [[nodiscard]] std::string MinVal() const override final {
219 return this->ToString(minimum); 234 if constexpr (std::is_arithmetic_v<Type> && !ranged) {
235 return this->ToString(std::numeric_limits<Type>::min());
236 } else {
237 return this->ToString(minimum);
238 }
220 } 239 }
221 [[nodiscard]] std::string MaxVal() const override final { 240 [[nodiscard]] std::string MaxVal() const override final {
222 return this->ToString(maximum); 241 if constexpr (std::is_arithmetic_v<Type> && !ranged) {
242 return this->ToString(std::numeric_limits<Type>::max());
243 } else {
244 return this->ToString(maximum);
245 }
223 } 246 }
224 247
225 [[nodiscard]] constexpr bool Ranged() const override { 248 [[nodiscard]] constexpr bool Ranged() const override {
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index c33910ade..30d2f7df6 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -627,8 +627,8 @@ add_library(core STATIC
627 hle/service/nfp/nfp_interface.h 627 hle/service/nfp/nfp_interface.h
628 hle/service/nfp/nfp_result.h 628 hle/service/nfp/nfp_result.h
629 hle/service/nfp/nfp_types.h 629 hle/service/nfp/nfp_types.h
630 hle/service/ngct/ngct.cpp 630 hle/service/ngc/ngc.cpp
631 hle/service/ngct/ngct.h 631 hle/service/ngc/ngc.h
632 hle/service/nifm/nifm.cpp 632 hle/service/nifm/nifm.cpp
633 hle/service/nifm/nifm.h 633 hle/service/nifm/nifm.h
634 hle/service/nim/nim.cpp 634 hle/service/nim/nim.cpp
@@ -864,6 +864,8 @@ add_library(core STATIC
864 telemetry_session.h 864 telemetry_session.h
865 tools/freezer.cpp 865 tools/freezer.cpp
866 tools/freezer.h 866 tools/freezer.h
867 tools/renderdoc.cpp
868 tools/renderdoc.h
867) 869)
868 870
869if (MSVC) 871if (MSVC)
@@ -879,6 +881,7 @@ else()
879 -Werror=conversion 881 -Werror=conversion
880 882
881 -Wno-sign-conversion 883 -Wno-sign-conversion
884 -Wno-cast-function-type
882 885
883 $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation> 886 $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>
884 ) 887 )
@@ -887,7 +890,7 @@ endif()
887create_target_directory_groups(core) 890create_target_directory_groups(core)
888 891
889target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb) 892target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb)
890target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus) 893target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus renderdoc)
891if (MINGW) 894if (MINGW)
892 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) 895 target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
893endif() 896endif()
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 2d6e61398..e8300cd05 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -51,6 +51,7 @@
51#include "core/reporter.h" 51#include "core/reporter.h"
52#include "core/telemetry_session.h" 52#include "core/telemetry_session.h"
53#include "core/tools/freezer.h" 53#include "core/tools/freezer.h"
54#include "core/tools/renderdoc.h"
54#include "network/network.h" 55#include "network/network.h"
55#include "video_core/host1x/host1x.h" 56#include "video_core/host1x/host1x.h"
56#include "video_core/renderer_base.h" 57#include "video_core/renderer_base.h"
@@ -281,6 +282,10 @@ struct System::Impl {
281 microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2); 282 microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2);
282 microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3); 283 microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3);
283 284
285 if (Settings::values.enable_renderdoc_hotkey) {
286 renderdoc_api = std::make_unique<Tools::RenderdocAPI>();
287 }
288
284 LOG_DEBUG(Core, "Initialized OK"); 289 LOG_DEBUG(Core, "Initialized OK");
285 290
286 return SystemResultStatus::Success; 291 return SystemResultStatus::Success;
@@ -521,6 +526,8 @@ struct System::Impl {
521 std::unique_ptr<Tools::Freezer> memory_freezer; 526 std::unique_ptr<Tools::Freezer> memory_freezer;
522 std::array<u8, 0x20> build_id{}; 527 std::array<u8, 0x20> build_id{};
523 528
529 std::unique_ptr<Tools::RenderdocAPI> renderdoc_api;
530
524 /// Frontend applets 531 /// Frontend applets
525 Service::AM::Applets::AppletManager applet_manager; 532 Service::AM::Applets::AppletManager applet_manager;
526 533
@@ -1024,6 +1031,10 @@ const Network::RoomNetwork& System::GetRoomNetwork() const {
1024 return impl->room_network; 1031 return impl->room_network;
1025} 1032}
1026 1033
1034Tools::RenderdocAPI& System::GetRenderdocAPI() {
1035 return *impl->renderdoc_api;
1036}
1037
1027void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) { 1038void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
1028 return impl->kernel.RunServer(std::move(server_manager)); 1039 return impl->kernel.RunServer(std::move(server_manager));
1029} 1040}
diff --git a/src/core/core.h b/src/core/core.h
index fba312125..df20f26f3 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -102,6 +102,10 @@ namespace Network {
102class RoomNetwork; 102class RoomNetwork;
103} 103}
104 104
105namespace Tools {
106class RenderdocAPI;
107}
108
105namespace Core { 109namespace Core {
106 110
107class ARM_Interface; 111class ARM_Interface;
@@ -413,6 +417,8 @@ public:
413 /// Gets an immutable reference to the Room Network. 417 /// Gets an immutable reference to the Room Network.
414 [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; 418 [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const;
415 419
420 [[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI();
421
416 void SetExitLocked(bool locked); 422 void SetExitLocked(bool locked);
417 bool GetExitLocked() const; 423 bool GetExitLocked() const;
418 424
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index e13c5cdc7..43a3c5ffd 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -724,14 +724,14 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
724 continue; 724 continue;
725 } 725 }
726 726
727 const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16); 727 const auto index = std::strtoul(out[0].substr(8, 2).c_str(), nullptr, 16);
728 keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); 728 keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
729 } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { 729 } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
730 if (!ValidCryptoRevisionString(out[0], 18, 2)) { 730 if (!ValidCryptoRevisionString(out[0], 18, 2)) {
731 continue; 731 continue;
732 } 732 }
733 733
734 const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); 734 const auto index = std::strtoul(out[0].substr(18, 2).c_str(), nullptr, 16);
735 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); 735 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
736 } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { 736 } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) {
737 eticket_extended_kek = Common::HexStringToArray<576>(out[1]); 737 eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
@@ -750,7 +750,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
750 } 750 }
751 if (out[0].compare(0, kv.second.size(), kv.second) == 0) { 751 if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
752 const auto index = 752 const auto index =
753 std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16); 753 std::strtoul(out[0].substr(kv.second.size(), 2).c_str(), nullptr, 16);
754 const auto sub = kv.first.second; 754 const auto sub = kv.first.second;
755 if (sub == 0) { 755 if (sub == 0) {
756 s128_keys[{kv.first.first, index, 0}] = 756 s128_keys[{kv.first.first, index, 0}] =
@@ -770,7 +770,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
770 const auto& match = kak_names[j]; 770 const auto& match = kak_names[j];
771 if (out[0].compare(0, std::strlen(match), match) == 0) { 771 if (out[0].compare(0, std::strlen(match), match) == 0) {
772 const auto index = 772 const auto index =
773 std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16); 773 std::strtoul(out[0].substr(std::strlen(match), 2).c_str(), nullptr, 16);
774 s128_keys[{S128KeyType::KeyArea, index, j}] = 774 s128_keys[{S128KeyType::KeyArea, index, j}] =
775 Common::HexStringToArray<16>(out[1]); 775 Common::HexStringToArray<16>(out[1]);
776 } 776 }
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index efdf18cee..7be1322cc 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -165,7 +165,7 @@ static std::string EscapeStringSequences(std::string in) {
165void IPSwitchCompiler::ParseFlag(const std::string& line) { 165void IPSwitchCompiler::ParseFlag(const std::string& line) {
166 if (StartsWith(line, "@flag offset_shift ")) { 166 if (StartsWith(line, "@flag offset_shift ")) {
167 // Offset Shift Flag 167 // Offset Shift Flag
168 offset_shift = std::stoll(line.substr(19), nullptr, 0); 168 offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0);
169 } else if (StartsWith(line, "@little-endian")) { 169 } else if (StartsWith(line, "@little-endian")) {
170 // Set values to read as little endian 170 // Set values to read as little endian
171 is_little_endian = true; 171 is_little_endian = true;
@@ -263,7 +263,7 @@ void IPSwitchCompiler::Parse() {
263 // 11 - 8 hex digit offset + space + minimum two digit overwrite val 263 // 11 - 8 hex digit offset + space + minimum two digit overwrite val
264 if (patch_line.length() < 11) 264 if (patch_line.length() < 11)
265 break; 265 break;
266 auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16); 266 auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16);
267 offset += static_cast<unsigned long>(offset_shift); 267 offset += static_cast<unsigned long>(offset_shift);
268 268
269 std::vector<u8> replace; 269 std::vector<u8> replace;
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index a4baddb15..8e475f25a 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -294,11 +294,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
294 return out; 294 return out;
295} 295}
296 296
297bool PatchManager::HasNSOPatch(const BuildID& build_id_) const { 297bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) const {
298 const auto build_id_raw = Common::HexToString(build_id_); 298 const auto build_id_raw = Common::HexToString(build_id_);
299 const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); 299 const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
300 300
301 LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id); 301 LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name);
302 302
303 const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); 303 const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
304 if (load_dir == nullptr) { 304 if (load_dir == nullptr) {
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index adcde7b7d..03e9c7301 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -52,7 +52,7 @@ public:
52 52
53 // Checks to see if PatchNSO() will have any effect given the NSO's build ID. 53 // Checks to see if PatchNSO() will have any effect given the NSO's build ID.
54 // Used to prevent expensive copies in NSO loader. 54 // Used to prevent expensive copies in NSO loader.
55 [[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const; 55 [[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const;
56 56
57 // Creates a CheatList object with all 57 // Creates a CheatList object with all
58 [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList( 58 [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 703049ede..4a099286b 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -96,6 +96,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string
96 process->m_is_suspended = false; 96 process->m_is_suspended = false;
97 process->m_schedule_count = 0; 97 process->m_schedule_count = 0;
98 process->m_is_handle_table_initialized = false; 98 process->m_is_handle_table_initialized = false;
99 process->m_is_hbl = false;
99 100
100 // Open a reference to the resource limit. 101 // Open a reference to the resource limit.
101 process->m_resource_limit->Open(); 102 process->m_resource_limit->Open();
@@ -351,12 +352,14 @@ Result KProcess::SetActivity(ProcessActivity activity) {
351 R_SUCCEED(); 352 R_SUCCEED();
352} 353}
353 354
354Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size) { 355Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
356 bool is_hbl) {
355 m_program_id = metadata.GetTitleID(); 357 m_program_id = metadata.GetTitleID();
356 m_ideal_core = metadata.GetMainThreadCore(); 358 m_ideal_core = metadata.GetMainThreadCore();
357 m_is_64bit_process = metadata.Is64BitProgram(); 359 m_is_64bit_process = metadata.Is64BitProgram();
358 m_system_resource_size = metadata.GetSystemResourceSize(); 360 m_system_resource_size = metadata.GetSystemResourceSize();
359 m_image_size = code_size; 361 m_image_size = code_size;
362 m_is_hbl = is_hbl;
360 363
361 if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) { 364 if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) {
362 // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large. 365 // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large.
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h
index 4fdeaf11a..146e07a57 100644
--- a/src/core/hle/kernel/k_process.h
+++ b/src/core/hle/kernel/k_process.h
@@ -338,7 +338,8 @@ public:
338 * @returns ResultSuccess if all relevant metadata was able to be 338 * @returns ResultSuccess if all relevant metadata was able to be
339 * loaded and parsed. Otherwise, an error code is returned. 339 * loaded and parsed. Otherwise, an error code is returned.
340 */ 340 */
341 Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size); 341 Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
342 bool is_hbl);
342 343
343 /** 344 /**
344 * Starts the main application thread for this process. 345 * Starts the main application thread for this process.
@@ -368,6 +369,10 @@ public:
368 return GetProcessId(); 369 return GetProcessId();
369 } 370 }
370 371
372 bool IsHbl() const {
373 return m_is_hbl;
374 }
375
371 bool IsSignaled() const override; 376 bool IsSignaled() const override;
372 377
373 void DoWorkerTaskImpl(); 378 void DoWorkerTaskImpl();
@@ -525,6 +530,7 @@ private:
525 bool m_is_immortal{}; 530 bool m_is_immortal{};
526 bool m_is_handle_table_initialized{}; 531 bool m_is_handle_table_initialized{};
527 bool m_is_initialized{}; 532 bool m_is_initialized{};
533 bool m_is_hbl{};
528 534
529 std::atomic<u16> m_num_running_threads{}; 535 std::atomic<u16> m_num_running_threads{};
530 536
diff --git a/src/core/hle/kernel/svc/svc_debug_string.cpp b/src/core/hle/kernel/svc/svc_debug_string.cpp
index 4c14ce668..00b65429b 100644
--- a/src/core/hle/kernel/svc/svc_debug_string.cpp
+++ b/src/core/hle/kernel/svc/svc_debug_string.cpp
@@ -14,7 +14,7 @@ Result OutputDebugString(Core::System& system, u64 address, u64 len) {
14 14
15 std::string str(len, '\0'); 15 std::string str(len, '\0');
16 GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size()); 16 GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size());
17 LOG_DEBUG(Debug_Emulated, "{}", str); 17 LOG_INFO(Debug_Emulated, "{}", str);
18 18
19 R_SUCCEED(); 19 R_SUCCEED();
20} 20}
diff --git a/src/core/hle/kernel/svc/svc_exception.cpp b/src/core/hle/kernel/svc/svc_exception.cpp
index 580cf2f75..c581c086b 100644
--- a/src/core/hle/kernel/svc/svc_exception.cpp
+++ b/src/core/hle/kernel/svc/svc_exception.cpp
@@ -3,6 +3,7 @@
3 3
4#include "core/core.h" 4#include "core/core.h"
5#include "core/debugger/debugger.h" 5#include "core/debugger/debugger.h"
6#include "core/hle/kernel/k_process.h"
6#include "core/hle/kernel/k_thread.h" 7#include "core/hle/kernel/k_thread.h"
7#include "core/hle/kernel/svc.h" 8#include "core/hle/kernel/svc.h"
8#include "core/hle/kernel/svc_types.h" 9#include "core/hle/kernel/svc_types.h"
@@ -107,7 +108,10 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) {
107 system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); 108 system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace();
108 } 109 }
109 110
110 if (system.DebuggerEnabled()) { 111 const bool is_hbl = GetCurrentProcess(system.Kernel()).IsHbl();
112 const bool should_break = is_hbl || !notification_only;
113
114 if (system.DebuggerEnabled() && should_break) {
111 auto* thread = system.Kernel().GetCurrentEmuThread(); 115 auto* thread = system.Kernel().GetCurrentEmuThread();
112 system.GetDebugger().NotifyThreadStopped(thread); 116 system.GetDebugger().NotifyThreadStopped(thread);
113 thread->RequestSuspend(Kernel::SuspendType::Debug); 117 thread->RequestSuspend(Kernel::SuspendType::Debug);
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index f9c4f9678..8ffdd19e7 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -1386,7 +1386,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
1386 {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"}, 1386 {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"},
1387 {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"}, 1387 {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"},
1388 {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"}, 1388 {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"},
1389 {28, nullptr, "GetSaveDataSizeMax"}, 1389 {28, &IApplicationFunctions::GetSaveDataSizeMax, "GetSaveDataSizeMax"},
1390 {29, nullptr, "GetCacheStorageMax"}, 1390 {29, nullptr, "GetCacheStorageMax"},
1391 {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"}, 1391 {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"},
1392 {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"}, 1392 {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"},
@@ -1821,6 +1821,18 @@ void IApplicationFunctions::CreateCacheStorage(HLERequestContext& ctx) {
1821 rb.PushRaw(resp); 1821 rb.PushRaw(resp);
1822} 1822}
1823 1823
1824void IApplicationFunctions::GetSaveDataSizeMax(HLERequestContext& ctx) {
1825 LOG_WARNING(Service_AM, "(STUBBED) called");
1826
1827 constexpr u64 size_max_normal = 0xFFFFFFF;
1828 constexpr u64 size_max_journal = 0xFFFFFFF;
1829
1830 IPC::ResponseBuilder rb{ctx, 6};
1831 rb.Push(ResultSuccess);
1832 rb.Push(size_max_normal);
1833 rb.Push(size_max_journal);
1834}
1835
1824void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) { 1836void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) {
1825 LOG_WARNING(Service_AM, "(STUBBED) called"); 1837 LOG_WARNING(Service_AM, "(STUBBED) called");
1826 1838
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index f75a665b2..f86841c60 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -316,6 +316,7 @@ private:
316 void ExtendSaveData(HLERequestContext& ctx); 316 void ExtendSaveData(HLERequestContext& ctx);
317 void GetSaveDataSize(HLERequestContext& ctx); 317 void GetSaveDataSize(HLERequestContext& ctx);
318 void CreateCacheStorage(HLERequestContext& ctx); 318 void CreateCacheStorage(HLERequestContext& ctx);
319 void GetSaveDataSizeMax(HLERequestContext& ctx);
319 void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); 320 void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
320 void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); 321 void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
321 void BeginBlockingHomeButton(HLERequestContext& ctx); 322 void BeginBlockingHomeButton(HLERequestContext& ctx);
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index 5dda12343..674d2e4b2 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -874,17 +874,19 @@ Result NfcDevice::RestoreAmiibo() {
874} 874}
875 875
876Result NfcDevice::Format() { 876Result NfcDevice::Format() {
877 auto result1 = DeleteApplicationArea(); 877 Result result = ResultSuccess;
878 auto result2 = DeleteRegisterInfo();
879 878
880 if (result1.IsError()) { 879 if (device_state == DeviceState::TagFound) {
881 return result1; 880 result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All);
882 } 881 }
883 882
884 if (result2.IsError()) { 883 if (result.IsError()) {
885 return result2; 884 return result;
886 } 885 }
887 886
887 DeleteApplicationArea();
888 DeleteRegisterInfo();
889
888 return Flush(); 890 return Flush();
889} 891}
890 892
diff --git a/src/core/hle/service/ngc/ngc.cpp b/src/core/hle/service/ngc/ngc.cpp
new file mode 100644
index 000000000..c26019ec0
--- /dev/null
+++ b/src/core/hle/service/ngc/ngc.cpp
@@ -0,0 +1,150 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/string_util.h"
5#include "core/core.h"
6#include "core/hle/service/ipc_helpers.h"
7#include "core/hle/service/ngc/ngc.h"
8#include "core/hle/service/server_manager.h"
9#include "core/hle/service/service.h"
10
11namespace Service::NGC {
12
13class NgctServiceImpl final : public ServiceFramework<NgctServiceImpl> {
14public:
15 explicit NgctServiceImpl(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
16 // clang-format off
17 static const FunctionInfo functions[] = {
18 {0, &NgctServiceImpl::Match, "Match"},
19 {1, &NgctServiceImpl::Filter, "Filter"},
20 };
21 // clang-format on
22
23 RegisterHandlers(functions);
24 }
25
26private:
27 void Match(HLERequestContext& ctx) {
28 const auto buffer = ctx.ReadBuffer();
29 const auto text = Common::StringFromFixedZeroTerminatedBuffer(
30 reinterpret_cast<const char*>(buffer.data()), buffer.size());
31
32 LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text);
33
34 IPC::ResponseBuilder rb{ctx, 3};
35 rb.Push(ResultSuccess);
36 // Return false since we don't censor anything
37 rb.Push(false);
38 }
39
40 void Filter(HLERequestContext& ctx) {
41 const auto buffer = ctx.ReadBuffer();
42 const auto text = Common::StringFromFixedZeroTerminatedBuffer(
43 reinterpret_cast<const char*>(buffer.data()), buffer.size());
44
45 LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text);
46
47 // Return the same string since we don't censor anything
48 ctx.WriteBuffer(buffer);
49
50 IPC::ResponseBuilder rb{ctx, 2};
51 rb.Push(ResultSuccess);
52 }
53};
54
55class NgcServiceImpl final : public ServiceFramework<NgcServiceImpl> {
56public:
57 explicit NgcServiceImpl(Core::System& system_) : ServiceFramework(system_, "ngc:u") {
58 // clang-format off
59 static const FunctionInfo functions[] = {
60 {0, &NgcServiceImpl::GetContentVersion, "GetContentVersion"},
61 {1, &NgcServiceImpl::Check, "Check"},
62 {2, &NgcServiceImpl::Mask, "Mask"},
63 {3, &NgcServiceImpl::Reload, "Reload"},
64 };
65 // clang-format on
66
67 RegisterHandlers(functions);
68 }
69
70private:
71 static constexpr u32 NgcContentVersion = 1;
72
73 // This is nn::ngc::detail::ProfanityFilterOption
74 struct ProfanityFilterOption {
75 INSERT_PADDING_BYTES_NOINIT(0x20);
76 };
77 static_assert(sizeof(ProfanityFilterOption) == 0x20,
78 "ProfanityFilterOption has incorrect size");
79
80 void GetContentVersion(HLERequestContext& ctx) {
81 LOG_INFO(Service_NGC, "(STUBBED) called");
82
83 // This calls nn::ngc::ProfanityFilter::GetContentVersion
84 const u32 version = NgcContentVersion;
85
86 IPC::ResponseBuilder rb{ctx, 3};
87 rb.Push(ResultSuccess);
88 rb.Push(version);
89 }
90
91 void Check(HLERequestContext& ctx) {
92 LOG_INFO(Service_NGC, "(STUBBED) called");
93
94 struct InputParameters {
95 u32 flags;
96 ProfanityFilterOption option;
97 };
98
99 IPC::RequestParser rp{ctx};
100 [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>();
101 [[maybe_unused]] const auto input = ctx.ReadBuffer(0);
102
103 // This calls nn::ngc::ProfanityFilter::CheckProfanityWords
104 const u32 out_flags = 0;
105
106 IPC::ResponseBuilder rb{ctx, 3};
107 rb.Push(ResultSuccess);
108 rb.Push(out_flags);
109 }
110
111 void Mask(HLERequestContext& ctx) {
112 LOG_INFO(Service_NGC, "(STUBBED) called");
113
114 struct InputParameters {
115 u32 flags;
116 ProfanityFilterOption option;
117 };
118
119 IPC::RequestParser rp{ctx};
120 [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>();
121 const auto input = ctx.ReadBuffer(0);
122
123 // This calls nn::ngc::ProfanityFilter::MaskProfanityWordsInText
124 const u32 out_flags = 0;
125 ctx.WriteBuffer(input);
126
127 IPC::ResponseBuilder rb{ctx, 3};
128 rb.Push(ResultSuccess);
129 rb.Push(out_flags);
130 }
131
132 void Reload(HLERequestContext& ctx) {
133 LOG_INFO(Service_NGC, "(STUBBED) called");
134
135 // This reloads the database.
136
137 IPC::ResponseBuilder rb{ctx, 2};
138 rb.Push(ResultSuccess);
139 }
140};
141
142void LoopProcess(Core::System& system) {
143 auto server_manager = std::make_unique<ServerManager>(system);
144
145 server_manager->RegisterNamedService("ngct:u", std::make_shared<NgctServiceImpl>(system));
146 server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system));
147 ServerManager::RunServer(std::move(server_manager));
148}
149
150} // namespace Service::NGC
diff --git a/src/core/hle/service/ngct/ngct.h b/src/core/hle/service/ngc/ngc.h
index 27c34dad4..823b1aa81 100644
--- a/src/core/hle/service/ngct/ngct.h
+++ b/src/core/hle/service/ngc/ngc.h
@@ -7,8 +7,8 @@ namespace Core {
7class System; 7class System;
8} 8}
9 9
10namespace Service::NGCT { 10namespace Service::NGC {
11 11
12void LoopProcess(Core::System& system); 12void LoopProcess(Core::System& system);
13 13
14} // namespace Service::NGCT 14} // namespace Service::NGC
diff --git a/src/core/hle/service/ngct/ngct.cpp b/src/core/hle/service/ngct/ngct.cpp
deleted file mode 100644
index 493c80ed2..000000000
--- a/src/core/hle/service/ngct/ngct.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/string_util.h"
5#include "core/core.h"
6#include "core/hle/service/ipc_helpers.h"
7#include "core/hle/service/ngct/ngct.h"
8#include "core/hle/service/server_manager.h"
9#include "core/hle/service/service.h"
10
11namespace Service::NGCT {
12
13class IService final : public ServiceFramework<IService> {
14public:
15 explicit IService(Core::System& system_) : ServiceFramework{system_, "ngct:u"} {
16 // clang-format off
17 static const FunctionInfo functions[] = {
18 {0, &IService::Match, "Match"},
19 {1, &IService::Filter, "Filter"},
20 };
21 // clang-format on
22
23 RegisterHandlers(functions);
24 }
25
26private:
27 void Match(HLERequestContext& ctx) {
28 const auto buffer = ctx.ReadBuffer();
29 const auto text = Common::StringFromFixedZeroTerminatedBuffer(
30 reinterpret_cast<const char*>(buffer.data()), buffer.size());
31
32 LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
33
34 IPC::ResponseBuilder rb{ctx, 3};
35 rb.Push(ResultSuccess);
36 // Return false since we don't censor anything
37 rb.Push(false);
38 }
39
40 void Filter(HLERequestContext& ctx) {
41 const auto buffer = ctx.ReadBuffer();
42 const auto text = Common::StringFromFixedZeroTerminatedBuffer(
43 reinterpret_cast<const char*>(buffer.data()), buffer.size());
44
45 LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text);
46
47 // Return the same string since we don't censor anything
48 ctx.WriteBuffer(buffer);
49
50 IPC::ResponseBuilder rb{ctx, 2};
51 rb.Push(ResultSuccess);
52 }
53};
54
55void LoopProcess(Core::System& system) {
56 auto server_manager = std::make_unique<ServerManager>(system);
57
58 server_manager->RegisterNamedService("ngct:u", std::make_shared<IService>(system));
59 ServerManager::RunServer(std::move(server_manager));
60}
61
62} // namespace Service::NGCT
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 69cdb5918..0ad607391 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -43,7 +43,7 @@
43#include "core/hle/service/ncm/ncm.h" 43#include "core/hle/service/ncm/ncm.h"
44#include "core/hle/service/nfc/nfc.h" 44#include "core/hle/service/nfc/nfc.h"
45#include "core/hle/service/nfp/nfp.h" 45#include "core/hle/service/nfp/nfp.h"
46#include "core/hle/service/ngct/ngct.h" 46#include "core/hle/service/ngc/ngc.h"
47#include "core/hle/service/nifm/nifm.h" 47#include "core/hle/service/nifm/nifm.h"
48#include "core/hle/service/nim/nim.h" 48#include "core/hle/service/nim/nim.h"
49#include "core/hle/service/npns/npns.h" 49#include "core/hle/service/npns/npns.h"
@@ -257,7 +257,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
257 kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); }); 257 kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); });
258 kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); }); 258 kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); });
259 kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); }); 259 kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); });
260 kernel.RunOnGuestCoreProcess("ngct", [&] { NGCT::LoopProcess(system); }); 260 kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); });
261 kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); }); 261 kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); });
262 kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); }); 262 kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); });
263 kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); }); 263 kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); });
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index d8509c1dd..85849d5f3 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -170,7 +170,7 @@ void BSD::Socket(HLERequestContext& ctx) {
170} 170}
171 171
172void BSD::Select(HLERequestContext& ctx) { 172void BSD::Select(HLERequestContext& ctx) {
173 LOG_WARNING(Service, "(STUBBED) called"); 173 LOG_DEBUG(Service, "(STUBBED) called");
174 174
175 IPC::ResponseBuilder rb{ctx, 4}; 175 IPC::ResponseBuilder rb{ctx, 4};
176 176
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index f4eaf3331..5a42dea48 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -18,7 +18,7 @@ namespace Loader {
18 18
19AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, 19AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_,
20 bool override_update_) 20 bool override_update_)
21 : AppLoader(std::move(file_)), override_update(override_update_) { 21 : AppLoader(std::move(file_)), override_update(override_update_), is_hbl(false) {
22 const auto file_dir = file->GetContainingDirectory(); 22 const auto file_dir = file->GetContainingDirectory();
23 23
24 // Title ID 24 // Title ID
@@ -69,9 +69,9 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys
69} 69}
70 70
71AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( 71AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(
72 FileSys::VirtualDir directory, bool override_update_) 72 FileSys::VirtualDir directory, bool override_update_, bool is_hbl_)
73 : AppLoader(directory->GetFile("main")), dir(std::move(directory)), 73 : AppLoader(directory->GetFile("main")), dir(std::move(directory)),
74 override_update(override_update_) {} 74 override_update(override_update_), is_hbl(is_hbl_) {}
75 75
76FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) { 76FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) {
77 if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) { 77 if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) {
@@ -147,7 +147,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
147 } 147 }
148 148
149 // Setup the process code layout 149 // Setup the process code layout
150 if (process.LoadFromMetadata(metadata, code_size).IsError()) { 150 if (process.LoadFromMetadata(metadata, code_size, is_hbl).IsError()) {
151 return {ResultStatus::ErrorUnableToParseKernelMetadata, {}}; 151 return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
152 } 152 }
153 153
diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h
index f7702225e..1e9f765c9 100644
--- a/src/core/loader/deconstructed_rom_directory.h
+++ b/src/core/loader/deconstructed_rom_directory.h
@@ -27,7 +27,8 @@ public:
27 27
28 // Overload to accept exefs directory. Must contain 'main' and 'main.npdm' 28 // Overload to accept exefs directory. Must contain 'main' and 'main.npdm'
29 explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory, 29 explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory,
30 bool override_update_ = false); 30 bool override_update_ = false,
31 bool is_hbl_ = false);
31 32
32 /** 33 /**
33 * Identifies whether or not the given file is a deconstructed ROM directory. 34 * Identifies whether or not the given file is a deconstructed ROM directory.
@@ -62,6 +63,7 @@ private:
62 std::string name; 63 std::string name;
63 u64 title_id{}; 64 u64 title_id{};
64 bool override_update; 65 bool override_update;
66 bool is_hbl;
65 67
66 Modules modules; 68 Modules modules;
67}; 69};
diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp
index d722459c6..bf56a08b4 100644
--- a/src/core/loader/kip.cpp
+++ b/src/core/loader/kip.cpp
@@ -90,7 +90,8 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process,
90 codeset.DataSegment().size += kip->GetBSSSize(); 90 codeset.DataSegment().size += kip->GetBSSSize();
91 91
92 // Setup the process code layout 92 // Setup the process code layout
93 if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size()) 93 if (process
94 .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false)
94 .IsError()) { 95 .IsError()) {
95 return {ResultStatus::ErrorNotInitialized, {}}; 96 return {ResultStatus::ErrorNotInitialized, {}};
96 } 97 }
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index d7562b4bc..69f1a54ed 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -196,7 +196,8 @@ static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data)
196 program_image.resize(static_cast<u32>(program_image.size()) + bss_size); 196 program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
197 197
198 // Setup the process code layout 198 // Setup the process code layout
199 if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size()) 199 if (process
200 .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false)
200 .IsError()) { 201 .IsError()) {
201 return false; 202 return false;
202 } 203 }
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 549822506..1350da8dc 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -127,13 +127,14 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
127 } 127 }
128 128
129 // Apply patches if necessary 129 // Apply patches if necessary
130 if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) { 130 const auto name = nso_file.GetName();
131 if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) {
131 std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size()); 132 std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size());
132 std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader)); 133 std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader));
133 std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(), 134 std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(),
134 program_image.size()); 135 program_image.size());
135 136
136 pi_header = pm->PatchNSO(pi_header, nso_file.GetName()); 137 pi_header = pm->PatchNSO(pi_header, name);
137 138
138 std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data()); 139 std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data());
139 } 140 }
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index fe2af1ae6..f4ab75b77 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -30,7 +30,8 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file_,
30 } 30 }
31 31
32 if (nsp->IsExtractedType()) { 32 if (nsp->IsExtractedType()) {
33 secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS()); 33 secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(
34 nsp->GetExeFS(), false, file->GetName() == "hbl.nsp");
34 } else { 35 } else {
35 const auto control_nca = 36 const auto control_nca =
36 nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); 37 nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control);
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index 7b52f61a7..a06e99166 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.cpp
@@ -154,7 +154,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
154 return {}; 154 return {};
155 } 155 }
156 156
157 const auto value = static_cast<u32>(std::stoul(hex, nullptr, 0x10)); 157 const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
158 out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = 158 out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
159 value; 159 value;
160 160
diff --git a/src/core/tools/renderdoc.cpp b/src/core/tools/renderdoc.cpp
new file mode 100644
index 000000000..44d24822a
--- /dev/null
+++ b/src/core/tools/renderdoc.cpp
@@ -0,0 +1,55 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <renderdoc_app.h>
5
6#include "common/assert.h"
7#include "common/dynamic_library.h"
8#include "core/tools/renderdoc.h"
9
10#ifdef WIN32
11#include <windows.h>
12#else
13#include <dlfcn.h>
14#endif
15
16namespace Tools {
17
18RenderdocAPI::RenderdocAPI() {
19#ifdef WIN32
20 if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) {
21 const auto RENDERDOC_GetAPI =
22 reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(mod, "RENDERDOC_GetAPI"));
23 const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api);
24 ASSERT(ret == 1);
25 }
26#else
27#ifdef ANDROID
28 static constexpr const char RENDERDOC_LIB[] = "libVkLayer_GLES_RenderDoc.so";
29#else
30 static constexpr const char RENDERDOC_LIB[] = "librenderdoc.so";
31#endif
32 if (void* mod = dlopen(RENDERDOC_LIB, RTLD_NOW | RTLD_NOLOAD)) {
33 const auto RENDERDOC_GetAPI =
34 reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI"));
35 const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api);
36 ASSERT(ret == 1);
37 }
38#endif
39}
40
41RenderdocAPI::~RenderdocAPI() = default;
42
43void RenderdocAPI::ToggleCapture() {
44 if (!rdoc_api) [[unlikely]] {
45 return;
46 }
47 if (!is_capturing) {
48 rdoc_api->StartFrameCapture(NULL, NULL);
49 } else {
50 rdoc_api->EndFrameCapture(NULL, NULL);
51 }
52 is_capturing = !is_capturing;
53}
54
55} // namespace Tools
diff --git a/src/core/tools/renderdoc.h b/src/core/tools/renderdoc.h
new file mode 100644
index 000000000..0e5e43da5
--- /dev/null
+++ b/src/core/tools/renderdoc.h
@@ -0,0 +1,22 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6struct RENDERDOC_API_1_6_0;
7
8namespace Tools {
9
10class RenderdocAPI {
11public:
12 explicit RenderdocAPI();
13 ~RenderdocAPI();
14
15 void ToggleCapture();
16
17private:
18 RENDERDOC_API_1_6_0* rdoc_api{};
19 bool is_capturing{false};
20};
21
22} // namespace Tools
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
index 34240b36f..8decdf399 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp
@@ -204,9 +204,7 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind
204 if (def.count > 1) { 204 if (def.count > 1) {
205 throw NotImplementedException("Indirect texture sample"); 205 throw NotImplementedException("Indirect texture sample");
206 } 206 }
207 const Id sampler_id{def.id}; 207 return ctx.OpLoad(ctx.image_buffer_type, def.id);
208 const Id id{ctx.OpLoad(ctx.sampled_texture_buffer_type, sampler_id)};
209 return ctx.OpImage(ctx.image_buffer_type, id);
210 } else { 208 } else {
211 const TextureDefinition& def{ctx.textures.at(info.descriptor_index)}; 209 const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
212 if (def.count > 1) { 210 if (def.count > 1) {
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index 238fb40e3..72f69b7aa 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -1247,9 +1247,8 @@ void EmitContext::DefineTextureBuffers(const Info& info, u32& binding) {
1247 } 1247 }
1248 const spv::ImageFormat format{spv::ImageFormat::Unknown}; 1248 const spv::ImageFormat format{spv::ImageFormat::Unknown};
1249 image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format); 1249 image_buffer_type = TypeImage(F32[1], spv::Dim::Buffer, 0U, false, false, 1, format);
1250 sampled_texture_buffer_type = TypeSampledImage(image_buffer_type);
1251 1250
1252 const Id type{TypePointer(spv::StorageClass::UniformConstant, sampled_texture_buffer_type)}; 1251 const Id type{TypePointer(spv::StorageClass::UniformConstant, image_buffer_type)};
1253 texture_buffers.reserve(info.texture_buffer_descriptors.size()); 1252 texture_buffers.reserve(info.texture_buffer_descriptors.size());
1254 for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) { 1253 for (const TextureBufferDescriptor& desc : info.texture_buffer_descriptors) {
1255 if (desc.count != 1) { 1254 if (desc.count != 1) {
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
index e63330f11..7c49fd504 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
@@ -206,7 +206,6 @@ public:
206 Id output_u32{}; 206 Id output_u32{};
207 207
208 Id image_buffer_type{}; 208 Id image_buffer_type{};
209 Id sampled_texture_buffer_type{};
210 Id image_u32{}; 209 Id image_u32{};
211 210
212 std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{}; 211 std::array<UniformDefinitions, Info::MAX_CBUFS> cbufs{};
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 4457b366f..1bdb0def5 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -719,6 +719,7 @@ typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_ad
719 return nullptr; 719 return nullptr;
720 } 720 }
721 const auto& image_map_ids = it->second; 721 const auto& image_map_ids = it->second;
722 boost::container::small_vector<const ImageBase*, 4> valid_images;
722 for (const ImageMapId map_id : image_map_ids) { 723 for (const ImageMapId map_id : image_map_ids) {
723 const ImageMapView& map = slot_map_views[map_id]; 724 const ImageMapView& map = slot_map_views[map_id];
724 const ImageBase& image = slot_images[map.image_id]; 725 const ImageBase& image = slot_images[map.image_id];
@@ -728,8 +729,20 @@ typename P::ImageView* TextureCache<P>::TryFindFramebufferImageView(VAddr cpu_ad
728 if (image.image_view_ids.empty()) { 729 if (image.image_view_ids.empty()) {
729 continue; 730 continue;
730 } 731 }
731 return &slot_image_views[image.image_view_ids.at(0)]; 732 valid_images.push_back(&image);
732 } 733 }
734
735 if (valid_images.size() == 1) [[likely]] {
736 return &slot_image_views[valid_images[0]->image_view_ids.at(0)];
737 }
738
739 if (valid_images.size() > 0) [[unlikely]] {
740 std::ranges::sort(valid_images, [](const auto* a, const auto* b) {
741 return a->modification_tick > b->modification_tick;
742 });
743 return &slot_image_views[valid_images[0]->image_view_ids.at(0)];
744 }
745
733 return nullptr; 746 return nullptr;
734} 747}
735 748
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index cbeb8f168..b22fda746 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -59,6 +59,8 @@ void ConfigureDebug::SetConfiguration() {
59 ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue()); 59 ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts.GetValue());
60 ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue()); 60 ui->use_auto_stub->setChecked(Settings::values.use_auto_stub.GetValue());
61 ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue()); 61 ui->enable_all_controllers->setChecked(Settings::values.enable_all_controllers.GetValue());
62 ui->enable_renderdoc_hotkey->setEnabled(runtime_lock);
63 ui->enable_renderdoc_hotkey->setChecked(Settings::values.enable_renderdoc_hotkey.GetValue());
62 ui->enable_graphics_debugging->setEnabled(runtime_lock); 64 ui->enable_graphics_debugging->setEnabled(runtime_lock);
63 ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue()); 65 ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug.GetValue());
64 ui->enable_shader_feedback->setEnabled(runtime_lock); 66 ui->enable_shader_feedback->setEnabled(runtime_lock);
@@ -111,6 +113,7 @@ void ConfigureDebug::ApplyConfiguration() {
111 Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); 113 Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
112 Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked(); 114 Settings::values.enable_all_controllers = ui->enable_all_controllers->isChecked();
113 Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked(); 115 Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked();
116 Settings::values.enable_renderdoc_hotkey = ui->enable_renderdoc_hotkey->isChecked();
114 Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked(); 117 Settings::values.renderer_shader_feedback = ui->enable_shader_feedback->isChecked();
115 Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked(); 118 Settings::values.cpu_debug_mode = ui->enable_cpu_debugging->isChecked();
116 Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked(); 119 Settings::values.enable_nsight_aftermath = ui->enable_nsight_aftermath->isChecked();
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 97c7d9022..66b8b7459 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -18,8 +18,8 @@
18 <rect> 18 <rect>
19 <x>0</x> 19 <x>0</x>
20 <y>0</y> 20 <y>0</y>
21 <width>829</width> 21 <width>842</width>
22 <height>758</height> 22 <height>741</height>
23 </rect> 23 </rect>
24 </property> 24 </property>
25 <layout class="QVBoxLayout" name="verticalLayout_1"> 25 <layout class="QVBoxLayout" name="verticalLayout_1">
@@ -260,7 +260,7 @@
260 <string>Graphics</string> 260 <string>Graphics</string>
261 </property> 261 </property>
262 <layout class="QGridLayout" name="gridLayout_2"> 262 <layout class="QGridLayout" name="gridLayout_2">
263 <item row="3" column="0"> 263 <item row="4" column="0">
264 <widget class="QCheckBox" name="disable_loop_safety_checks"> 264 <widget class="QCheckBox" name="disable_loop_safety_checks">
265 <property name="toolTip"> 265 <property name="toolTip">
266 <string>When checked, it executes shaders without loop logic changes</string> 266 <string>When checked, it executes shaders without loop logic changes</string>
@@ -270,33 +270,53 @@
270 </property> 270 </property>
271 </widget> 271 </widget>
272 </item> 272 </item>
273 <item row="4" column="0"> 273 <item row="8" column="0">
274 <widget class="QCheckBox" name="dump_shaders"> 274 <widget class="QCheckBox" name="disable_macro_hle">
275 <property name="enabled"> 275 <property name="enabled">
276 <bool>true</bool> 276 <bool>true</bool>
277 </property> 277 </property>
278 <property name="toolTip"> 278 <property name="toolTip">
279 <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string> 279 <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string>
280 </property> 280 </property>
281 <property name="text"> 281 <property name="text">
282 <string>Dump Game Shaders</string> 282 <string>Disable Macro HLE</string>
283 </property> 283 </property>
284 </widget> 284 </widget>
285 </item> 285 </item>
286 <item row="7" column="0"> 286 <item row="7" column="0">
287 <widget class="QCheckBox" name="disable_macro_hle"> 287 <widget class="QCheckBox" name="dump_macros">
288 <property name="enabled"> 288 <property name="enabled">
289 <bool>true</bool> 289 <bool>true</bool>
290 </property> 290 </property>
291 <property name="toolTip"> 291 <property name="toolTip">
292 <string>When checked, it disables the macro HLE functions. Enabling this makes games run slower</string> 292 <string>When checked, it will dump all the macro programs of the GPU</string>
293 </property> 293 </property>
294 <property name="text"> 294 <property name="text">
295 <string>Disable Macro HLE</string> 295 <string>Dump Maxwell Macros</string>
296 </property> 296 </property>
297 </widget> 297 </widget>
298 </item> 298 </item>
299 <item row="5" column="0"> 299 <item row="3" column="0">
300 <widget class="QCheckBox" name="enable_nsight_aftermath">
301 <property name="toolTip">
302 <string>When checked, it enables Nsight Aftermath crash dumps</string>
303 </property>
304 <property name="text">
305 <string>Enable Nsight Aftermath</string>
306 </property>
307 </widget>
308 </item>
309 <item row="2" column="0">
310 <widget class="QCheckBox" name="enable_shader_feedback">
311 <property name="toolTip">
312 <string>When checked, yuzu will log statistics about the compiled pipeline cache</string>
313 </property>
314 <property name="text">
315 <string>Enable Shader Feedback</string>
316 </property>
317 </widget>
318 </item>
319 <item row="6" column="0">
300 <widget class="QCheckBox" name="disable_macro_jit"> 320 <widget class="QCheckBox" name="disable_macro_jit">
301 <property name="enabled"> 321 <property name="enabled">
302 <bool>true</bool> 322 <bool>true</bool>
@@ -309,6 +329,22 @@
309 </property> 329 </property>
310 </widget> 330 </widget>
311 </item> 331 </item>
332 <item row="9" column="0">
333 <spacer name="verticalSpacer_5">
334 <property name="orientation">
335 <enum>Qt::Vertical</enum>
336 </property>
337 <property name="sizeType">
338 <enum>QSizePolicy::Preferred</enum>
339 </property>
340 <property name="sizeHint" stdset="0">
341 <size>
342 <width>20</width>
343 <height>0</height>
344 </size>
345 </property>
346 </spacer>
347 </item>
312 <item row="0" column="0"> 348 <item row="0" column="0">
313 <widget class="QCheckBox" name="enable_graphics_debugging"> 349 <widget class="QCheckBox" name="enable_graphics_debugging">
314 <property name="enabled"> 350 <property name="enabled">
@@ -322,55 +358,26 @@
322 </property> 358 </property>
323 </widget> 359 </widget>
324 </item> 360 </item>
325 <item row="6" column="0"> 361 <item row="5" column="0">
326 <widget class="QCheckBox" name="dump_macros"> 362 <widget class="QCheckBox" name="dump_shaders">
327 <property name="enabled"> 363 <property name="enabled">
328 <bool>true</bool> 364 <bool>true</bool>
329 </property> 365 </property>
330 <property name="toolTip"> 366 <property name="toolTip">
331 <string>When checked, it will dump all the macro programs of the GPU</string> 367 <string>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</string>
332 </property> 368 </property>
333 <property name="text"> 369 <property name="text">
334 <string>Dump Maxwell Macros</string> 370 <string>Dump Game Shaders</string>
335 </property> 371 </property>
336 </widget> 372 </widget>
337 </item> 373 </item>
338 <item row="1" column="0"> 374 <item row="1" column="0">
339 <widget class="QCheckBox" name="enable_shader_feedback"> 375 <widget class="QCheckBox" name="enable_renderdoc_hotkey">
340 <property name="toolTip">
341 <string>When checked, yuzu will log statistics about the compiled pipeline cache</string>
342 </property>
343 <property name="text">
344 <string>Enable Shader Feedback</string>
345 </property>
346 </widget>
347 </item>
348 <item row="2" column="0">
349 <widget class="QCheckBox" name="enable_nsight_aftermath">
350 <property name="toolTip">
351 <string>When checked, it enables Nsight Aftermath crash dumps</string>
352 </property>
353 <property name="text"> 376 <property name="text">
354 <string>Enable Nsight Aftermath</string> 377 <string>Enable Renderdoc Hotkey</string>
355 </property> 378 </property>
356 </widget> 379 </widget>
357 </item> 380 </item>
358 <item row="8" column="0">
359 <spacer name="verticalSpacer_5">
360 <property name="orientation">
361 <enum>Qt::Vertical</enum>
362 </property>
363 <property name="sizeType">
364 <enum>QSizePolicy::Preferred</enum>
365 </property>
366 <property name="sizeHint" stdset="0">
367 <size>
368 <width>20</width>
369 <height>0</height>
370 </size>
371 </property>
372 </spacer>
373 </item>
374 </layout> 381 </layout>
375 </widget> 382 </widget>
376 </item> 383 </item>
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 34ab01617..a9fde9f4f 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -4,6 +4,7 @@
4#include "yuzu/configuration/configure_ui.h" 4#include "yuzu/configuration/configure_ui.h"
5 5
6#include <array> 6#include <array>
7#include <cstdlib>
7#include <set> 8#include <set>
8#include <stdexcept> 9#include <stdexcept>
9#include <string> 10#include <string>
@@ -94,11 +95,7 @@ static void PopulateResolutionComboBox(QComboBox* screenshot_height, QWidget* pa
94} 95}
95 96
96static u32 ScreenshotDimensionToInt(const QString& height) { 97static u32 ScreenshotDimensionToInt(const QString& height) {
97 try { 98 return std::strtoul(height.toUtf8(), nullptr, 0);
98 return std::stoi(height.toStdString());
99 } catch (std::invalid_argument&) {
100 return 0;
101 }
102} 99}
103 100
104ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent) 101ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
diff --git a/src/yuzu/configuration/shared_widget.cpp b/src/yuzu/configuration/shared_widget.cpp
index d63093985..ea8d7add4 100644
--- a/src/yuzu/configuration/shared_widget.cpp
+++ b/src/yuzu/configuration/shared_widget.cpp
@@ -63,7 +63,7 @@ static QString DefaultSuffix(QWidget* parent, Settings::BasicSetting& setting) {
63 return tr("%", context.c_str()); 63 return tr("%", context.c_str());
64 } 64 }
65 65
66 return QStringLiteral(""); 66 return default_suffix;
67} 67}
68 68
69QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) { 69QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* parent) {
@@ -71,7 +71,7 @@ QPushButton* Widget::CreateRestoreGlobalButton(bool using_global, QWidget* paren
71 71
72 QStyle* style = parent->style(); 72 QStyle* style = parent->style();
73 QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton)); 73 QIcon* icon = new QIcon(style->standardIcon(QStyle::SP_LineEditClearButton));
74 QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(""), parent); 74 QPushButton* restore_button = new QPushButton(*icon, QStringLiteral(), parent);
75 restore_button->setObjectName(QStringLiteral("RestoreButton%1").arg(restore_button_count)); 75 restore_button->setObjectName(QStringLiteral("RestoreButton%1").arg(restore_button_count));
76 restore_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 76 restore_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
77 77
@@ -151,7 +151,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
151 return -1; 151 return -1;
152 }; 152 };
153 153
154 const u32 setting_value = std::stoi(setting.ToString()); 154 const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
155 combobox->setCurrentIndex(find_index(setting_value)); 155 combobox->setCurrentIndex(find_index(setting_value));
156 156
157 serializer = [this, enumeration]() { 157 serializer = [this, enumeration]() {
@@ -160,7 +160,7 @@ QWidget* Widget::CreateCombobox(std::function<std::string()>& serializer,
160 }; 160 };
161 161
162 restore_func = [this, find_index]() { 162 restore_func = [this, find_index]() {
163 const u32 global_value = std::stoi(RelevantDefault(setting)); 163 const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0);
164 combobox->setCurrentIndex(find_index(global_value)); 164 combobox->setCurrentIndex(find_index(global_value));
165 }; 165 };
166 166
@@ -209,7 +209,7 @@ QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer,
209 } 209 }
210 }; 210 };
211 211
212 const u32 setting_value = std::stoi(setting.ToString()); 212 const u32 setting_value = std::strtoul(setting.ToString().c_str(), nullptr, 0);
213 set_index(setting_value); 213 set_index(setting_value);
214 214
215 serializer = [get_selected]() { 215 serializer = [get_selected]() {
@@ -218,7 +218,7 @@ QWidget* Widget::CreateRadioGroup(std::function<std::string()>& serializer,
218 }; 218 };
219 219
220 restore_func = [this, set_index]() { 220 restore_func = [this, set_index]() {
221 const u32 global_value = std::stoi(RelevantDefault(setting)); 221 const u32 global_value = std::strtoul(RelevantDefault(setting).c_str(), nullptr, 0);
222 set_index(global_value); 222 set_index(global_value);
223 }; 223 };
224 224
@@ -255,6 +255,59 @@ QWidget* Widget::CreateLineEdit(std::function<std::string()>& serializer,
255 return line_edit; 255 return line_edit;
256} 256}
257 257
258static void CreateIntSlider(Settings::BasicSetting& setting, bool reversed, float multiplier,
259 QLabel* feedback, const QString& use_format, QSlider* slider,
260 std::function<std::string()>& serializer,
261 std::function<void()>& restore_func) {
262 const int max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0);
263
264 const auto update_feedback = [=](int value) {
265 int present = (reversed ? max_val - value : value) * multiplier + 0.5f;
266 feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>()));
267 };
268
269 QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback);
270 update_feedback(std::strtol(setting.ToString().c_str(), nullptr, 0));
271
272 slider->setMinimum(std::strtol(setting.MinVal().c_str(), nullptr, 0));
273 slider->setMaximum(max_val);
274 slider->setValue(std::strtol(setting.ToString().c_str(), nullptr, 0));
275
276 serializer = [slider]() { return std::to_string(slider->value()); };
277 restore_func = [slider, &setting]() {
278 slider->setValue(std::strtol(RelevantDefault(setting).c_str(), nullptr, 0));
279 };
280}
281
282static void CreateFloatSlider(Settings::BasicSetting& setting, bool reversed, float multiplier,
283 QLabel* feedback, const QString& use_format, QSlider* slider,
284 std::function<std::string()>& serializer,
285 std::function<void()>& restore_func) {
286 const float max_val = std::strtof(setting.MaxVal().c_str(), nullptr);
287 const float min_val = std::strtof(setting.MinVal().c_str(), nullptr);
288 const float use_multiplier =
289 multiplier == default_multiplier ? default_float_multiplier : multiplier;
290
291 const auto update_feedback = [=](float value) {
292 int present = (reversed ? max_val - value : value) + 0.5f;
293 feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>()));
294 };
295
296 QObject::connect(slider, &QAbstractSlider::valueChanged, update_feedback);
297 update_feedback(std::strtof(setting.ToString().c_str(), nullptr));
298
299 slider->setMinimum(min_val * use_multiplier);
300 slider->setMaximum(max_val * use_multiplier);
301 slider->setValue(std::strtof(setting.ToString().c_str(), nullptr) * use_multiplier);
302
303 serializer = [slider, use_multiplier]() {
304 return std::to_string(slider->value() / use_multiplier);
305 };
306 restore_func = [slider, &setting, use_multiplier]() {
307 slider->setValue(std::strtof(RelevantDefault(setting).c_str(), nullptr) * use_multiplier);
308 };
309}
310
258QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& given_suffix, 311QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& given_suffix,
259 std::function<std::string()>& serializer, 312 std::function<std::string()>& serializer,
260 std::function<void()>& restore_func, 313 std::function<void()>& restore_func,
@@ -278,27 +331,19 @@ QWidget* Widget::CreateSlider(bool reversed, float multiplier, const QString& gi
278 331
279 layout->setContentsMargins(0, 0, 0, 0); 332 layout->setContentsMargins(0, 0, 0, 0);
280 333
281 int max_val = std::stoi(setting.MaxVal()); 334 QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
282
283 QString suffix =
284 given_suffix == QStringLiteral("") ? DefaultSuffix(this, setting) : given_suffix;
285 335
286 const QString use_format = QStringLiteral("%1").append(suffix); 336 const QString use_format = QStringLiteral("%1").append(suffix);
287 337
288 QObject::connect(slider, &QAbstractSlider::valueChanged, [=](int value) { 338 if (setting.IsIntegral()) {
289 int present = (reversed ? max_val - value : value) * multiplier + 0.5f; 339 CreateIntSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer,
290 feedback->setText(use_format.arg(QVariant::fromValue(present).value<QString>())); 340 restore_func);
291 }); 341 } else {
292 342 CreateFloatSlider(setting, reversed, multiplier, feedback, use_format, slider, serializer,
293 slider->setMinimum(std::stoi(setting.MinVal())); 343 restore_func);
294 slider->setMaximum(max_val); 344 }
295 slider->setValue(std::stoi(setting.ToString()));
296 345
297 slider->setInvertedAppearance(reversed); 346 slider->setInvertedAppearance(reversed);
298 slider->setInvertedControls(reversed);
299
300 serializer = [this]() { return std::to_string(slider->value()); };
301 restore_func = [this]() { slider->setValue(std::stoi(RelevantDefault(setting))); };
302 347
303 if (!Settings::IsConfiguringGlobal()) { 348 if (!Settings::IsConfiguringGlobal()) {
304 QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); }); 349 QObject::connect(slider, &QAbstractSlider::actionTriggered, [touch]() { touch(); });
@@ -311,14 +356,11 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
311 std::function<std::string()>& serializer, 356 std::function<std::string()>& serializer,
312 std::function<void()>& restore_func, 357 std::function<void()>& restore_func,
313 const std::function<void()>& touch) { 358 const std::function<void()>& touch) {
314 const int min_val = 359 const auto min_val = std::strtol(setting.MinVal().c_str(), nullptr, 0);
315 setting.Ranged() ? std::stoi(setting.MinVal()) : std::numeric_limits<int>::min(); 360 const auto max_val = std::strtol(setting.MaxVal().c_str(), nullptr, 0);
316 const int max_val = 361 const auto default_val = std::strtol(setting.ToString().c_str(), nullptr, 0);
317 setting.Ranged() ? std::stoi(setting.MaxVal()) : std::numeric_limits<int>::max();
318 const int default_val = std::stoi(setting.ToString());
319 362
320 QString suffix = 363 QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
321 given_suffix == QStringLiteral("") ? DefaultSuffix(this, setting) : given_suffix;
322 364
323 spinbox = new QSpinBox(this); 365 spinbox = new QSpinBox(this);
324 spinbox->setRange(min_val, max_val); 366 spinbox->setRange(min_val, max_val);
@@ -329,13 +371,13 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
329 serializer = [this]() { return std::to_string(spinbox->value()); }; 371 serializer = [this]() { return std::to_string(spinbox->value()); };
330 372
331 restore_func = [this]() { 373 restore_func = [this]() {
332 auto value{std::stol(RelevantDefault(setting))}; 374 auto value{std::strtol(RelevantDefault(setting).c_str(), nullptr, 0)};
333 spinbox->setValue(value); 375 spinbox->setValue(value);
334 }; 376 };
335 377
336 if (!Settings::IsConfiguringGlobal()) { 378 if (!Settings::IsConfiguringGlobal()) {
337 QObject::connect(spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this, touch]() { 379 QObject::connect(spinbox, QOverload<int>::of(&QSpinBox::valueChanged), [this, touch]() {
338 if (spinbox->value() != std::stoi(setting.ToStringGlobal())) { 380 if (spinbox->value() != std::strtol(setting.ToStringGlobal().c_str(), nullptr, 0)) {
339 touch(); 381 touch();
340 } 382 }
341 }); 383 });
@@ -344,6 +386,42 @@ QWidget* Widget::CreateSpinBox(const QString& given_suffix,
344 return spinbox; 386 return spinbox;
345} 387}
346 388
389QWidget* Widget::CreateDoubleSpinBox(const QString& given_suffix,
390 std::function<std::string()>& serializer,
391 std::function<void()>& restore_func,
392 const std::function<void()>& touch) {
393 const auto min_val = std::strtod(setting.MinVal().c_str(), nullptr);
394 const auto max_val = std::strtod(setting.MaxVal().c_str(), nullptr);
395 const auto default_val = std::strtod(setting.ToString().c_str(), nullptr);
396
397 QString suffix = given_suffix == default_suffix ? DefaultSuffix(this, setting) : given_suffix;
398
399 double_spinbox = new QDoubleSpinBox(this);
400 double_spinbox->setRange(min_val, max_val);
401 double_spinbox->setValue(default_val);
402 double_spinbox->setSuffix(suffix);
403 double_spinbox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
404
405 serializer = [this]() { return fmt::format("{:f}", double_spinbox->value()); };
406
407 restore_func = [this]() {
408 auto value{std::strtod(RelevantDefault(setting).c_str(), nullptr)};
409 double_spinbox->setValue(value);
410 };
411
412 if (!Settings::IsConfiguringGlobal()) {
413 QObject::connect(double_spinbox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
414 [this, touch]() {
415 if (double_spinbox->value() !=
416 std::strtod(setting.ToStringGlobal().c_str(), nullptr)) {
417 touch();
418 }
419 });
420 }
421
422 return double_spinbox;
423}
424
347QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer, 425QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
348 std::function<void()>& restore_func, 426 std::function<void()>& restore_func,
349 const std::function<void()>& touch) { 427 const std::function<void()>& touch) {
@@ -353,7 +431,8 @@ QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
353 } 431 }
354 432
355 auto to_hex = [=](const std::string& input) { 433 auto to_hex = [=](const std::string& input) {
356 return QString::fromStdString(fmt::format("{:08x}", std::stoul(input))); 434 return QString::fromStdString(
435 fmt::format("{:08x}", std::strtoul(input.c_str(), nullptr, 0)));
357 }; 436 };
358 437
359 QRegularExpressionValidator* regex = new QRegularExpressionValidator( 438 QRegularExpressionValidator* regex = new QRegularExpressionValidator(
@@ -366,7 +445,7 @@ QWidget* Widget::CreateHexEdit(std::function<std::string()>& serializer,
366 line_edit->setValidator(regex); 445 line_edit->setValidator(regex);
367 446
368 auto hex_to_dec = [this]() -> std::string { 447 auto hex_to_dec = [this]() -> std::string {
369 return std::to_string(std::stoul(line_edit->text().toStdString(), nullptr, 16)); 448 return std::to_string(std::strtoul(line_edit->text().toStdString().c_str(), nullptr, 16));
370 }; 449 };
371 450
372 serializer = [hex_to_dec]() { return hex_to_dec(); }; 451 serializer = [hex_to_dec]() { return hex_to_dec(); };
@@ -386,7 +465,8 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict,
386 std::function<void()>& restore_func, 465 std::function<void()>& restore_func,
387 const std::function<void()>& touch) { 466 const std::function<void()>& touch) {
388 const long long current_time = QDateTime::currentSecsSinceEpoch(); 467 const long long current_time = QDateTime::currentSecsSinceEpoch();
389 const s64 the_time = disabled ? current_time : std::stoll(setting.ToString()); 468 const s64 the_time =
469 disabled ? current_time : std::strtoll(setting.ToString().c_str(), nullptr, 0);
390 const auto default_val = QDateTime::fromSecsSinceEpoch(the_time); 470 const auto default_val = QDateTime::fromSecsSinceEpoch(the_time);
391 471
392 date_time_edit = new QDateTimeEdit(this); 472 date_time_edit = new QDateTimeEdit(this);
@@ -399,7 +479,7 @@ QWidget* Widget::CreateDateTimeEdit(bool disabled, bool restrict,
399 auto get_clear_val = [this, restrict, current_time]() { 479 auto get_clear_val = [this, restrict, current_time]() {
400 return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() { 480 return QDateTime::fromSecsSinceEpoch([this, restrict, current_time]() {
401 if (restrict && checkbox->checkState() == Qt::Checked) { 481 if (restrict && checkbox->checkState() == Qt::Checked) {
402 return std::stoll(RelevantDefault(setting)); 482 return std::strtoll(RelevantDefault(setting).c_str(), nullptr, 0);
403 } 483 }
404 return current_time; 484 return current_time;
405 }()); 485 }());
@@ -506,8 +586,7 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu
506 } else { 586 } else {
507 data_component = CreateCombobox(serializer, restore_func, touch); 587 data_component = CreateCombobox(serializer, restore_func, touch);
508 } 588 }
509 } else if (type == typeid(u32) || type == typeid(int) || type == typeid(u16) || 589 } else if (setting.IsIntegral()) {
510 type == typeid(s64) || type == typeid(u8)) {
511 switch (request) { 590 switch (request) {
512 case RequestType::Slider: 591 case RequestType::Slider:
513 case RequestType::ReverseSlider: 592 case RequestType::ReverseSlider:
@@ -534,6 +613,20 @@ void Widget::SetupComponent(const QString& label, std::function<void()>& load_fu
534 default: 613 default:
535 UNIMPLEMENTED(); 614 UNIMPLEMENTED();
536 } 615 }
616 } else if (setting.IsFloatingPoint()) {
617 switch (request) {
618 case RequestType::Default:
619 case RequestType::SpinBox:
620 data_component = CreateDoubleSpinBox(suffix, serializer, restore_func, touch);
621 break;
622 case RequestType::Slider:
623 case RequestType::ReverseSlider:
624 data_component = CreateSlider(request == RequestType::ReverseSlider, multiplier, suffix,
625 serializer, restore_func, touch);
626 break;
627 default:
628 UNIMPLEMENTED();
629 }
537 } else if (type == typeid(std::string)) { 630 } else if (type == typeid(std::string)) {
538 switch (request) { 631 switch (request) {
539 case RequestType::Default: 632 case RequestType::Default:
@@ -638,10 +731,10 @@ Widget::Widget(Settings::BasicSetting* setting_, const TranslationMap& translati
638 return std::pair{translations.at(id).first, translations.at(id).second}; 731 return std::pair{translations.at(id).first, translations.at(id).second};
639 } 732 }
640 LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label); 733 LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label);
641 return std::pair{QString::fromStdString(setting_label), QStringLiteral("")}; 734 return std::pair{QString::fromStdString(setting_label), QStringLiteral()};
642 }(); 735 }();
643 736
644 if (label == QStringLiteral("")) { 737 if (label == QStringLiteral()) {
645 LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...", 738 LOG_DEBUG(Frontend, "Translation table has empty entry for \"{}\", skipping...",
646 setting.GetLabel()); 739 setting.GetLabel());
647 return; 740 return;
diff --git a/src/yuzu/configuration/shared_widget.h b/src/yuzu/configuration/shared_widget.h
index 5303dd898..226284cf3 100644
--- a/src/yuzu/configuration/shared_widget.h
+++ b/src/yuzu/configuration/shared_widget.h
@@ -22,6 +22,7 @@ class QObject;
22class QPushButton; 22class QPushButton;
23class QSlider; 23class QSlider;
24class QSpinBox; 24class QSpinBox;
25class QDoubleSpinBox;
25class QRadioButton; 26class QRadioButton;
26 27
27namespace Settings { 28namespace Settings {
@@ -43,6 +44,10 @@ enum class RequestType {
43 MaxEnum, 44 MaxEnum,
44}; 45};
45 46
47constexpr float default_multiplier{1.f};
48constexpr float default_float_multiplier{100.f};
49static const QString default_suffix = QStringLiteral();
50
46class Widget : public QWidget { 51class Widget : public QWidget {
47 Q_OBJECT 52 Q_OBJECT
48 53
@@ -66,8 +71,9 @@ public:
66 const ComboboxTranslationMap& combobox_translations, QWidget* parent, 71 const ComboboxTranslationMap& combobox_translations, QWidget* parent,
67 bool runtime_lock, std::vector<std::function<void(bool)>>& apply_funcs_, 72 bool runtime_lock, std::vector<std::function<void(bool)>>& apply_funcs_,
68 RequestType request = RequestType::Default, bool managed = true, 73 RequestType request = RequestType::Default, bool managed = true,
69 float multiplier = 1.0f, Settings::BasicSetting* other_setting = nullptr, 74 float multiplier = default_multiplier,
70 const QString& suffix = QStringLiteral("")); 75 Settings::BasicSetting* other_setting = nullptr,
76 const QString& suffix = default_suffix);
71 virtual ~Widget(); 77 virtual ~Widget();
72 78
73 /** 79 /**
@@ -89,6 +95,7 @@ public:
89 QPushButton* restore_button{}; ///< Restore button for custom configurations 95 QPushButton* restore_button{}; ///< Restore button for custom configurations
90 QLineEdit* line_edit{}; ///< QLineEdit, used for LineEdit and HexEdit 96 QLineEdit* line_edit{}; ///< QLineEdit, used for LineEdit and HexEdit
91 QSpinBox* spinbox{}; 97 QSpinBox* spinbox{};
98 QDoubleSpinBox* double_spinbox{};
92 QCheckBox* checkbox{}; 99 QCheckBox* checkbox{};
93 QSlider* slider{}; 100 QSlider* slider{};
94 QComboBox* combobox{}; 101 QComboBox* combobox{};
@@ -126,6 +133,9 @@ private:
126 const std::function<void()>& touch); 133 const std::function<void()>& touch);
127 QWidget* CreateSpinBox(const QString& suffix, std::function<std::string()>& serializer, 134 QWidget* CreateSpinBox(const QString& suffix, std::function<std::string()>& serializer,
128 std::function<void()>& restore_func, const std::function<void()>& touch); 135 std::function<void()>& restore_func, const std::function<void()>& touch);
136 QWidget* CreateDoubleSpinBox(const QString& suffix, std::function<std::string()>& serializer,
137 std::function<void()>& restore_func,
138 const std::function<void()>& touch);
129 139
130 QWidget* parent; 140 QWidget* parent;
131 const TranslationMap& translations; 141 const TranslationMap& translations;
@@ -145,14 +155,15 @@ public:
145 Widget* BuildWidget(Settings::BasicSetting* setting, 155 Widget* BuildWidget(Settings::BasicSetting* setting,
146 std::vector<std::function<void(bool)>>& apply_funcs, 156 std::vector<std::function<void(bool)>>& apply_funcs,
147 RequestType request = RequestType::Default, bool managed = true, 157 RequestType request = RequestType::Default, bool managed = true,
148 float multiplier = 1.0f, Settings::BasicSetting* other_setting = nullptr, 158 float multiplier = default_multiplier,
149 const QString& suffix = QStringLiteral("")) const; 159 Settings::BasicSetting* other_setting = nullptr,
160 const QString& suffix = default_suffix) const;
150 161
151 Widget* BuildWidget(Settings::BasicSetting* setting, 162 Widget* BuildWidget(Settings::BasicSetting* setting,
152 std::vector<std::function<void(bool)>>& apply_funcs, 163 std::vector<std::function<void(bool)>>& apply_funcs,
153 Settings::BasicSetting* other_setting, 164 Settings::BasicSetting* other_setting,
154 RequestType request = RequestType::Default, 165 RequestType request = RequestType::Default,
155 const QString& suffix = QStringLiteral("")) const; 166 const QString& suffix = default_suffix) const;
156 167
157 const ComboboxTranslationMap& ComboboxTranslations() const; 168 const ComboboxTranslationMap& ComboboxTranslations() const;
158 169
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h
index 848239c35..56eee8d82 100644
--- a/src/yuzu/hotkeys.h
+++ b/src/yuzu/hotkeys.h
@@ -4,10 +4,12 @@
4#pragma once 4#pragma once
5 5
6#include <map> 6#include <map>
7#include <QKeySequence>
8#include <QString>
9#include <QWidget>
7#include "core/hid/hid_types.h" 10#include "core/hid/hid_types.h"
8 11
9class QDialog; 12class QDialog;
10class QKeySequence;
11class QSettings; 13class QSettings;
12class QShortcut; 14class QShortcut;
13class ControllerShortcut; 15class ControllerShortcut;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 97d216638..d32aa9615 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -9,6 +9,7 @@
9#include <memory> 9#include <memory>
10#include <thread> 10#include <thread>
11#include "core/loader/nca.h" 11#include "core/loader/nca.h"
12#include "core/tools/renderdoc.h"
12#ifdef __APPLE__ 13#ifdef __APPLE__
13#include <unistd.h> // for chdir 14#include <unistd.h> // for chdir
14#endif 15#endif
@@ -1348,6 +1349,11 @@ void GMainWindow::InitializeHotkeys() {
1348 connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { 1349 connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] {
1349 Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); 1350 Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue());
1350 }); 1351 });
1352 connect_shortcut(QStringLiteral("Toggle Renderdoc Capture"), [this] {
1353 if (Settings::values.enable_renderdoc_hotkey) {
1354 system->GetRenderdocAPI().ToggleCapture();
1355 }
1356 });
1351 connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { 1357 connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] {
1352 if (Settings::values.mouse_enabled) { 1358 if (Settings::values.mouse_enabled) {
1353 Settings::values.mouse_panning = false; 1359 Settings::values.mouse_panning = false;
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index c42d98709..0d25ff400 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -259,7 +259,7 @@ void Config::ReadValues() {
259 std::stringstream ss(title_list); 259 std::stringstream ss(title_list);
260 std::string line; 260 std::string line;
261 while (std::getline(ss, line, '|')) { 261 while (std::getline(ss, line, '|')) {
262 const auto title_id = std::stoul(line, nullptr, 16); 262 const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
263 const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, ""); 263 const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, "");
264 264
265 std::stringstream inner_ss(disabled_list); 265 std::stringstream inner_ss(disabled_list);
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 55d0938f7..087cfaa26 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -264,8 +264,9 @@ int main(int argc, char** argv) {
264 nickname = match[1]; 264 nickname = match[1];
265 password = match[2]; 265 password = match[2];
266 address = match[3]; 266 address = match[3];
267 if (!match[4].str().empty()) 267 if (!match[4].str().empty()) {
268 port = static_cast<u16>(std::stoi(match[4])); 268 port = static_cast<u16>(std::strtoul(match[4].str().c_str(), nullptr, 0));
269 }
269 std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$"); 270 std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
270 if (!std::regex_match(nickname, nickname_re)) { 271 if (!std::regex_match(nickname, nickname_re)) {
271 std::cout 272 std::cout