summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/jni/native.cpp1
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp9
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.h2
-rw-r--r--src/audio_core/renderer/command/mix/depop_prepare.h2
-rw-r--r--src/common/CMakeLists.txt8
-rw-r--r--src/common/arm64/native_clock.cpp72
-rw-r--r--src/common/arm64/native_clock.h47
-rw-r--r--src/common/common_funcs.h4
-rw-r--r--src/common/elf.h8
-rw-r--r--src/common/fs/fs_paths.h2
-rw-r--r--src/common/fs/path_util.cpp2
-rw-r--r--src/common/fs/path_util.h2
-rw-r--r--src/common/string_util.cpp5
-rw-r--r--src/common/string_util.h1
-rw-r--r--src/common/wall_clock.cpp8
-rw-r--r--src/core/CMakeLists.txt10
-rw-r--r--src/core/core.cpp4
-rw-r--r--src/core/debugger/gdbstub.cpp17
-rw-r--r--src/core/file_sys/program_metadata.cpp8
-rw-r--r--src/core/file_sys/program_metadata.h1
-rw-r--r--src/core/file_sys/vfs.h2
-rw-r--r--src/core/hle/kernel/k_memory_layout.cpp2
-rw-r--r--src/core/hle/kernel/k_page_table.cpp29
-rw-r--r--src/core/hle/kernel/k_page_table.h3
-rw-r--r--src/core/hle/kernel/k_transfer_memory.cpp89
-rw-r--r--src/core/hle/kernel/k_transfer_memory.h14
-rw-r--r--src/core/hle/kernel/kernel.cpp16
-rw-r--r--src/core/hle/kernel/svc/svc_transfer_memory.cpp54
-rw-r--r--src/core/hle/service/am/am.cpp272
-rw-r--r--src/core/hle/service/am/am.h37
-rw-r--r--src/core/hle/service/am/applet_ae.cpp20
-rw-r--r--src/core/hle/service/am/applets/applet_cabinet.h11
-rw-r--r--src/core/hle/service/am/applets/applet_error.cpp5
-rw-r--r--src/core/hle/service/am/applets/applet_general_backend.cpp6
-rw-r--r--src/core/hle/service/am/applets/applets.cpp16
-rw-r--r--src/core/hle/service/am/applets/applets.h49
-rw-r--r--src/core/hle/service/caps/caps.cpp21
-rw-r--r--src/core/hle/service/caps/caps.h81
-rw-r--r--src/core/hle/service/caps/caps_a.cpp239
-rw-r--r--src/core/hle/service/caps/caps_a.h21
-rw-r--r--src/core/hle/service/caps/caps_c.cpp50
-rw-r--r--src/core/hle/service/caps/caps_c.h10
-rw-r--r--src/core/hle/service/caps/caps_manager.cpp386
-rw-r--r--src/core/hle/service/caps/caps_manager.h79
-rw-r--r--src/core/hle/service/caps/caps_result.h35
-rw-r--r--src/core/hle/service/caps/caps_sc.cpp5
-rw-r--r--src/core/hle/service/caps/caps_sc.h6
-rw-r--r--src/core/hle/service/caps/caps_ss.cpp5
-rw-r--r--src/core/hle/service/caps/caps_ss.h6
-rw-r--r--src/core/hle/service/caps/caps_su.cpp9
-rw-r--r--src/core/hle/service/caps/caps_su.h6
-rw-r--r--src/core/hle/service/caps/caps_types.h184
-rw-r--r--src/core/hle/service/caps/caps_u.cpp146
-rw-r--r--src/core/hle/service/caps/caps_u.h12
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp3
-rw-r--r--src/core/hle/service/jit/jit_context.cpp36
-rw-r--r--src/core/hle/service/ldn/ldn.cpp91
-rw-r--r--src/core/hle/service/nfc/common/device.cpp9
-rw-r--r--src/core/hle/service/nifm/nifm.cpp12
-rw-r--r--src/core/hle/service/nifm/nifm.h1
-rw-r--r--src/core/hle/service/ns/ns.cpp27
-rw-r--r--src/core/hle/service/ns/ns.h4
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.h14
-rw-r--r--src/core/hle/service/nvnflinger/buffer_item.h2
-rw-r--r--src/core/hle/service/nvnflinger/buffer_slot.h2
-rw-r--r--src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp351
-rw-r--r--src/core/hle/service/nvnflinger/fb_share_buffer_manager.h65
-rw-r--r--src/core/hle/service/nvnflinger/graphic_buffer_producer.h2
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.cpp11
-rw-r--r--src/core/hle/service/nvnflinger/nvnflinger.h9
-rw-r--r--src/core/hle/service/nvnflinger/ui/fence.h3
-rw-r--r--src/core/hle/service/nvnflinger/ui/graphic_buffer.h4
-rw-r--r--src/core/hle/service/pctl/pctl_module.cpp9
-rw-r--r--src/core/hle/service/vi/vi.cpp129
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp2
-rw-r--r--src/input_common/drivers/virtual_gamepad.h2
-rw-r--r--src/input_common/helpers/joycon_protocol/generic_functions.h2
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp2
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h46
-rw-r--r--src/video_core/dma_pusher.h2
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt1
-rw-r--r--src/video_core/host_shaders/convert_d32f_to_abgr8.frag14
-rw-r--r--src/video_core/renderer_base.h3
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp14
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h2
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h1
-rw-r--r--src/video_core/renderer_vulkan/blit_image.cpp13
-rw-r--r--src/video_core/renderer_vulkan/blit_image.h4
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp5
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp1
-rw-r--r--src/video_core/renderer_vulkan/vk_present_manager.cpp67
-rw-r--r--src/video_core/renderer_vulkan/vk_present_manager.h10
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp3
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp33
-rw-r--r--src/video_core/renderer_vulkan/vk_staging_buffer_pool.h5
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp48
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h2
-rw-r--r--src/video_core/surface.cpp3
-rw-r--r--src/video_core/surface.h4
-rw-r--r--src/video_core/texture_cache/format_lookup_table.cpp6
-rw-r--r--src/video_core/texture_cache/formatter.h2
-rw-r--r--src/video_core/texture_cache/image_base.h2
-rw-r--r--src/video_core/texture_cache/image_view_base.cpp1
-rw-r--r--src/video_core/texture_cache/texture_cache.h1
-rw-r--r--src/video_core/texture_cache/util.cpp43
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp56
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h2
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp29
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.h14
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.h3
-rw-r--r--src/yuzu/CMakeLists.txt2
-rw-r--r--src/yuzu/applets/qt_controller.cpp39
-rw-r--r--src/yuzu/applets/qt_controller.h6
-rw-r--r--src/yuzu/applets/qt_controller.ui54
-rw-r--r--src/yuzu/configuration/configure_audio.cpp3
-rw-r--r--src/yuzu/configuration/configure_ui.cpp5
-rw-r--r--src/yuzu/configuration/configure_ui.ui7
-rw-r--r--src/yuzu/configuration/shared_translation.cpp3
-rw-r--r--src/yuzu/game_list.cpp18
-rw-r--r--src/yuzu/game_list.h7
-rw-r--r--src/yuzu/game_list_p.h26
-rw-r--r--src/yuzu/game_list_worker.cpp23
-rw-r--r--src/yuzu/game_list_worker.h6
-rw-r--r--src/yuzu/main.cpp213
-rw-r--r--src/yuzu/main.h13
-rw-r--r--src/yuzu/main.ui36
-rw-r--r--src/yuzu/play_time_manager.cpp179
-rw-r--r--src/yuzu/play_time_manager.h44
-rw-r--r--src/yuzu/uisettings.h5
-rw-r--r--src/yuzu/util/util.cpp102
-rw-r--r--src/yuzu/util/util.h14
132 files changed, 3623 insertions, 557 deletions
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 9cf71680c..598f4e8bf 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -218,7 +218,6 @@ public:
218 return; 218 return;
219 } 219 }
220 m_window->OnSurfaceChanged(m_native_window); 220 m_window->OnSurfaceChanged(m_native_window);
221 m_system.Renderer().NotifySurfaceChanged();
222 } 221 }
223 222
224 void ConfigureFilesystemProvider(const std::string& filepath) { 223 void ConfigureFilesystemProvider(const std::string& filepath) {
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
index 972d5e45b..ef301d8b4 100644
--- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
+++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
@@ -77,6 +77,7 @@ void AudioRenderer::Wait() {
77 "{}, got {}", 77 "{}, got {}",
78 Message::RenderResponse, msg); 78 Message::RenderResponse, msg);
79 } 79 }
80 PostDSPClearCommandBuffer();
80} 81}
81 82
82void AudioRenderer::Send(Direction dir, u32 message) { 83void AudioRenderer::Send(Direction dir, u32 message) {
@@ -96,6 +97,14 @@ void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u
96 command_buffers[session_id].reset_buffer = reset; 97 command_buffers[session_id].reset_buffer = reset;
97} 98}
98 99
100void AudioRenderer::PostDSPClearCommandBuffer() noexcept {
101 for (auto& buffer : command_buffers) {
102 buffer.buffer = 0;
103 buffer.size = 0;
104 buffer.reset_buffer = false;
105 }
106}
107
99u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept { 108u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept {
100 return command_buffers[session_id].remaining_command_count; 109 return command_buffers[session_id].remaining_command_count;
101} 110}
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
index 85874d88a..57b89d9fe 100644
--- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
+++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
@@ -85,6 +85,8 @@ private:
85 */ 85 */
86 void CreateSinkStreams(); 86 void CreateSinkStreams();
87 87
88 void PostDSPClearCommandBuffer() noexcept;
89
88 /// Core system 90 /// Core system
89 Core::System& system; 91 Core::System& system;
90 /// The output sink the AudioRenderer will send samples to 92 /// The output sink the AudioRenderer will send samples to
diff --git a/src/audio_core/renderer/command/mix/depop_prepare.h b/src/audio_core/renderer/command/mix/depop_prepare.h
index 161a94461..a0cc228f7 100644
--- a/src/audio_core/renderer/command/mix/depop_prepare.h
+++ b/src/audio_core/renderer/command/mix/depop_prepare.h
@@ -16,7 +16,7 @@ namespace AudioCore::Renderer {
16 16
17/** 17/**
18 * AudioRenderer command for preparing depop. 18 * AudioRenderer command for preparing depop.
19 * Adds the previusly output last samples to the depop buffer. 19 * Adds the previously output last samples to the depop buffer.
20 */ 20 */
21struct DepopPrepareCommand : ICommand { 21struct DepopPrepareCommand : ICommand {
22 /** 22 /**
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 416203c59..8a1861051 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -189,6 +189,14 @@ if(ARCHITECTURE_x86_64)
189 target_link_libraries(common PRIVATE xbyak::xbyak) 189 target_link_libraries(common PRIVATE xbyak::xbyak)
190endif() 190endif()
191 191
192if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX))
193 target_sources(common
194 PRIVATE
195 arm64/native_clock.cpp
196 arm64/native_clock.h
197 )
198endif()
199
192if (MSVC) 200if (MSVC)
193 target_compile_definitions(common PRIVATE 201 target_compile_definitions(common PRIVATE
194 # The standard library doesn't provide any replacement for codecvt yet 202 # The standard library doesn't provide any replacement for codecvt yet
diff --git a/src/common/arm64/native_clock.cpp b/src/common/arm64/native_clock.cpp
new file mode 100644
index 000000000..88fdba527
--- /dev/null
+++ b/src/common/arm64/native_clock.cpp
@@ -0,0 +1,72 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/arm64/native_clock.h"
5
6namespace Common::Arm64 {
7
8namespace {
9
10NativeClock::FactorType GetFixedPointFactor(u64 num, u64 den) {
11 return (static_cast<NativeClock::FactorType>(num) << 64) / den;
12}
13
14u64 MultiplyHigh(u64 m, NativeClock::FactorType factor) {
15 return static_cast<u64>((m * factor) >> 64);
16}
17
18} // namespace
19
20NativeClock::NativeClock() {
21 const u64 host_cntfrq = GetHostCNTFRQ();
22 ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq);
23 us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq);
24 ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq);
25 guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq);
26 gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq);
27}
28
29std::chrono::nanoseconds NativeClock::GetTimeNS() const {
30 return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_cntfrq_factor)};
31}
32
33std::chrono::microseconds NativeClock::GetTimeUS() const {
34 return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_cntfrq_factor)};
35}
36
37std::chrono::milliseconds NativeClock::GetTimeMS() const {
38 return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_cntfrq_factor)};
39}
40
41u64 NativeClock::GetCNTPCT() const {
42 return MultiplyHigh(GetHostTicksElapsed(), guest_cntfrq_factor);
43}
44
45u64 NativeClock::GetGPUTick() const {
46 return MultiplyHigh(GetHostTicksElapsed(), gputick_cntfrq_factor);
47}
48
49u64 NativeClock::GetHostTicksNow() const {
50 u64 cntvct_el0 = 0;
51 asm volatile("dsb ish\n\t"
52 "mrs %[cntvct_el0], cntvct_el0\n\t"
53 "dsb ish\n\t"
54 : [cntvct_el0] "=r"(cntvct_el0));
55 return cntvct_el0;
56}
57
58u64 NativeClock::GetHostTicksElapsed() const {
59 return GetHostTicksNow();
60}
61
62bool NativeClock::IsNative() const {
63 return true;
64}
65
66u64 NativeClock::GetHostCNTFRQ() {
67 u64 cntfrq_el0 = 0;
68 asm("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
69 return cntfrq_el0;
70}
71
72} // namespace Common::Arm64
diff --git a/src/common/arm64/native_clock.h b/src/common/arm64/native_clock.h
new file mode 100644
index 000000000..a28b419f2
--- /dev/null
+++ b/src/common/arm64/native_clock.h
@@ -0,0 +1,47 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/wall_clock.h"
7
8namespace Common::Arm64 {
9
10class NativeClock final : public WallClock {
11public:
12 explicit NativeClock();
13
14 std::chrono::nanoseconds GetTimeNS() const override;
15
16 std::chrono::microseconds GetTimeUS() const override;
17
18 std::chrono::milliseconds GetTimeMS() const override;
19
20 u64 GetCNTPCT() const override;
21
22 u64 GetGPUTick() const override;
23
24 u64 GetHostTicksNow() const override;
25
26 u64 GetHostTicksElapsed() const override;
27
28 bool IsNative() const override;
29
30 static u64 GetHostCNTFRQ();
31
32public:
33 using FactorType = unsigned __int128;
34
35 FactorType GetGuestCNTFRQFactor() const {
36 return guest_cntfrq_factor;
37 }
38
39private:
40 FactorType ns_cntfrq_factor;
41 FactorType us_cntfrq_factor;
42 FactorType ms_cntfrq_factor;
43 FactorType guest_cntfrq_factor;
44 FactorType gputick_cntfrq_factor;
45};
46
47} // namespace Common::Arm64
diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h
index 0dad9338a..47d028d48 100644
--- a/src/common/common_funcs.h
+++ b/src/common/common_funcs.h
@@ -39,8 +39,12 @@
39#define Crash() exit(1) 39#define Crash() exit(1)
40#endif 40#endif
41 41
42#define LTO_NOINLINE __attribute__((noinline))
43
42#else // _MSC_VER 44#else // _MSC_VER
43 45
46#define LTO_NOINLINE
47
44// Locale Cross-Compatibility 48// Locale Cross-Compatibility
45#define locale_t _locale_t 49#define locale_t _locale_t
46 50
diff --git a/src/common/elf.h b/src/common/elf.h
index 14a5e9597..0b728dc54 100644
--- a/src/common/elf.h
+++ b/src/common/elf.h
@@ -211,6 +211,11 @@ struct Elf64_Rela {
211 Elf64_Sxword r_addend; /* Addend */ 211 Elf64_Sxword r_addend; /* Addend */
212}; 212};
213 213
214/* RELR relocation table entry */
215
216using Elf32_Relr = Elf32_Word;
217using Elf64_Relr = Elf64_Xword;
218
214/* How to extract and insert information held in the r_info field. */ 219/* How to extract and insert information held in the r_info field. */
215 220
216static inline u32 Elf32RelSymIndex(Elf32_Word r_info) { 221static inline u32 Elf32RelSymIndex(Elf32_Word r_info) {
@@ -328,6 +333,9 @@ constexpr u32 ElfDtFiniArray = 26; /* Array with addresses of fini fct */
328constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */ 333constexpr u32 ElfDtInitArraySz = 27; /* Size in bytes of DT_INIT_ARRAY */
329constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */ 334constexpr u32 ElfDtFiniArraySz = 28; /* Size in bytes of DT_FINI_ARRAY */
330constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */ 335constexpr u32 ElfDtSymtabShndx = 34; /* Address of SYMTAB_SHNDX section */
336constexpr u32 ElfDtRelrsz = 35; /* Size of RELR relative relocations */
337constexpr u32 ElfDtRelr = 36; /* Address of RELR relative relocations */
338constexpr u32 ElfDtRelrent = 37; /* Size of one RELR relative relocation */
331 339
332} // namespace ELF 340} // namespace ELF
333} // namespace Common 341} // namespace Common
diff --git a/src/common/fs/fs_paths.h b/src/common/fs/fs_paths.h
index 61bac9eba..441c8af97 100644
--- a/src/common/fs/fs_paths.h
+++ b/src/common/fs/fs_paths.h
@@ -18,10 +18,12 @@
18#define LOAD_DIR "load" 18#define LOAD_DIR "load"
19#define LOG_DIR "log" 19#define LOG_DIR "log"
20#define NAND_DIR "nand" 20#define NAND_DIR "nand"
21#define PLAY_TIME_DIR "play_time"
21#define SCREENSHOTS_DIR "screenshots" 22#define SCREENSHOTS_DIR "screenshots"
22#define SDMC_DIR "sdmc" 23#define SDMC_DIR "sdmc"
23#define SHADER_DIR "shader" 24#define SHADER_DIR "shader"
24#define TAS_DIR "tas" 25#define TAS_DIR "tas"
26#define ICONS_DIR "icons"
25 27
26// yuzu-specific files 28// yuzu-specific files
27 29
diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp
index dce219fcf..0abd81a45 100644
--- a/src/common/fs/path_util.cpp
+++ b/src/common/fs/path_util.cpp
@@ -124,10 +124,12 @@ public:
124 GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR); 124 GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR);
125 GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR); 125 GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR);
126 GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR); 126 GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR);
127 GenerateYuzuPath(YuzuPath::PlayTimeDir, yuzu_path / PLAY_TIME_DIR);
127 GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR); 128 GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR);
128 GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR); 129 GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
129 GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR); 130 GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
130 GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR); 131 GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
132 GenerateYuzuPath(YuzuPath::IconsDir, yuzu_path / ICONS_DIR);
131 } 133 }
132 134
133private: 135private:
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index ba28964d0..63801c924 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -20,10 +20,12 @@ enum class YuzuPath {
20 LoadDir, // Where cheat/mod files are stored. 20 LoadDir, // Where cheat/mod files are stored.
21 LogDir, // Where log files are stored. 21 LogDir, // Where log files are stored.
22 NANDDir, // Where the emulated NAND is stored. 22 NANDDir, // Where the emulated NAND is stored.
23 PlayTimeDir, // Where play time data is stored.
23 ScreenshotsDir, // Where yuzu screenshots are stored. 24 ScreenshotsDir, // Where yuzu screenshots are stored.
24 SDMCDir, // Where the emulated SDMC is stored. 25 SDMCDir, // Where the emulated SDMC is stored.
25 ShaderDir, // Where shaders are stored. 26 ShaderDir, // Where shaders are stored.
26 TASDir, // Where TAS scripts are stored. 27 TASDir, // Where TAS scripts are stored.
28 IconsDir, // Where Icons for Windows shortcuts are stored.
27}; 29};
28 30
29/** 31/**
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp
index feab1653d..4c7aba3f5 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -135,6 +135,11 @@ std::u16string UTF8ToUTF16(std::string_view input) {
135 return convert.from_bytes(input.data(), input.data() + input.size()); 135 return convert.from_bytes(input.data(), input.data() + input.size());
136} 136}
137 137
138std::u32string UTF8ToUTF32(std::string_view input) {
139 std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
140 return convert.from_bytes(input.data(), input.data() + input.size());
141}
142
138#ifdef _WIN32 143#ifdef _WIN32
139static std::wstring CPToUTF16(u32 code_page, std::string_view input) { 144static std::wstring CPToUTF16(u32 code_page, std::string_view input) {
140 const auto size = 145 const auto size =
diff --git a/src/common/string_util.h b/src/common/string_util.h
index c351f1a0c..9da1ca4e9 100644
--- a/src/common/string_util.h
+++ b/src/common/string_util.h
@@ -38,6 +38,7 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
38 38
39[[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input); 39[[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input);
40[[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input); 40[[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input);
41[[nodiscard]] std::u32string UTF8ToUTF32(std::string_view input);
41 42
42#ifdef _WIN32 43#ifdef _WIN32
43[[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input); 44[[nodiscard]] std::string UTF16ToUTF8(std::wstring_view input);
diff --git a/src/common/wall_clock.cpp b/src/common/wall_clock.cpp
index 71e15ab4c..caca9a123 100644
--- a/src/common/wall_clock.cpp
+++ b/src/common/wall_clock.cpp
@@ -10,6 +10,10 @@
10#include "common/x64/rdtsc.h" 10#include "common/x64/rdtsc.h"
11#endif 11#endif
12 12
13#if defined(ARCHITECTURE_arm64) && defined(__linux__)
14#include "common/arm64/native_clock.h"
15#endif
16
13namespace Common { 17namespace Common {
14 18
15class StandardWallClock final : public WallClock { 19class StandardWallClock final : public WallClock {
@@ -53,7 +57,7 @@ private:
53}; 57};
54 58
55std::unique_ptr<WallClock> CreateOptimalClock() { 59std::unique_ptr<WallClock> CreateOptimalClock() {
56#ifdef ARCHITECTURE_x86_64 60#if defined(ARCHITECTURE_x86_64)
57 const auto& caps = GetCPUCaps(); 61 const auto& caps = GetCPUCaps();
58 62
59 if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) { 63 if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) {
@@ -64,6 +68,8 @@ std::unique_ptr<WallClock> CreateOptimalClock() {
64 // - Is not more precise than 1 GHz (1ns resolution) 68 // - Is not more precise than 1 GHz (1ns resolution)
65 return std::make_unique<StandardWallClock>(); 69 return std::make_unique<StandardWallClock>();
66 } 70 }
71#elif defined(ARCHITECTURE_arm64) && defined(__linux__)
72 return std::make_unique<Arm64::NativeClock>();
67#else 73#else
68 return std::make_unique<StandardWallClock>(); 74 return std::make_unique<StandardWallClock>();
69#endif 75#endif
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index d0f76e57e..e4f499135 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -466,14 +466,18 @@ add_library(core STATIC
466 hle/service/caps/caps_a.h 466 hle/service/caps/caps_a.h
467 hle/service/caps/caps_c.cpp 467 hle/service/caps/caps_c.cpp
468 hle/service/caps/caps_c.h 468 hle/service/caps/caps_c.h
469 hle/service/caps/caps_u.cpp 469 hle/service/caps/caps_manager.cpp
470 hle/service/caps/caps_u.h 470 hle/service/caps/caps_manager.h
471 hle/service/caps/caps_result.h
471 hle/service/caps/caps_sc.cpp 472 hle/service/caps/caps_sc.cpp
472 hle/service/caps/caps_sc.h 473 hle/service/caps/caps_sc.h
473 hle/service/caps/caps_ss.cpp 474 hle/service/caps/caps_ss.cpp
474 hle/service/caps/caps_ss.h 475 hle/service/caps/caps_ss.h
475 hle/service/caps/caps_su.cpp 476 hle/service/caps/caps_su.cpp
476 hle/service/caps/caps_su.h 477 hle/service/caps/caps_su.h
478 hle/service/caps/caps_types.h
479 hle/service/caps/caps_u.cpp
480 hle/service/caps/caps_u.h
477 hle/service/erpt/erpt.cpp 481 hle/service/erpt/erpt.cpp
478 hle/service/erpt/erpt.h 482 hle/service/erpt/erpt.h
479 hle/service/es/es.cpp 483 hle/service/es/es.cpp
@@ -698,6 +702,8 @@ add_library(core STATIC
698 hle/service/nvnflinger/consumer_base.cpp 702 hle/service/nvnflinger/consumer_base.cpp
699 hle/service/nvnflinger/consumer_base.h 703 hle/service/nvnflinger/consumer_base.h
700 hle/service/nvnflinger/consumer_listener.h 704 hle/service/nvnflinger/consumer_listener.h
705 hle/service/nvnflinger/fb_share_buffer_manager.cpp
706 hle/service/nvnflinger/fb_share_buffer_manager.h
701 hle/service/nvnflinger/graphic_buffer_producer.cpp 707 hle/service/nvnflinger/graphic_buffer_producer.cpp
702 hle/service/nvnflinger/graphic_buffer_producer.h 708 hle/service/nvnflinger/graphic_buffer_producer.h
703 hle/service/nvnflinger/hos_binder_driver_server.cpp 709 hle/service/nvnflinger/hos_binder_driver_server.cpp
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 08cbb8978..0ab2e3b76 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -1078,6 +1078,10 @@ void System::ApplySettings() {
1078 impl->RefreshTime(); 1078 impl->RefreshTime();
1079 1079
1080 if (IsPoweredOn()) { 1080 if (IsPoweredOn()) {
1081 if (Settings::values.custom_rtc_enabled) {
1082 const s64 posix_time{Settings::values.custom_rtc.GetValue()};
1083 GetTimeManager().UpdateLocalSystemClockTime(posix_time);
1084 }
1081 Renderer().RefreshBaseSettings(); 1085 Renderer().RefreshBaseSettings();
1082 } 1086 }
1083} 1087}
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
index e55831f27..82964f0a1 100644
--- a/src/core/debugger/gdbstub.cpp
+++ b/src/core/debugger/gdbstub.cpp
@@ -2,6 +2,8 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <atomic> 4#include <atomic>
5#include <codecvt>
6#include <locale>
5#include <numeric> 7#include <numeric>
6#include <optional> 8#include <optional>
7#include <thread> 9#include <thread>
@@ -12,6 +14,7 @@
12#include "common/logging/log.h" 14#include "common/logging/log.h"
13#include "common/scope_exit.h" 15#include "common/scope_exit.h"
14#include "common/settings.h" 16#include "common/settings.h"
17#include "common/string_util.h"
15#include "core/arm/arm_interface.h" 18#include "core/arm/arm_interface.h"
16#include "core/core.h" 19#include "core/core.h"
17#include "core/debugger/gdbstub.h" 20#include "core/debugger/gdbstub.h"
@@ -68,10 +71,16 @@ static std::string EscapeGDB(std::string_view data) {
68} 71}
69 72
70static std::string EscapeXML(std::string_view data) { 73static std::string EscapeXML(std::string_view data) {
74 std::u32string converted = U"[Encoding error]";
75 try {
76 converted = Common::UTF8ToUTF32(data);
77 } catch (std::range_error&) {
78 }
79
71 std::string escaped; 80 std::string escaped;
72 escaped.reserve(data.size()); 81 escaped.reserve(data.size());
73 82
74 for (char c : data) { 83 for (char32_t c : converted) {
75 switch (c) { 84 switch (c) {
76 case '&': 85 case '&':
77 escaped += "&amp;"; 86 escaped += "&amp;";
@@ -86,7 +95,11 @@ static std::string EscapeXML(std::string_view data) {
86 escaped += "&gt;"; 95 escaped += "&gt;";
87 break; 96 break;
88 default: 97 default:
89 escaped += c; 98 if (c > 0x7f) {
99 escaped += fmt::format("&#{};", static_cast<u32>(c));
100 } else {
101 escaped += static_cast<char>(c);
102 }
90 break; 103 break;
91 } 104 }
92 } 105 }
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index f00479bd3..8e291ff67 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -5,6 +5,7 @@
5#include <vector> 5#include <vector>
6 6
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8#include "common/scope_exit.h"
8#include "core/file_sys/program_metadata.h" 9#include "core/file_sys/program_metadata.h"
9#include "core/file_sys/vfs.h" 10#include "core/file_sys/vfs.h"
10#include "core/loader/loader.h" 11#include "core/loader/loader.h"
@@ -95,6 +96,13 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
95 return Loader::ResultStatus::Success; 96 return Loader::ResultStatus::Success;
96} 97}
97 98
99Loader::ResultStatus ProgramMetadata::Reload(VirtualFile file) {
100 const u64 original_program_id = aci_header.title_id;
101 SCOPE_EXIT({ aci_header.title_id = original_program_id; });
102
103 return this->Load(file);
104}
105
98/*static*/ ProgramMetadata ProgramMetadata::GetDefault() { 106/*static*/ ProgramMetadata ProgramMetadata::GetDefault() {
99 // Allow use of cores 0~3 and thread priorities 1~63. 107 // Allow use of cores 0~3 and thread priorities 1~63.
100 constexpr u32 default_thread_info_capability = 0x30007F7; 108 constexpr u32 default_thread_info_capability = 0x30007F7;
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index 2e8960b07..9f8e74b13 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -56,6 +56,7 @@ public:
56 static ProgramMetadata GetDefault(); 56 static ProgramMetadata GetDefault();
57 57
58 Loader::ResultStatus Load(VirtualFile file); 58 Loader::ResultStatus Load(VirtualFile file);
59 Loader::ResultStatus Reload(VirtualFile file);
59 60
60 /// Load from parameters instead of NPDM file, used for KIP 61 /// Load from parameters instead of NPDM file, used for KIP
61 void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, s32 main_thread_prio, 62 void LoadManual(bool is_64_bit, ProgramAddressSpaceType address_space, s32 main_thread_prio,
diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h
index a93e21f67..a7cd1cae3 100644
--- a/src/core/file_sys/vfs.h
+++ b/src/core/file_sys/vfs.h
@@ -175,7 +175,7 @@ public:
175 return Write(reinterpret_cast<const u8*>(&data), sizeof(T), offset); 175 return Write(reinterpret_cast<const u8*>(&data), sizeof(T), offset);
176 } 176 }
177 177
178 // Renames the file to name. Returns whether or not the operation was successsful. 178 // Renames the file to name. Returns whether or not the operation was successful.
179 virtual bool Rename(std::string_view name) = 0; 179 virtual bool Rename(std::string_view name) = 0;
180 180
181 // Returns the full path of this file as a string, recursively 181 // Returns the full path of this file as a string, recursively
diff --git a/src/core/hle/kernel/k_memory_layout.cpp b/src/core/hle/kernel/k_memory_layout.cpp
index af40092c0..bec714668 100644
--- a/src/core/hle/kernel/k_memory_layout.cpp
+++ b/src/core/hle/kernel/k_memory_layout.cpp
@@ -61,7 +61,7 @@ bool KMemoryRegionTree::Insert(u64 address, size_t size, u32 type_id, u32 new_at
61 found->Reset(address, inserted_region_last, old_pair, new_attr, type_id); 61 found->Reset(address, inserted_region_last, old_pair, new_attr, type_id);
62 this->insert(*found); 62 this->insert(*found);
63 } else { 63 } else {
64 // If we can't re-use, adjust the old region. 64 // If we can't reuse, adjust the old region.
65 found->Reset(old_address, address - 1, old_pair, old_attr, old_type); 65 found->Reset(old_address, address - 1, old_pair, old_attr, old_type);
66 this->insert(*found); 66 this->insert(*found);
67 67
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
index 9bfc85b34..1fbfbf31f 100644
--- a/src/core/hle/kernel/k_page_table.cpp
+++ b/src/core/hle/kernel/k_page_table.cpp
@@ -5,6 +5,7 @@
5#include "common/assert.h" 5#include "common/assert.h"
6#include "common/literals.h" 6#include "common/literals.h"
7#include "common/scope_exit.h" 7#include "common/scope_exit.h"
8#include "common/settings.h"
8#include "core/core.h" 9#include "core/core.h"
9#include "core/hle/kernel/k_address_space_info.h" 10#include "core/hle/kernel/k_address_space_info.h"
10#include "core/hle/kernel/k_memory_block.h" 11#include "core/hle/kernel/k_memory_block.h"
@@ -337,11 +338,14 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type
337} 338}
338 339
339void KPageTable::Finalize() { 340void KPageTable::Finalize() {
341 auto HostUnmapCallback = [&](KProcessAddress addr, u64 size) {
342 if (Settings::IsFastmemEnabled()) {
343 m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size);
344 }
345 };
346
340 // Finalize memory blocks. 347 // Finalize memory blocks.
341 m_memory_block_manager.Finalize(m_memory_block_slab_manager, 348 m_memory_block_manager.Finalize(m_memory_block_slab_manager, std::move(HostUnmapCallback));
342 [&](KProcessAddress addr, u64 size) {
343 m_memory->UnmapRegion(*m_page_table_impl, addr, size);
344 });
345 349
346 // Release any insecure mapped memory. 350 // Release any insecure mapped memory.
347 if (m_mapped_insecure_memory) { 351 if (m_mapped_insecure_memory) {
@@ -2945,6 +2949,23 @@ Result KPageTable::UnlockForIpcUserBuffer(KProcessAddress address, size_t size)
2945 KMemoryAttribute::Locked, nullptr)); 2949 KMemoryAttribute::Locked, nullptr));
2946} 2950}
2947 2951
2952Result KPageTable::LockForTransferMemory(KPageGroup* out, KProcessAddress address, size_t size,
2953 KMemoryPermission perm) {
2954 R_RETURN(this->LockMemoryAndOpen(out, nullptr, address, size, KMemoryState::FlagCanTransfer,
2955 KMemoryState::FlagCanTransfer, KMemoryPermission::All,
2956 KMemoryPermission::UserReadWrite, KMemoryAttribute::All,
2957 KMemoryAttribute::None, perm, KMemoryAttribute::Locked));
2958}
2959
2960Result KPageTable::UnlockForTransferMemory(KProcessAddress address, size_t size,
2961 const KPageGroup& pg) {
2962 R_RETURN(this->UnlockMemory(address, size, KMemoryState::FlagCanTransfer,
2963 KMemoryState::FlagCanTransfer, KMemoryPermission::None,
2964 KMemoryPermission::None, KMemoryAttribute::All,
2965 KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite,
2966 KMemoryAttribute::Locked, std::addressof(pg)));
2967}
2968
2948Result KPageTable::LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size) { 2969Result KPageTable::LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size) {
2949 R_RETURN(this->LockMemoryAndOpen( 2970 R_RETURN(this->LockMemoryAndOpen(
2950 out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory, 2971 out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory,
diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h
index b9e8c6042..7da675f27 100644
--- a/src/core/hle/kernel/k_page_table.h
+++ b/src/core/hle/kernel/k_page_table.h
@@ -104,6 +104,9 @@ public:
104 Result CleanupForIpcServer(KProcessAddress address, size_t size, KMemoryState dst_state); 104 Result CleanupForIpcServer(KProcessAddress address, size_t size, KMemoryState dst_state);
105 Result CleanupForIpcClient(KProcessAddress address, size_t size, KMemoryState dst_state); 105 Result CleanupForIpcClient(KProcessAddress address, size_t size, KMemoryState dst_state);
106 106
107 Result LockForTransferMemory(KPageGroup* out, KProcessAddress address, size_t size,
108 KMemoryPermission perm);
109 Result UnlockForTransferMemory(KProcessAddress address, size_t size, const KPageGroup& pg);
107 Result LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size); 110 Result LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size);
108 Result UnlockForCodeMemory(KProcessAddress addr, size_t size, const KPageGroup& pg); 111 Result UnlockForCodeMemory(KProcessAddress addr, size_t size, const KPageGroup& pg);
109 Result MakeAndOpenPageGroup(KPageGroup* out, KProcessAddress address, size_t num_pages, 112 Result MakeAndOpenPageGroup(KPageGroup* out, KProcessAddress address, size_t num_pages,
diff --git a/src/core/hle/kernel/k_transfer_memory.cpp b/src/core/hle/kernel/k_transfer_memory.cpp
index 13d34125c..0e2e11743 100644
--- a/src/core/hle/kernel/k_transfer_memory.cpp
+++ b/src/core/hle/kernel/k_transfer_memory.cpp
@@ -1,6 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "common/scope_exit.h"
4#include "core/hle/kernel/k_process.h" 5#include "core/hle/kernel/k_process.h"
5#include "core/hle/kernel/k_resource_limit.h" 6#include "core/hle/kernel/k_resource_limit.h"
6#include "core/hle/kernel/k_transfer_memory.h" 7#include "core/hle/kernel/k_transfer_memory.h"
@@ -9,28 +10,50 @@
9namespace Kernel { 10namespace Kernel {
10 11
11KTransferMemory::KTransferMemory(KernelCore& kernel) 12KTransferMemory::KTransferMemory(KernelCore& kernel)
12 : KAutoObjectWithSlabHeapAndContainer{kernel} {} 13 : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{kernel} {}
13 14
14KTransferMemory::~KTransferMemory() = default; 15KTransferMemory::~KTransferMemory() = default;
15 16
16Result KTransferMemory::Initialize(KProcessAddress address, std::size_t size, 17Result KTransferMemory::Initialize(KProcessAddress addr, std::size_t size,
17 Svc::MemoryPermission owner_perm) { 18 Svc::MemoryPermission own_perm) {
18 // Set members. 19 // Set members.
19 m_owner = GetCurrentProcessPointer(m_kernel); 20 m_owner = GetCurrentProcessPointer(m_kernel);
20 21
21 // TODO(bunnei): Lock for transfer memory 22 // Get the owner page table.
23 auto& page_table = m_owner->GetPageTable();
24
25 // Construct the page group, guarding to make sure our state is valid on exit.
26 m_page_group.emplace(m_kernel, page_table.GetBlockInfoManager());
27 auto pg_guard = SCOPE_GUARD({ m_page_group.reset(); });
28
29 // Lock the memory.
30 R_TRY(page_table.LockForTransferMemory(std::addressof(*m_page_group), addr, size,
31 ConvertToKMemoryPermission(own_perm)));
22 32
23 // Set remaining tracking members. 33 // Set remaining tracking members.
24 m_owner->Open(); 34 m_owner->Open();
25 m_owner_perm = owner_perm; 35 m_owner_perm = own_perm;
26 m_address = address; 36 m_address = addr;
27 m_size = size;
28 m_is_initialized = true; 37 m_is_initialized = true;
38 m_is_mapped = false;
29 39
40 // We succeeded.
41 pg_guard.Cancel();
30 R_SUCCEED(); 42 R_SUCCEED();
31} 43}
32 44
33void KTransferMemory::Finalize() {} 45void KTransferMemory::Finalize() {
46 // Unlock.
47 if (!m_is_mapped) {
48 const size_t size = m_page_group->GetNumPages() * PageSize;
49 ASSERT(R_SUCCEEDED(
50 m_owner->GetPageTable().UnlockForTransferMemory(m_address, size, *m_page_group)));
51 }
52
53 // Close the page group.
54 m_page_group->Close();
55 m_page_group->Finalize();
56}
34 57
35void KTransferMemory::PostDestroy(uintptr_t arg) { 58void KTransferMemory::PostDestroy(uintptr_t arg) {
36 KProcess* owner = reinterpret_cast<KProcess*>(arg); 59 KProcess* owner = reinterpret_cast<KProcess*>(arg);
@@ -38,4 +61,54 @@ void KTransferMemory::PostDestroy(uintptr_t arg) {
38 owner->Close(); 61 owner->Close();
39} 62}
40 63
64Result KTransferMemory::Map(KProcessAddress address, size_t size, Svc::MemoryPermission map_perm) {
65 // Validate the size.
66 R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
67
68 // Validate the permission.
69 R_UNLESS(m_owner_perm == map_perm, ResultInvalidState);
70
71 // Lock ourselves.
72 KScopedLightLock lk(m_lock);
73
74 // Ensure we're not already mapped.
75 R_UNLESS(!m_is_mapped, ResultInvalidState);
76
77 // Map the memory.
78 const KMemoryState state = (m_owner_perm == Svc::MemoryPermission::None)
79 ? KMemoryState::Transfered
80 : KMemoryState::SharedTransfered;
81 R_TRY(GetCurrentProcess(m_kernel).GetPageTable().MapPageGroup(
82 address, *m_page_group, state, KMemoryPermission::UserReadWrite));
83
84 // Mark ourselves as mapped.
85 m_is_mapped = true;
86
87 R_SUCCEED();
88}
89
90Result KTransferMemory::Unmap(KProcessAddress address, size_t size) {
91 // Validate the size.
92 R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
93
94 // Lock ourselves.
95 KScopedLightLock lk(m_lock);
96
97 // Unmap the memory.
98 const KMemoryState state = (m_owner_perm == Svc::MemoryPermission::None)
99 ? KMemoryState::Transfered
100 : KMemoryState::SharedTransfered;
101 R_TRY(GetCurrentProcess(m_kernel).GetPageTable().UnmapPageGroup(address, *m_page_group, state));
102
103 // Mark ourselves as unmapped.
104 ASSERT(m_is_mapped);
105 m_is_mapped = false;
106
107 R_SUCCEED();
108}
109
110size_t KTransferMemory::GetSize() const {
111 return m_is_initialized ? m_page_group->GetNumPages() * PageSize : 0;
112}
113
41} // namespace Kernel 114} // namespace Kernel
diff --git a/src/core/hle/kernel/k_transfer_memory.h b/src/core/hle/kernel/k_transfer_memory.h
index 54f97ccb4..8a0b08761 100644
--- a/src/core/hle/kernel/k_transfer_memory.h
+++ b/src/core/hle/kernel/k_transfer_memory.h
@@ -3,6 +3,9 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <optional>
7
8#include "core/hle/kernel/k_page_group.h"
6#include "core/hle/kernel/slab_helpers.h" 9#include "core/hle/kernel/slab_helpers.h"
7#include "core/hle/kernel/svc_types.h" 10#include "core/hle/kernel/svc_types.h"
8#include "core/hle/result.h" 11#include "core/hle/result.h"
@@ -48,16 +51,19 @@ public:
48 return m_address; 51 return m_address;
49 } 52 }
50 53
51 size_t GetSize() const { 54 size_t GetSize() const;
52 return m_is_initialized ? m_size : 0; 55
53 } 56 Result Map(KProcessAddress address, size_t size, Svc::MemoryPermission map_perm);
57 Result Unmap(KProcessAddress address, size_t size);
54 58
55private: 59private:
60 std::optional<KPageGroup> m_page_group{};
56 KProcess* m_owner{}; 61 KProcess* m_owner{};
57 KProcessAddress m_address{}; 62 KProcessAddress m_address{};
63 KLightLock m_lock;
58 Svc::MemoryPermission m_owner_perm{}; 64 Svc::MemoryPermission m_owner_perm{};
59 size_t m_size{};
60 bool m_is_initialized{}; 65 bool m_is_initialized{};
66 bool m_is_mapped{};
61}; 67};
62 68
63} // namespace Kernel 69} // namespace Kernel
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index a1134b7e2..cb025c3d6 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -373,7 +373,7 @@ struct KernelCore::Impl {
373 static inline thread_local u8 host_thread_id = UINT8_MAX; 373 static inline thread_local u8 host_thread_id = UINT8_MAX;
374 374
375 /// Sets the host thread ID for the caller. 375 /// Sets the host thread ID for the caller.
376 u32 SetHostThreadId(std::size_t core_id) { 376 LTO_NOINLINE u32 SetHostThreadId(std::size_t core_id) {
377 // This should only be called during core init. 377 // This should only be called during core init.
378 ASSERT(host_thread_id == UINT8_MAX); 378 ASSERT(host_thread_id == UINT8_MAX);
379 379
@@ -384,13 +384,13 @@ struct KernelCore::Impl {
384 } 384 }
385 385
386 /// Gets the host thread ID for the caller 386 /// Gets the host thread ID for the caller
387 u32 GetHostThreadId() const { 387 LTO_NOINLINE u32 GetHostThreadId() const {
388 return host_thread_id; 388 return host_thread_id;
389 } 389 }
390 390
391 // Gets the dummy KThread for the caller, allocating a new one if this is the first time 391 // Gets the dummy KThread for the caller, allocating a new one if this is the first time
392 KThread* GetHostDummyThread(KThread* existing_thread) { 392 LTO_NOINLINE KThread* GetHostDummyThread(KThread* existing_thread) {
393 const auto initialize{[](KThread* thread) { 393 const auto initialize{[](KThread* thread) LTO_NOINLINE {
394 ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess()); 394 ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess());
395 return thread; 395 return thread;
396 }}; 396 }};
@@ -424,11 +424,11 @@ struct KernelCore::Impl {
424 424
425 static inline thread_local bool is_phantom_mode_for_singlecore{false}; 425 static inline thread_local bool is_phantom_mode_for_singlecore{false};
426 426
427 bool IsPhantomModeForSingleCore() const { 427 LTO_NOINLINE bool IsPhantomModeForSingleCore() const {
428 return is_phantom_mode_for_singlecore; 428 return is_phantom_mode_for_singlecore;
429 } 429 }
430 430
431 void SetIsPhantomModeForSingleCore(bool value) { 431 LTO_NOINLINE void SetIsPhantomModeForSingleCore(bool value) {
432 ASSERT(!is_multicore); 432 ASSERT(!is_multicore);
433 is_phantom_mode_for_singlecore = value; 433 is_phantom_mode_for_singlecore = value;
434 } 434 }
@@ -439,14 +439,14 @@ struct KernelCore::Impl {
439 439
440 static inline thread_local KThread* current_thread{nullptr}; 440 static inline thread_local KThread* current_thread{nullptr};
441 441
442 KThread* GetCurrentEmuThread() { 442 LTO_NOINLINE KThread* GetCurrentEmuThread() {
443 if (!current_thread) { 443 if (!current_thread) {
444 current_thread = GetHostDummyThread(nullptr); 444 current_thread = GetHostDummyThread(nullptr);
445 } 445 }
446 return current_thread; 446 return current_thread;
447 } 447 }
448 448
449 void SetCurrentEmuThread(KThread* thread) { 449 LTO_NOINLINE void SetCurrentEmuThread(KThread* thread) {
450 current_thread = thread; 450 current_thread = thread;
451 } 451 }
452 452
diff --git a/src/core/hle/kernel/svc/svc_transfer_memory.cpp b/src/core/hle/kernel/svc/svc_transfer_memory.cpp
index 7d94e7f09..1f97121b3 100644
--- a/src/core/hle/kernel/svc/svc_transfer_memory.cpp
+++ b/src/core/hle/kernel/svc/svc_transfer_memory.cpp
@@ -71,15 +71,59 @@ Result CreateTransferMemory(Core::System& system, Handle* out, u64 address, u64
71} 71}
72 72
73Result MapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address, uint64_t size, 73Result MapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address, uint64_t size,
74 MemoryPermission owner_perm) { 74 MemoryPermission map_perm) {
75 UNIMPLEMENTED(); 75 // Validate the address/size.
76 R_THROW(ResultNotImplemented); 76 R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress);
77 R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize);
78 R_UNLESS(size > 0, ResultInvalidSize);
79 R_UNLESS((address < address + size), ResultInvalidCurrentMemory);
80
81 // Validate the permission.
82 R_UNLESS(IsValidTransferMemoryPermission(map_perm), ResultInvalidState);
83
84 // Get the transfer memory.
85 KScopedAutoObject trmem = GetCurrentProcess(system.Kernel())
86 .GetHandleTable()
87 .GetObject<KTransferMemory>(trmem_handle);
88 R_UNLESS(trmem.IsNotNull(), ResultInvalidHandle);
89
90 // Verify that the mapping is in range.
91 R_UNLESS(GetCurrentProcess(system.Kernel())
92 .GetPageTable()
93 .CanContain(address, size, KMemoryState::Transfered),
94 ResultInvalidMemoryRegion);
95
96 // Map the transfer memory.
97 R_TRY(trmem->Map(address, size, map_perm));
98
99 // We succeeded.
100 R_SUCCEED();
77} 101}
78 102
79Result UnmapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address, 103Result UnmapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address,
80 uint64_t size) { 104 uint64_t size) {
81 UNIMPLEMENTED(); 105 // Validate the address/size.
82 R_THROW(ResultNotImplemented); 106 R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress);
107 R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize);
108 R_UNLESS(size > 0, ResultInvalidSize);
109 R_UNLESS((address < address + size), ResultInvalidCurrentMemory);
110
111 // Get the transfer memory.
112 KScopedAutoObject trmem = GetCurrentProcess(system.Kernel())
113 .GetHandleTable()
114 .GetObject<KTransferMemory>(trmem_handle);
115 R_UNLESS(trmem.IsNotNull(), ResultInvalidHandle);
116
117 // Verify that the mapping is in range.
118 R_UNLESS(GetCurrentProcess(system.Kernel())
119 .GetPageTable()
120 .CanContain(address, size, KMemoryState::Transfered),
121 ResultInvalidMemoryRegion);
122
123 // Unmap the transfer memory.
124 R_TRY(trmem->Unmap(address, size));
125
126 R_SUCCEED();
83} 127}
84 128
85Result MapTransferMemory64(Core::System& system, Handle trmem_handle, uint64_t address, 129Result MapTransferMemory64(Core::System& system, Handle trmem_handle, uint64_t address,
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index a83622f7c..ac376b55a 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -8,6 +8,7 @@
8#include "common/settings.h" 8#include "common/settings.h"
9#include "common/settings_enums.h" 9#include "common/settings_enums.h"
10#include "core/core.h" 10#include "core/core.h"
11#include "core/core_timing.h"
11#include "core/file_sys/control_metadata.h" 12#include "core/file_sys/control_metadata.h"
12#include "core/file_sys/patch_manager.h" 13#include "core/file_sys/patch_manager.h"
13#include "core/file_sys/registered_cache.h" 14#include "core/file_sys/registered_cache.h"
@@ -19,6 +20,7 @@
19#include "core/hle/service/am/am.h" 20#include "core/hle/service/am/am.h"
20#include "core/hle/service/am/applet_ae.h" 21#include "core/hle/service/am/applet_ae.h"
21#include "core/hle/service/am/applet_oe.h" 22#include "core/hle/service/am/applet_oe.h"
23#include "core/hle/service/am/applets/applet_cabinet.h"
22#include "core/hle/service/am/applets/applet_mii_edit_types.h" 24#include "core/hle/service/am/applets/applet_mii_edit_types.h"
23#include "core/hle/service/am/applets/applet_profile_select.h" 25#include "core/hle/service/am/applets/applet_profile_select.h"
24#include "core/hle/service/am/applets/applet_web_browser.h" 26#include "core/hle/service/am/applets/applet_web_browser.h"
@@ -29,15 +31,17 @@
29#include "core/hle/service/apm/apm_controller.h" 31#include "core/hle/service/apm/apm_controller.h"
30#include "core/hle/service/apm/apm_interface.h" 32#include "core/hle/service/apm/apm_interface.h"
31#include "core/hle/service/bcat/backend/backend.h" 33#include "core/hle/service/bcat/backend/backend.h"
32#include "core/hle/service/caps/caps.h" 34#include "core/hle/service/caps/caps_types.h"
33#include "core/hle/service/filesystem/filesystem.h" 35#include "core/hle/service/filesystem/filesystem.h"
34#include "core/hle/service/ipc_helpers.h" 36#include "core/hle/service/ipc_helpers.h"
35#include "core/hle/service/ns/ns.h" 37#include "core/hle/service/ns/ns.h"
38#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
36#include "core/hle/service/nvnflinger/nvnflinger.h" 39#include "core/hle/service/nvnflinger/nvnflinger.h"
37#include "core/hle/service/pm/pm.h" 40#include "core/hle/service/pm/pm.h"
38#include "core/hle/service/server_manager.h" 41#include "core/hle/service/server_manager.h"
39#include "core/hle/service/sm/sm.h" 42#include "core/hle/service/sm/sm.h"
40#include "core/hle/service/vi/vi.h" 43#include "core/hle/service/vi/vi.h"
44#include "core/hle/service/vi/vi_results.h"
41#include "core/memory.h" 45#include "core/memory.h"
42 46
43namespace Service::AM { 47namespace Service::AM {
@@ -190,7 +194,7 @@ IDisplayController::IDisplayController(Core::System& system_)
190 {4, nullptr, "UpdateCallerAppletCaptureImage"}, 194 {4, nullptr, "UpdateCallerAppletCaptureImage"},
191 {5, nullptr, "GetLastForegroundCaptureImageEx"}, 195 {5, nullptr, "GetLastForegroundCaptureImageEx"},
192 {6, nullptr, "GetLastApplicationCaptureImageEx"}, 196 {6, nullptr, "GetLastApplicationCaptureImageEx"},
193 {7, nullptr, "GetCallerAppletCaptureImageEx"}, 197 {7, &IDisplayController::GetCallerAppletCaptureImageEx, "GetCallerAppletCaptureImageEx"},
194 {8, &IDisplayController::TakeScreenShotOfOwnLayer, "TakeScreenShotOfOwnLayer"}, 198 {8, &IDisplayController::TakeScreenShotOfOwnLayer, "TakeScreenShotOfOwnLayer"},
195 {9, nullptr, "CopyBetweenCaptureBuffers"}, 199 {9, nullptr, "CopyBetweenCaptureBuffers"},
196 {10, nullptr, "AcquireLastApplicationCaptureBuffer"}, 200 {10, nullptr, "AcquireLastApplicationCaptureBuffer"},
@@ -208,8 +212,8 @@ IDisplayController::IDisplayController(Core::System& system_)
208 {23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"}, 212 {23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"},
209 {24, nullptr, "AcquireLastForegroundCaptureSharedBuffer"}, 213 {24, nullptr, "AcquireLastForegroundCaptureSharedBuffer"},
210 {25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"}, 214 {25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"},
211 {26, nullptr, "AcquireCallerAppletCaptureSharedBuffer"}, 215 {26, &IDisplayController::AcquireCallerAppletCaptureSharedBuffer, "AcquireCallerAppletCaptureSharedBuffer"},
212 {27, nullptr, "ReleaseCallerAppletCaptureSharedBuffer"}, 216 {27, &IDisplayController::ReleaseCallerAppletCaptureSharedBuffer, "ReleaseCallerAppletCaptureSharedBuffer"},
213 {28, nullptr, "TakeScreenShotOfOwnLayerEx"}, 217 {28, nullptr, "TakeScreenShotOfOwnLayerEx"},
214 }; 218 };
215 // clang-format on 219 // clang-format on
@@ -219,6 +223,15 @@ IDisplayController::IDisplayController(Core::System& system_)
219 223
220IDisplayController::~IDisplayController() = default; 224IDisplayController::~IDisplayController() = default;
221 225
226void IDisplayController::GetCallerAppletCaptureImageEx(HLERequestContext& ctx) {
227 LOG_WARNING(Service_AM, "(STUBBED) called");
228
229 IPC::ResponseBuilder rb{ctx, 4};
230 rb.Push(ResultSuccess);
231 rb.Push(1u);
232 rb.Push(0);
233}
234
222void IDisplayController::TakeScreenShotOfOwnLayer(HLERequestContext& ctx) { 235void IDisplayController::TakeScreenShotOfOwnLayer(HLERequestContext& ctx) {
223 LOG_WARNING(Service_AM, "(STUBBED) called"); 236 LOG_WARNING(Service_AM, "(STUBBED) called");
224 237
@@ -226,6 +239,22 @@ void IDisplayController::TakeScreenShotOfOwnLayer(HLERequestContext& ctx) {
226 rb.Push(ResultSuccess); 239 rb.Push(ResultSuccess);
227} 240}
228 241
242void IDisplayController::AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) {
243 LOG_WARNING(Service_AM, "(STUBBED) called");
244
245 IPC::ResponseBuilder rb{ctx, 4};
246 rb.Push(ResultSuccess);
247 rb.Push(1U);
248 rb.Push(0);
249}
250
251void IDisplayController::ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) {
252 LOG_WARNING(Service_AM, "(STUBBED) called");
253
254 IPC::ResponseBuilder rb{ctx, 2};
255 rb.Push(ResultSuccess);
256}
257
229IDebugFunctions::IDebugFunctions(Core::System& system_) 258IDebugFunctions::IDebugFunctions(Core::System& system_)
230 : ServiceFramework{system_, "IDebugFunctions"} { 259 : ServiceFramework{system_, "IDebugFunctions"} {
231 // clang-format off 260 // clang-format off
@@ -285,14 +314,14 @@ ISelfController::ISelfController(Core::System& system_, Nvnflinger::Nvnflinger&
285 {20, nullptr, "SetDesirableKeyboardLayout"}, 314 {20, nullptr, "SetDesirableKeyboardLayout"},
286 {21, nullptr, "GetScreenShotProgramId"}, 315 {21, nullptr, "GetScreenShotProgramId"},
287 {40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"}, 316 {40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"},
288 {41, nullptr, "IsSystemBufferSharingEnabled"}, 317 {41, &ISelfController::IsSystemBufferSharingEnabled, "IsSystemBufferSharingEnabled"},
289 {42, nullptr, "GetSystemSharedLayerHandle"}, 318 {42, &ISelfController::GetSystemSharedLayerHandle, "GetSystemSharedLayerHandle"},
290 {43, nullptr, "GetSystemSharedBufferHandle"}, 319 {43, &ISelfController::GetSystemSharedBufferHandle, "GetSystemSharedBufferHandle"},
291 {44, &ISelfController::CreateManagedDisplaySeparableLayer, "CreateManagedDisplaySeparableLayer"}, 320 {44, &ISelfController::CreateManagedDisplaySeparableLayer, "CreateManagedDisplaySeparableLayer"},
292 {45, nullptr, "SetManagedDisplayLayerSeparationMode"}, 321 {45, nullptr, "SetManagedDisplayLayerSeparationMode"},
293 {46, nullptr, "SetRecordingLayerCompositionEnabled"}, 322 {46, nullptr, "SetRecordingLayerCompositionEnabled"},
294 {50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"}, 323 {50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"},
295 {51, nullptr, "ApproveToDisplay"}, 324 {51, &ISelfController::ApproveToDisplay, "ApproveToDisplay"},
296 {60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"}, 325 {60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"},
297 {61, nullptr, "SetMediaPlaybackState"}, 326 {61, nullptr, "SetMediaPlaybackState"},
298 {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"}, 327 {62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"},
@@ -491,6 +520,50 @@ void ISelfController::CreateManagedDisplayLayer(HLERequestContext& ctx) {
491 rb.Push(*layer_id); 520 rb.Push(*layer_id);
492} 521}
493 522
523void ISelfController::IsSystemBufferSharingEnabled(HLERequestContext& ctx) {
524 LOG_WARNING(Service_AM, "(STUBBED) called");
525
526 IPC::ResponseBuilder rb{ctx, 2};
527 rb.Push(this->EnsureBufferSharingEnabled());
528}
529
530void ISelfController::GetSystemSharedLayerHandle(HLERequestContext& ctx) {
531 LOG_WARNING(Service_AM, "(STUBBED) called");
532
533 IPC::ResponseBuilder rb{ctx, 6};
534 rb.Push(this->EnsureBufferSharingEnabled());
535 rb.Push<s64>(system_shared_buffer_id);
536 rb.Push<s64>(system_shared_layer_id);
537}
538
539void ISelfController::GetSystemSharedBufferHandle(HLERequestContext& ctx) {
540 LOG_WARNING(Service_AM, "(STUBBED) called");
541
542 IPC::ResponseBuilder rb{ctx, 4};
543 rb.Push(this->EnsureBufferSharingEnabled());
544 rb.Push<s64>(system_shared_buffer_id);
545}
546
547Result ISelfController::EnsureBufferSharingEnabled() {
548 if (buffer_sharing_enabled) {
549 return ResultSuccess;
550 }
551
552 if (system.GetAppletManager().GetCurrentAppletId() <= Applets::AppletId::Application) {
553 return VI::ResultOperationFailed;
554 }
555
556 const auto display_id = nvnflinger.OpenDisplay("Default");
557 const auto result = nvnflinger.GetSystemBufferManager().Initialize(
558 &system_shared_buffer_id, &system_shared_layer_id, *display_id);
559
560 if (result.IsSuccess()) {
561 buffer_sharing_enabled = true;
562 }
563
564 return result;
565}
566
494void ISelfController::CreateManagedDisplaySeparableLayer(HLERequestContext& ctx) { 567void ISelfController::CreateManagedDisplaySeparableLayer(HLERequestContext& ctx) {
495 LOG_WARNING(Service_AM, "(STUBBED) called"); 568 LOG_WARNING(Service_AM, "(STUBBED) called");
496 569
@@ -516,6 +589,13 @@ void ISelfController::SetHandlesRequestToDisplay(HLERequestContext& ctx) {
516 rb.Push(ResultSuccess); 589 rb.Push(ResultSuccess);
517} 590}
518 591
592void ISelfController::ApproveToDisplay(HLERequestContext& ctx) {
593 LOG_WARNING(Service_AM, "(STUBBED) called");
594
595 IPC::ResponseBuilder rb{ctx, 2};
596 rb.Push(ResultSuccess);
597}
598
519void ISelfController::SetIdleTimeDetectionExtension(HLERequestContext& ctx) { 599void ISelfController::SetIdleTimeDetectionExtension(HLERequestContext& ctx) {
520 IPC::RequestParser rp{ctx}; 600 IPC::RequestParser rp{ctx};
521 idle_time_detection_extension = rp.Pop<u32>(); 601 idle_time_detection_extension = rp.Pop<u32>();
@@ -684,9 +764,70 @@ void AppletMessageQueue::OperationModeChanged() {
684 on_operation_mode_changed->Signal(); 764 on_operation_mode_changed->Signal();
685} 765}
686 766
767ILockAccessor::ILockAccessor(Core::System& system_)
768 : ServiceFramework{system_, "ILockAccessor"}, service_context{system_, "ILockAccessor"} {
769 // clang-format off
770 static const FunctionInfo functions[] = {
771 {1, &ILockAccessor::TryLock, "TryLock"},
772 {2, &ILockAccessor::Unlock, "Unlock"},
773 {3, &ILockAccessor::GetEvent, "GetEvent"},
774 {4,&ILockAccessor::IsLocked, "IsLocked"},
775 };
776 // clang-format on
777
778 RegisterHandlers(functions);
779
780 lock_event = service_context.CreateEvent("ILockAccessor::LockEvent");
781}
782
783ILockAccessor::~ILockAccessor() = default;
784
785void ILockAccessor::TryLock(HLERequestContext& ctx) {
786 IPC::RequestParser rp{ctx};
787 const auto return_handle = rp.Pop<bool>();
788
789 LOG_WARNING(Service_AM, "(STUBBED) called, return_handle={}", return_handle);
790
791 // TODO: When return_handle is true this function should return the lock handle
792
793 is_locked = true;
794
795 IPC::ResponseBuilder rb{ctx, 3};
796 rb.Push(ResultSuccess);
797 rb.Push<u8>(is_locked);
798}
799
800void ILockAccessor::Unlock(HLERequestContext& ctx) {
801 LOG_INFO(Service_AM, "called");
802
803 is_locked = false;
804
805 IPC::ResponseBuilder rb{ctx, 2};
806 rb.Push(ResultSuccess);
807}
808
809void ILockAccessor::GetEvent(HLERequestContext& ctx) {
810 LOG_INFO(Service_AM, "called");
811
812 lock_event->Signal();
813
814 IPC::ResponseBuilder rb{ctx, 2, 1};
815 rb.Push(ResultSuccess);
816 rb.PushCopyObjects(lock_event->GetReadableEvent());
817}
818
819void ILockAccessor::IsLocked(HLERequestContext& ctx) {
820 LOG_INFO(Service_AM, "called");
821
822 IPC::ResponseBuilder rb{ctx, 2};
823 rb.Push(ResultSuccess);
824 rb.Push<u8>(is_locked);
825}
826
687ICommonStateGetter::ICommonStateGetter(Core::System& system_, 827ICommonStateGetter::ICommonStateGetter(Core::System& system_,
688 std::shared_ptr<AppletMessageQueue> msg_queue_) 828 std::shared_ptr<AppletMessageQueue> msg_queue_)
689 : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)} { 829 : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)},
830 service_context{system_, "ICommonStateGetter"} {
690 // clang-format off 831 // clang-format off
691 static const FunctionInfo functions[] = { 832 static const FunctionInfo functions[] = {
692 {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"}, 833 {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"},
@@ -699,14 +840,14 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
699 {7, nullptr, "GetCradleStatus"}, 840 {7, nullptr, "GetCradleStatus"},
700 {8, &ICommonStateGetter::GetBootMode, "GetBootMode"}, 841 {8, &ICommonStateGetter::GetBootMode, "GetBootMode"},
701 {9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"}, 842 {9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"},
702 {10, nullptr, "RequestToAcquireSleepLock"}, 843 {10, &ICommonStateGetter::RequestToAcquireSleepLock, "RequestToAcquireSleepLock"},
703 {11, nullptr, "ReleaseSleepLock"}, 844 {11, nullptr, "ReleaseSleepLock"},
704 {12, nullptr, "ReleaseSleepLockTransiently"}, 845 {12, nullptr, "ReleaseSleepLockTransiently"},
705 {13, nullptr, "GetAcquiredSleepLockEvent"}, 846 {13, &ICommonStateGetter::GetAcquiredSleepLockEvent, "GetAcquiredSleepLockEvent"},
706 {14, nullptr, "GetWakeupCount"}, 847 {14, nullptr, "GetWakeupCount"},
707 {20, nullptr, "PushToGeneralChannel"}, 848 {20, nullptr, "PushToGeneralChannel"},
708 {30, nullptr, "GetHomeButtonReaderLockAccessor"}, 849 {30, nullptr, "GetHomeButtonReaderLockAccessor"},
709 {31, nullptr, "GetReaderLockAccessorEx"}, 850 {31, &ICommonStateGetter::GetReaderLockAccessorEx, "GetReaderLockAccessorEx"},
710 {32, nullptr, "GetWriterLockAccessorEx"}, 851 {32, nullptr, "GetWriterLockAccessorEx"},
711 {40, nullptr, "GetCradleFwVersion"}, 852 {40, nullptr, "GetCradleFwVersion"},
712 {50, &ICommonStateGetter::IsVrModeEnabled, "IsVrModeEnabled"}, 853 {50, &ICommonStateGetter::IsVrModeEnabled, "IsVrModeEnabled"},
@@ -724,7 +865,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
724 {65, nullptr, "GetApplicationIdByContentActionName"}, 865 {65, nullptr, "GetApplicationIdByContentActionName"},
725 {66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"}, 866 {66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"},
726 {67, nullptr, "CancelCpuBoostMode"}, 867 {67, nullptr, "CancelCpuBoostMode"},
727 {68, nullptr, "GetBuiltInDisplayType"}, 868 {68, &ICommonStateGetter::GetBuiltInDisplayType, "GetBuiltInDisplayType"},
728 {80, &ICommonStateGetter::PerformSystemButtonPressingIfInFocus, "PerformSystemButtonPressingIfInFocus"}, 869 {80, &ICommonStateGetter::PerformSystemButtonPressingIfInFocus, "PerformSystemButtonPressingIfInFocus"},
729 {90, nullptr, "SetPerformanceConfigurationChangedNotification"}, 870 {90, nullptr, "SetPerformanceConfigurationChangedNotification"},
730 {91, nullptr, "GetCurrentPerformanceConfiguration"}, 871 {91, nullptr, "GetCurrentPerformanceConfiguration"},
@@ -745,6 +886,8 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
745 886
746 RegisterHandlers(functions); 887 RegisterHandlers(functions);
747 888
889 sleep_lock_event = service_context.CreateEvent("ICommonStateGetter::SleepLockEvent");
890
748 // Configure applets to be in foreground state 891 // Configure applets to be in foreground state
749 msg_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); 892 msg_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
750 msg_queue->PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground); 893 msg_queue->PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
@@ -793,6 +936,36 @@ void ICommonStateGetter::GetCurrentFocusState(HLERequestContext& ctx) {
793 rb.Push(static_cast<u8>(FocusState::InFocus)); 936 rb.Push(static_cast<u8>(FocusState::InFocus));
794} 937}
795 938
939void ICommonStateGetter::RequestToAcquireSleepLock(HLERequestContext& ctx) {
940 LOG_WARNING(Service_AM, "(STUBBED) called");
941
942 // Sleep lock is acquired immediately.
943 sleep_lock_event->Signal();
944
945 IPC::ResponseBuilder rb{ctx, 2};
946 rb.Push(ResultSuccess);
947}
948
949void ICommonStateGetter::GetReaderLockAccessorEx(HLERequestContext& ctx) {
950 IPC::RequestParser rp{ctx};
951 const auto unknown = rp.Pop<u32>();
952
953 LOG_INFO(Service_AM, "called, unknown={}", unknown);
954
955 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
956
957 rb.Push(ResultSuccess);
958 rb.PushIpcInterface<ILockAccessor>(system);
959}
960
961void ICommonStateGetter::GetAcquiredSleepLockEvent(HLERequestContext& ctx) {
962 LOG_WARNING(Service_AM, "called");
963
964 IPC::ResponseBuilder rb{ctx, 2, 1};
965 rb.Push(ResultSuccess);
966 rb.PushCopyObjects(sleep_lock_event->GetReadableEvent());
967}
968
796void ICommonStateGetter::IsVrModeEnabled(HLERequestContext& ctx) { 969void ICommonStateGetter::IsVrModeEnabled(HLERequestContext& ctx) {
797 LOG_DEBUG(Service_AM, "called"); 970 LOG_DEBUG(Service_AM, "called");
798 971
@@ -869,6 +1042,14 @@ void ICommonStateGetter::SetCpuBoostMode(HLERequestContext& ctx) {
869 apm_sys->SetCpuBoostMode(ctx); 1042 apm_sys->SetCpuBoostMode(ctx);
870} 1043}
871 1044
1045void ICommonStateGetter::GetBuiltInDisplayType(HLERequestContext& ctx) {
1046 LOG_WARNING(Service_AM, "(STUBBED) called");
1047
1048 IPC::ResponseBuilder rb{ctx, 3};
1049 rb.Push(ResultSuccess);
1050 rb.Push(0);
1051}
1052
872void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx) { 1053void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx) {
873 IPC::RequestParser rp{ctx}; 1054 IPC::RequestParser rp{ctx};
874 const auto system_button{rp.PopEnum<SystemButtonType>()}; 1055 const auto system_button{rp.PopEnum<SystemButtonType>()};
@@ -1385,7 +1566,19 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
1385 // clang-format on 1566 // clang-format on
1386 RegisterHandlers(functions); 1567 RegisterHandlers(functions);
1387 1568
1388 PushInShowMiiEditData(); 1569 switch (system.GetAppletManager().GetCurrentAppletId()) {
1570 case Applets::AppletId::Cabinet:
1571 PushInShowCabinetData();
1572 break;
1573 case Applets::AppletId::MiiEdit:
1574 PushInShowMiiEditData();
1575 break;
1576 case Applets::AppletId::PhotoViewer:
1577 PushInShowAlbum();
1578 break;
1579 default:
1580 break;
1581 }
1389} 1582}
1390 1583
1391ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default; 1584ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default;
@@ -1431,7 +1624,7 @@ void ILibraryAppletSelfAccessor::GetLibraryAppletInfo(HLERequestContext& ctx) {
1431 LOG_WARNING(Service_AM, "(STUBBED) called"); 1624 LOG_WARNING(Service_AM, "(STUBBED) called");
1432 1625
1433 const LibraryAppletInfo applet_info{ 1626 const LibraryAppletInfo applet_info{
1434 .applet_id = Applets::AppletId::MiiEdit, 1627 .applet_id = system.GetAppletManager().GetCurrentAppletId(),
1435 .library_applet_mode = Applets::LibraryAppletMode::AllForeground, 1628 .library_applet_mode = Applets::LibraryAppletMode::AllForeground,
1436 }; 1629 };
1437 1630
@@ -1459,6 +1652,52 @@ void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext&
1459 rb.PushRaw(applet_info); 1652 rb.PushRaw(applet_info);
1460} 1653}
1461 1654
1655void ILibraryAppletSelfAccessor::PushInShowAlbum() {
1656 const Applets::CommonArguments arguments{
1657 .arguments_version = Applets::CommonArgumentVersion::Version3,
1658 .size = Applets::CommonArgumentSize::Version3,
1659 .library_version = 1,
1660 .theme_color = Applets::ThemeColor::BasicBlack,
1661 .play_startup_sound = true,
1662 .system_tick = system.CoreTiming().GetClockTicks(),
1663 };
1664
1665 std::vector<u8> argument_data(sizeof(arguments));
1666 std::vector<u8> settings_data{2};
1667 std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
1668 queue_data.emplace_back(std::move(argument_data));
1669 queue_data.emplace_back(std::move(settings_data));
1670}
1671
1672void ILibraryAppletSelfAccessor::PushInShowCabinetData() {
1673 const Applets::CommonArguments arguments{
1674 .arguments_version = Applets::CommonArgumentVersion::Version3,
1675 .size = Applets::CommonArgumentSize::Version3,
1676 .library_version = static_cast<u32>(Applets::CabinetAppletVersion::Version1),
1677 .theme_color = Applets::ThemeColor::BasicBlack,
1678 .play_startup_sound = true,
1679 .system_tick = system.CoreTiming().GetClockTicks(),
1680 };
1681
1682 const Applets::StartParamForAmiiboSettings amiibo_settings{
1683 .param_1 = 0,
1684 .applet_mode = system.GetAppletManager().GetCabinetMode(),
1685 .flags = Applets::CabinetFlags::None,
1686 .amiibo_settings_1 = 0,
1687 .device_handle = 0,
1688 .tag_info{},
1689 .register_info{},
1690 .amiibo_settings_3{},
1691 };
1692
1693 std::vector<u8> argument_data(sizeof(arguments));
1694 std::vector<u8> settings_data(sizeof(amiibo_settings));
1695 std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
1696 std::memcpy(settings_data.data(), &amiibo_settings, sizeof(amiibo_settings));
1697 queue_data.emplace_back(std::move(argument_data));
1698 queue_data.emplace_back(std::move(settings_data));
1699}
1700
1462void ILibraryAppletSelfAccessor::PushInShowMiiEditData() { 1701void ILibraryAppletSelfAccessor::PushInShowMiiEditData() {
1463 struct MiiEditV3 { 1702 struct MiiEditV3 {
1464 Applets::MiiEditAppletInputCommon common; 1703 Applets::MiiEditAppletInputCommon common;
@@ -2235,7 +2474,7 @@ void IProcessWindingController::GetLaunchReason(HLERequestContext& ctx) {
2235} 2474}
2236 2475
2237void IProcessWindingController::OpenCallingLibraryApplet(HLERequestContext& ctx) { 2476void IProcessWindingController::OpenCallingLibraryApplet(HLERequestContext& ctx) {
2238 const auto applet_id = Applets::AppletId::MiiEdit; 2477 const auto applet_id = system.GetAppletManager().GetCurrentAppletId();
2239 const auto applet_mode = Applets::LibraryAppletMode::AllForeground; 2478 const auto applet_mode = Applets::LibraryAppletMode::AllForeground;
2240 2479
2241 LOG_WARNING(Service_AM, "(STUBBED) called with applet_id={:08X}, applet_mode={:08X}", applet_id, 2480 LOG_WARNING(Service_AM, "(STUBBED) called with applet_id={:08X}, applet_mode={:08X}", applet_id,
@@ -2256,4 +2495,5 @@ void IProcessWindingController::OpenCallingLibraryApplet(HLERequestContext& ctx)
2256 rb.Push(ResultSuccess); 2495 rb.Push(ResultSuccess);
2257 rb.PushIpcInterface<ILibraryAppletAccessor>(system, applet); 2496 rb.PushIpcInterface<ILibraryAppletAccessor>(system, applet);
2258} 2497}
2498
2259} // namespace Service::AM 2499} // namespace Service::AM
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 5b97eb5e3..4a045cfd4 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -122,7 +122,10 @@ public:
122 ~IDisplayController() override; 122 ~IDisplayController() override;
123 123
124private: 124private:
125 void GetCallerAppletCaptureImageEx(HLERequestContext& ctx);
125 void TakeScreenShotOfOwnLayer(HLERequestContext& ctx); 126 void TakeScreenShotOfOwnLayer(HLERequestContext& ctx);
127 void AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx);
128 void ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx);
126}; 129};
127 130
128class IDebugFunctions final : public ServiceFramework<IDebugFunctions> { 131class IDebugFunctions final : public ServiceFramework<IDebugFunctions> {
@@ -150,9 +153,13 @@ private:
150 void SetRestartMessageEnabled(HLERequestContext& ctx); 153 void SetRestartMessageEnabled(HLERequestContext& ctx);
151 void SetOutOfFocusSuspendingEnabled(HLERequestContext& ctx); 154 void SetOutOfFocusSuspendingEnabled(HLERequestContext& ctx);
152 void SetAlbumImageOrientation(HLERequestContext& ctx); 155 void SetAlbumImageOrientation(HLERequestContext& ctx);
156 void IsSystemBufferSharingEnabled(HLERequestContext& ctx);
157 void GetSystemSharedBufferHandle(HLERequestContext& ctx);
158 void GetSystemSharedLayerHandle(HLERequestContext& ctx);
153 void CreateManagedDisplayLayer(HLERequestContext& ctx); 159 void CreateManagedDisplayLayer(HLERequestContext& ctx);
154 void CreateManagedDisplaySeparableLayer(HLERequestContext& ctx); 160 void CreateManagedDisplaySeparableLayer(HLERequestContext& ctx);
155 void SetHandlesRequestToDisplay(HLERequestContext& ctx); 161 void SetHandlesRequestToDisplay(HLERequestContext& ctx);
162 void ApproveToDisplay(HLERequestContext& ctx);
156 void SetIdleTimeDetectionExtension(HLERequestContext& ctx); 163 void SetIdleTimeDetectionExtension(HLERequestContext& ctx);
157 void GetIdleTimeDetectionExtension(HLERequestContext& ctx); 164 void GetIdleTimeDetectionExtension(HLERequestContext& ctx);
158 void ReportUserIsActive(HLERequestContext& ctx); 165 void ReportUserIsActive(HLERequestContext& ctx);
@@ -164,6 +171,8 @@ private:
164 void SaveCurrentScreenshot(HLERequestContext& ctx); 171 void SaveCurrentScreenshot(HLERequestContext& ctx);
165 void SetRecordVolumeMuted(HLERequestContext& ctx); 172 void SetRecordVolumeMuted(HLERequestContext& ctx);
166 173
174 Result EnsureBufferSharingEnabled();
175
167 enum class ScreenshotPermission : u32 { 176 enum class ScreenshotPermission : u32 {
168 Inherit = 0, 177 Inherit = 0,
169 Enable = 1, 178 Enable = 1,
@@ -179,10 +188,30 @@ private:
179 188
180 u32 idle_time_detection_extension = 0; 189 u32 idle_time_detection_extension = 0;
181 u64 num_fatal_sections_entered = 0; 190 u64 num_fatal_sections_entered = 0;
191 u64 system_shared_buffer_id = 0;
192 u64 system_shared_layer_id = 0;
182 bool is_auto_sleep_disabled = false; 193 bool is_auto_sleep_disabled = false;
194 bool buffer_sharing_enabled = false;
183 ScreenshotPermission screenshot_permission = ScreenshotPermission::Inherit; 195 ScreenshotPermission screenshot_permission = ScreenshotPermission::Inherit;
184}; 196};
185 197
198class ILockAccessor final : public ServiceFramework<ILockAccessor> {
199public:
200 explicit ILockAccessor(Core::System& system_);
201 ~ILockAccessor() override;
202
203private:
204 void TryLock(HLERequestContext& ctx);
205 void Unlock(HLERequestContext& ctx);
206 void GetEvent(HLERequestContext& ctx);
207 void IsLocked(HLERequestContext& ctx);
208
209 bool is_locked{};
210
211 Kernel::KEvent* lock_event;
212 KernelHelpers::ServiceContext service_context;
213};
214
186class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> { 215class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
187public: 216public:
188 explicit ICommonStateGetter(Core::System& system_, 217 explicit ICommonStateGetter(Core::System& system_,
@@ -223,6 +252,9 @@ private:
223 void GetEventHandle(HLERequestContext& ctx); 252 void GetEventHandle(HLERequestContext& ctx);
224 void ReceiveMessage(HLERequestContext& ctx); 253 void ReceiveMessage(HLERequestContext& ctx);
225 void GetCurrentFocusState(HLERequestContext& ctx); 254 void GetCurrentFocusState(HLERequestContext& ctx);
255 void RequestToAcquireSleepLock(HLERequestContext& ctx);
256 void GetAcquiredSleepLockEvent(HLERequestContext& ctx);
257 void GetReaderLockAccessorEx(HLERequestContext& ctx);
226 void GetDefaultDisplayResolutionChangeEvent(HLERequestContext& ctx); 258 void GetDefaultDisplayResolutionChangeEvent(HLERequestContext& ctx);
227 void GetOperationMode(HLERequestContext& ctx); 259 void GetOperationMode(HLERequestContext& ctx);
228 void GetPerformanceMode(HLERequestContext& ctx); 260 void GetPerformanceMode(HLERequestContext& ctx);
@@ -234,12 +266,15 @@ private:
234 void EndVrModeEx(HLERequestContext& ctx); 266 void EndVrModeEx(HLERequestContext& ctx);
235 void GetDefaultDisplayResolution(HLERequestContext& ctx); 267 void GetDefaultDisplayResolution(HLERequestContext& ctx);
236 void SetCpuBoostMode(HLERequestContext& ctx); 268 void SetCpuBoostMode(HLERequestContext& ctx);
269 void GetBuiltInDisplayType(HLERequestContext& ctx);
237 void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx); 270 void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx);
238 void GetSettingsPlatformRegion(HLERequestContext& ctx); 271 void GetSettingsPlatformRegion(HLERequestContext& ctx);
239 void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx); 272 void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx);
240 273
241 std::shared_ptr<AppletMessageQueue> msg_queue; 274 std::shared_ptr<AppletMessageQueue> msg_queue;
242 bool vr_mode_state{}; 275 bool vr_mode_state{};
276 Kernel::KEvent* sleep_lock_event;
277 KernelHelpers::ServiceContext service_context;
243}; 278};
244 279
245class IStorageImpl { 280class IStorageImpl {
@@ -311,6 +346,8 @@ private:
311 void ExitProcessAndReturn(HLERequestContext& ctx); 346 void ExitProcessAndReturn(HLERequestContext& ctx);
312 void GetCallerAppletIdentityInfo(HLERequestContext& ctx); 347 void GetCallerAppletIdentityInfo(HLERequestContext& ctx);
313 348
349 void PushInShowAlbum();
350 void PushInShowCabinetData();
314 void PushInShowMiiEditData(); 351 void PushInShowMiiEditData();
315 352
316 std::deque<std::vector<u8>> queue_data; 353 std::deque<std::vector<u8>> queue_data;
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
index eb12312cc..e30e6478a 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -28,8 +28,8 @@ public:
28 {11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"}, 28 {11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
29 {20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"}, 29 {20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"},
30 {21, &ILibraryAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"}, 30 {21, &ILibraryAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"},
31 {22, nullptr, "GetHomeMenuFunctions"}, 31 {22, &ILibraryAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"},
32 {23, nullptr, "GetGlobalStateController"}, 32 {23, &ILibraryAppletProxy::GetGlobalStateController, "GetGlobalStateController"},
33 {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, 33 {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
34 }; 34 };
35 // clang-format on 35 // clang-format on
@@ -110,6 +110,22 @@ private:
110 rb.PushIpcInterface<IAppletCommonFunctions>(system); 110 rb.PushIpcInterface<IAppletCommonFunctions>(system);
111 } 111 }
112 112
113 void GetHomeMenuFunctions(HLERequestContext& ctx) {
114 LOG_DEBUG(Service_AM, "called");
115
116 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
117 rb.Push(ResultSuccess);
118 rb.PushIpcInterface<IHomeMenuFunctions>(system);
119 }
120
121 void GetGlobalStateController(HLERequestContext& ctx) {
122 LOG_DEBUG(Service_AM, "called");
123
124 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
125 rb.Push(ResultSuccess);
126 rb.PushIpcInterface<IGlobalStateController>(system);
127 }
128
113 void GetDebugFunctions(HLERequestContext& ctx) { 129 void GetDebugFunctions(HLERequestContext& ctx) {
114 LOG_DEBUG(Service_AM, "called"); 130 LOG_DEBUG(Service_AM, "called");
115 131
diff --git a/src/core/hle/service/am/applets/applet_cabinet.h b/src/core/hle/service/am/applets/applet_cabinet.h
index b56427021..f498796f7 100644
--- a/src/core/hle/service/am/applets/applet_cabinet.h
+++ b/src/core/hle/service/am/applets/applet_cabinet.h
@@ -29,6 +29,15 @@ enum class CabinetAppletVersion : u32 {
29 Version1 = 0x1, 29 Version1 = 0x1,
30}; 30};
31 31
32enum class CabinetFlags : u8 {
33 None = 0,
34 DeviceHandle = 1 << 0,
35 TagInfo = 1 << 1,
36 RegisterInfo = 1 << 2,
37 All = DeviceHandle | TagInfo | RegisterInfo,
38};
39DECLARE_ENUM_FLAG_OPERATORS(CabinetFlags)
40
32enum class CabinetResult : u8 { 41enum class CabinetResult : u8 {
33 Cancel = 0, 42 Cancel = 0,
34 TagInfo = 1 << 1, 43 TagInfo = 1 << 1,
@@ -51,7 +60,7 @@ static_assert(sizeof(AmiiboSettingsStartParam) == 0x30,
51struct StartParamForAmiiboSettings { 60struct StartParamForAmiiboSettings {
52 u8 param_1; 61 u8 param_1;
53 Service::NFP::CabinetMode applet_mode; 62 Service::NFP::CabinetMode applet_mode;
54 u8 flags; 63 CabinetFlags flags;
55 u8 amiibo_settings_1; 64 u8 amiibo_settings_1;
56 u64 device_handle; 65 u64 device_handle;
57 Service::NFP::TagInfo tag_info; 66 Service::NFP::TagInfo tag_info;
diff --git a/src/core/hle/service/am/applets/applet_error.cpp b/src/core/hle/service/am/applets/applet_error.cpp
index b46ea840c..5d17c353f 100644
--- a/src/core/hle/service/am/applets/applet_error.cpp
+++ b/src/core/hle/service/am/applets/applet_error.cpp
@@ -138,6 +138,10 @@ void Error::Initialize() {
138 CopyArgumentData(data, args->application_error); 138 CopyArgumentData(data, args->application_error);
139 error_code = Result(args->application_error.error_code); 139 error_code = Result(args->application_error.error_code);
140 break; 140 break;
141 case ErrorAppletMode::ShowErrorPctl:
142 CopyArgumentData(data, args->error_record);
143 error_code = Decode64BitError(args->error_record.error_code_64);
144 break;
141 case ErrorAppletMode::ShowErrorRecord: 145 case ErrorAppletMode::ShowErrorRecord:
142 CopyArgumentData(data, args->error_record); 146 CopyArgumentData(data, args->error_record);
143 error_code = Decode64BitError(args->error_record.error_code_64); 147 error_code = Decode64BitError(args->error_record.error_code_64);
@@ -191,6 +195,7 @@ void Error::Execute() {
191 frontend.ShowCustomErrorText(error_code, main_text_string, detail_text_string, callback); 195 frontend.ShowCustomErrorText(error_code, main_text_string, detail_text_string, callback);
192 break; 196 break;
193 } 197 }
198 case ErrorAppletMode::ShowErrorPctl:
194 case ErrorAppletMode::ShowErrorRecord: 199 case ErrorAppletMode::ShowErrorRecord:
195 reporter.SaveErrorReport(title_id, error_code, 200 reporter.SaveErrorReport(title_id, error_code,
196 fmt::format("{:016X}", args->error_record.posix_time)); 201 fmt::format("{:016X}", args->error_record.posix_time));
diff --git a/src/core/hle/service/am/applets/applet_general_backend.cpp b/src/core/hle/service/am/applets/applet_general_backend.cpp
index 8b352020e..c0032f652 100644
--- a/src/core/hle/service/am/applets/applet_general_backend.cpp
+++ b/src/core/hle/service/am/applets/applet_general_backend.cpp
@@ -223,9 +223,9 @@ void StubApplet::Initialize() {
223 223
224 const auto data = broker.PeekDataToAppletForDebug(); 224 const auto data = broker.PeekDataToAppletForDebug();
225 system.GetReporter().SaveUnimplementedAppletReport( 225 system.GetReporter().SaveUnimplementedAppletReport(
226 static_cast<u32>(id), common_args.arguments_version, common_args.library_version, 226 static_cast<u32>(id), static_cast<u32>(common_args.arguments_version),
227 common_args.theme_color, common_args.play_startup_sound, common_args.system_tick, 227 common_args.library_version, static_cast<u32>(common_args.theme_color),
228 data.normal, data.interactive); 228 common_args.play_startup_sound, common_args.system_tick, data.normal, data.interactive);
229 229
230 LogCurrentStorage(broker, "Initialize"); 230 LogCurrentStorage(broker, "Initialize");
231} 231}
diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index 10afbc2da..89d5434af 100644
--- a/src/core/hle/service/am/applets/applets.cpp
+++ b/src/core/hle/service/am/applets/applets.cpp
@@ -199,6 +199,14 @@ const AppletFrontendSet& AppletManager::GetAppletFrontendSet() const {
199 return frontend; 199 return frontend;
200} 200}
201 201
202NFP::CabinetMode AppletManager::GetCabinetMode() const {
203 return cabinet_mode;
204}
205
206AppletId AppletManager::GetCurrentAppletId() const {
207 return current_applet_id;
208}
209
202void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) { 210void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
203 if (set.cabinet != nullptr) { 211 if (set.cabinet != nullptr) {
204 frontend.cabinet = std::move(set.cabinet); 212 frontend.cabinet = std::move(set.cabinet);
@@ -237,6 +245,14 @@ void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
237 } 245 }
238} 246}
239 247
248void AppletManager::SetCabinetMode(NFP::CabinetMode mode) {
249 cabinet_mode = mode;
250}
251
252void AppletManager::SetCurrentAppletId(AppletId applet_id) {
253 current_applet_id = applet_id;
254}
255
240void AppletManager::SetDefaultAppletFrontendSet() { 256void AppletManager::SetDefaultAppletFrontendSet() {
241 ClearAll(); 257 ClearAll();
242 SetDefaultAppletsIfMissing(); 258 SetDefaultAppletsIfMissing();
diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h
index 12f374199..f02bbc450 100644
--- a/src/core/hle/service/am/applets/applets.h
+++ b/src/core/hle/service/am/applets/applets.h
@@ -34,6 +34,10 @@ class KEvent;
34class KReadableEvent; 34class KReadableEvent;
35} // namespace Kernel 35} // namespace Kernel
36 36
37namespace Service::NFP {
38enum class CabinetMode : u8;
39} // namespace Service::NFP
40
37namespace Service::AM { 41namespace Service::AM {
38 42
39class IStorage; 43class IStorage;
@@ -41,6 +45,8 @@ class IStorage;
41namespace Applets { 45namespace Applets {
42 46
43enum class AppletId : u32 { 47enum class AppletId : u32 {
48 None = 0x00,
49 Application = 0x01,
44 OverlayDisplay = 0x02, 50 OverlayDisplay = 0x02,
45 QLaunch = 0x03, 51 QLaunch = 0x03,
46 Starter = 0x04, 52 Starter = 0x04,
@@ -71,6 +77,32 @@ enum class LibraryAppletMode : u32 {
71 AllForegroundInitiallyHidden = 4, 77 AllForegroundInitiallyHidden = 4,
72}; 78};
73 79
80enum class CommonArgumentVersion : u32 {
81 Version0,
82 Version1,
83 Version2,
84 Version3,
85};
86
87enum class CommonArgumentSize : u32 {
88 Version3 = 0x20,
89};
90
91enum class ThemeColor : u32 {
92 BasicWhite = 0,
93 BasicBlack = 3,
94};
95
96struct CommonArguments {
97 CommonArgumentVersion arguments_version;
98 CommonArgumentSize size;
99 u32 library_version;
100 ThemeColor theme_color;
101 bool play_startup_sound;
102 u64_le system_tick;
103};
104static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size.");
105
74class AppletDataBroker final { 106class AppletDataBroker final {
75public: 107public:
76 explicit AppletDataBroker(Core::System& system_, LibraryAppletMode applet_mode_); 108 explicit AppletDataBroker(Core::System& system_, LibraryAppletMode applet_mode_);
@@ -161,16 +193,6 @@ public:
161 } 193 }
162 194
163protected: 195protected:
164 struct CommonArguments {
165 u32_le arguments_version;
166 u32_le size;
167 u32_le library_version;
168 u32_le theme_color;
169 bool play_startup_sound;
170 u64_le system_tick;
171 };
172 static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size.");
173
174 CommonArguments common_args{}; 196 CommonArguments common_args{};
175 AppletDataBroker broker; 197 AppletDataBroker broker;
176 LibraryAppletMode applet_mode; 198 LibraryAppletMode applet_mode;
@@ -219,8 +241,12 @@ public:
219 ~AppletManager(); 241 ~AppletManager();
220 242
221 const AppletFrontendSet& GetAppletFrontendSet() const; 243 const AppletFrontendSet& GetAppletFrontendSet() const;
244 NFP::CabinetMode GetCabinetMode() const;
245 AppletId GetCurrentAppletId() const;
222 246
223 void SetAppletFrontendSet(AppletFrontendSet set); 247 void SetAppletFrontendSet(AppletFrontendSet set);
248 void SetCabinetMode(NFP::CabinetMode mode);
249 void SetCurrentAppletId(AppletId applet_id);
224 void SetDefaultAppletFrontendSet(); 250 void SetDefaultAppletFrontendSet();
225 void SetDefaultAppletsIfMissing(); 251 void SetDefaultAppletsIfMissing();
226 void ClearAll(); 252 void ClearAll();
@@ -228,6 +254,9 @@ public:
228 std::shared_ptr<Applet> GetApplet(AppletId id, LibraryAppletMode mode) const; 254 std::shared_ptr<Applet> GetApplet(AppletId id, LibraryAppletMode mode) const;
229 255
230private: 256private:
257 AppletId current_applet_id{};
258 NFP::CabinetMode cabinet_mode{};
259
231 AppletFrontendSet frontend; 260 AppletFrontendSet frontend;
232 Core::System& system; 261 Core::System& system;
233}; 262};
diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp
index 610fe9940..31dd98140 100644
--- a/src/core/hle/service/caps/caps.cpp
+++ b/src/core/hle/service/caps/caps.cpp
@@ -4,6 +4,7 @@
4#include "core/hle/service/caps/caps.h" 4#include "core/hle/service/caps/caps.h"
5#include "core/hle/service/caps/caps_a.h" 5#include "core/hle/service/caps/caps_a.h"
6#include "core/hle/service/caps/caps_c.h" 6#include "core/hle/service/caps/caps_c.h"
7#include "core/hle/service/caps/caps_manager.h"
7#include "core/hle/service/caps/caps_sc.h" 8#include "core/hle/service/caps/caps_sc.h"
8#include "core/hle/service/caps/caps_ss.h" 9#include "core/hle/service/caps/caps_ss.h"
9#include "core/hle/service/caps/caps_su.h" 10#include "core/hle/service/caps/caps_su.h"
@@ -15,13 +16,21 @@ namespace Service::Capture {
15 16
16void LoopProcess(Core::System& system) { 17void LoopProcess(Core::System& system) {
17 auto server_manager = std::make_unique<ServerManager>(system); 18 auto server_manager = std::make_unique<ServerManager>(system);
19 auto album_manager = std::make_shared<AlbumManager>(system);
20
21 server_manager->RegisterNamedService(
22 "caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager));
23 server_manager->RegisterNamedService(
24 "caps:c", std::make_shared<IAlbumControlService>(system, album_manager));
25 server_manager->RegisterNamedService(
26 "caps:u", std::make_shared<IAlbumApplicationService>(system, album_manager));
27
28 server_manager->RegisterNamedService("caps:ss", std::make_shared<IScreenShotService>(system));
29 server_manager->RegisterNamedService("caps:sc",
30 std::make_shared<IScreenShotControlService>(system));
31 server_manager->RegisterNamedService("caps:su",
32 std::make_shared<IScreenShotApplicationService>(system));
18 33
19 server_manager->RegisterNamedService("caps:a", std::make_shared<CAPS_A>(system));
20 server_manager->RegisterNamedService("caps:c", std::make_shared<CAPS_C>(system));
21 server_manager->RegisterNamedService("caps:u", std::make_shared<CAPS_U>(system));
22 server_manager->RegisterNamedService("caps:sc", std::make_shared<CAPS_SC>(system));
23 server_manager->RegisterNamedService("caps:ss", std::make_shared<CAPS_SS>(system));
24 server_manager->RegisterNamedService("caps:su", std::make_shared<CAPS_SU>(system));
25 ServerManager::RunServer(std::move(server_manager)); 34 ServerManager::RunServer(std::move(server_manager));
26} 35}
27 36
diff --git a/src/core/hle/service/caps/caps.h b/src/core/hle/service/caps/caps.h
index 15f0ecfaa..58e9725b8 100644
--- a/src/core/hle/service/caps/caps.h
+++ b/src/core/hle/service/caps/caps.h
@@ -3,93 +3,12 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8
9namespace Core { 6namespace Core {
10class System; 7class System;
11} 8}
12 9
13namespace Service::SM {
14class ServiceManager;
15}
16
17namespace Service::Capture { 10namespace Service::Capture {
18 11
19enum class AlbumImageOrientation {
20 Orientation0 = 0,
21 Orientation1 = 1,
22 Orientation2 = 2,
23 Orientation3 = 3,
24};
25
26enum class AlbumReportOption : s32 {
27 Disable = 0,
28 Enable = 1,
29};
30
31enum class ContentType : u8 {
32 Screenshot = 0,
33 Movie = 1,
34 ExtraMovie = 3,
35};
36
37enum class AlbumStorage : u8 {
38 NAND = 0,
39 SD = 1,
40};
41
42struct AlbumFileDateTime {
43 s16 year{};
44 s8 month{};
45 s8 day{};
46 s8 hour{};
47 s8 minute{};
48 s8 second{};
49 s8 uid{};
50};
51static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime has incorrect size.");
52
53struct AlbumEntry {
54 u64 size{};
55 u64 application_id{};
56 AlbumFileDateTime datetime{};
57 AlbumStorage storage{};
58 ContentType content{};
59 INSERT_PADDING_BYTES(6);
60};
61static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry has incorrect size.");
62
63struct AlbumFileEntry {
64 u64 size{}; // Size of the entry
65 u64 hash{}; // AES256 with hardcoded key over AlbumEntry
66 AlbumFileDateTime datetime{};
67 AlbumStorage storage{};
68 ContentType content{};
69 INSERT_PADDING_BYTES(5);
70 u8 unknown{1}; // Set to 1 on official SW
71};
72static_assert(sizeof(AlbumFileEntry) == 0x20, "AlbumFileEntry has incorrect size.");
73
74struct ApplicationAlbumEntry {
75 u64 size{}; // Size of the entry
76 u64 hash{}; // AES256 with hardcoded key over AlbumEntry
77 AlbumFileDateTime datetime{};
78 AlbumStorage storage{};
79 ContentType content{};
80 INSERT_PADDING_BYTES(5);
81 u8 unknown{1}; // Set to 1 on official SW
82};
83static_assert(sizeof(ApplicationAlbumEntry) == 0x20, "ApplicationAlbumEntry has incorrect size.");
84
85struct ApplicationAlbumFileEntry {
86 ApplicationAlbumEntry entry{};
87 AlbumFileDateTime datetime{};
88 u64 unknown{};
89};
90static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30,
91 "ApplicationAlbumFileEntry has incorrect size.");
92
93void LoopProcess(Core::System& system); 12void LoopProcess(Core::System& system);
94 13
95} // namespace Service::Capture 14} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_a.cpp b/src/core/hle/service/caps/caps_a.cpp
index 44267b284..9925720a3 100644
--- a/src/core/hle/service/caps/caps_a.cpp
+++ b/src/core/hle/service/caps/caps_a.cpp
@@ -1,40 +1,26 @@
1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "common/logging/log.h"
4#include "core/hle/service/caps/caps_a.h" 5#include "core/hle/service/caps/caps_a.h"
6#include "core/hle/service/caps/caps_manager.h"
7#include "core/hle/service/caps/caps_result.h"
8#include "core/hle/service/caps/caps_types.h"
9#include "core/hle/service/ipc_helpers.h"
5 10
6namespace Service::Capture { 11namespace Service::Capture {
7 12
8class IAlbumAccessorSession final : public ServiceFramework<IAlbumAccessorSession> { 13IAlbumAccessorService::IAlbumAccessorService(Core::System& system_,
9public: 14 std::shared_ptr<AlbumManager> album_manager)
10 explicit IAlbumAccessorSession(Core::System& system_) 15 : ServiceFramework{system_, "caps:a"}, manager{album_manager} {
11 : ServiceFramework{system_, "IAlbumAccessorSession"} {
12 // clang-format off
13 static const FunctionInfo functions[] = {
14 {2001, nullptr, "OpenAlbumMovieReadStream"},
15 {2002, nullptr, "CloseAlbumMovieReadStream"},
16 {2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
17 {2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
18 {2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
19 {2006, nullptr, "GetAlbumMovieReadStreamImageDataSize"},
20 {2007, nullptr, "ReadImageDataFromAlbumMovieReadStream"},
21 {2008, nullptr, "ReadFileAttributeFromAlbumMovieReadStream"},
22 };
23 // clang-format on
24
25 RegisterHandlers(functions);
26 }
27};
28
29CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
30 // clang-format off 16 // clang-format off
31 static const FunctionInfo functions[] = { 17 static const FunctionInfo functions[] = {
32 {0, nullptr, "GetAlbumFileCount"}, 18 {0, nullptr, "GetAlbumFileCount"},
33 {1, nullptr, "GetAlbumFileList"}, 19 {1, nullptr, "GetAlbumFileList"},
34 {2, nullptr, "LoadAlbumFile"}, 20 {2, nullptr, "LoadAlbumFile"},
35 {3, nullptr, "DeleteAlbumFile"}, 21 {3, &IAlbumAccessorService::DeleteAlbumFile, "DeleteAlbumFile"},
36 {4, nullptr, "StorageCopyAlbumFile"}, 22 {4, nullptr, "StorageCopyAlbumFile"},
37 {5, nullptr, "IsAlbumMounted"}, 23 {5, &IAlbumAccessorService::IsAlbumMounted, "IsAlbumMounted"},
38 {6, nullptr, "GetAlbumUsage"}, 24 {6, nullptr, "GetAlbumUsage"},
39 {7, nullptr, "GetAlbumFileSize"}, 25 {7, nullptr, "GetAlbumFileSize"},
40 {8, nullptr, "LoadAlbumFileThumbnail"}, 26 {8, nullptr, "LoadAlbumFileThumbnail"},
@@ -47,18 +33,18 @@ CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
47 {15, nullptr, "GetAlbumUsage3"}, 33 {15, nullptr, "GetAlbumUsage3"},
48 {16, nullptr, "GetAlbumMountResult"}, 34 {16, nullptr, "GetAlbumMountResult"},
49 {17, nullptr, "GetAlbumUsage16"}, 35 {17, nullptr, "GetAlbumUsage16"},
50 {18, nullptr, "Unknown18"}, 36 {18, &IAlbumAccessorService::Unknown18, "Unknown18"},
51 {19, nullptr, "Unknown19"}, 37 {19, nullptr, "Unknown19"},
52 {100, nullptr, "GetAlbumFileCountEx0"}, 38 {100, nullptr, "GetAlbumFileCountEx0"},
53 {101, nullptr, "GetAlbumFileListEx0"}, 39 {101, &IAlbumAccessorService::GetAlbumFileListEx0, "GetAlbumFileListEx0"},
54 {202, nullptr, "SaveEditedScreenShot"}, 40 {202, nullptr, "SaveEditedScreenShot"},
55 {301, nullptr, "GetLastThumbnail"}, 41 {301, nullptr, "GetLastThumbnail"},
56 {302, nullptr, "GetLastOverlayMovieThumbnail"}, 42 {302, nullptr, "GetLastOverlayMovieThumbnail"},
57 {401, nullptr, "GetAutoSavingStorage"}, 43 {401, &IAlbumAccessorService::GetAutoSavingStorage, "GetAutoSavingStorage"},
58 {501, nullptr, "GetRequiredStorageSpaceSizeToCopyAll"}, 44 {501, nullptr, "GetRequiredStorageSpaceSizeToCopyAll"},
59 {1001, nullptr, "LoadAlbumScreenShotThumbnailImageEx0"}, 45 {1001, nullptr, "LoadAlbumScreenShotThumbnailImageEx0"},
60 {1002, nullptr, "LoadAlbumScreenShotImageEx1"}, 46 {1002, &IAlbumAccessorService::LoadAlbumScreenShotImageEx1, "LoadAlbumScreenShotImageEx1"},
61 {1003, nullptr, "LoadAlbumScreenShotThumbnailImageEx1"}, 47 {1003, &IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1, "LoadAlbumScreenShotThumbnailImageEx1"},
62 {8001, nullptr, "ForceAlbumUnmounted"}, 48 {8001, nullptr, "ForceAlbumUnmounted"},
63 {8002, nullptr, "ResetAlbumMountStatus"}, 49 {8002, nullptr, "ResetAlbumMountStatus"},
64 {8011, nullptr, "RefreshAlbumCache"}, 50 {8011, nullptr, "RefreshAlbumCache"},
@@ -74,6 +60,199 @@ CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
74 RegisterHandlers(functions); 60 RegisterHandlers(functions);
75} 61}
76 62
77CAPS_A::~CAPS_A() = default; 63IAlbumAccessorService::~IAlbumAccessorService() = default;
64
65void IAlbumAccessorService::DeleteAlbumFile(HLERequestContext& ctx) {
66 IPC::RequestParser rp{ctx};
67 const auto file_id{rp.PopRaw<AlbumFileId>()};
68
69 LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}",
70 file_id.application_id, file_id.storage, file_id.type);
71
72 Result result = manager->DeleteAlbumFile(file_id);
73 result = TranslateResult(result);
74
75 IPC::ResponseBuilder rb{ctx, 2};
76 rb.Push(result);
77}
78
79void IAlbumAccessorService::IsAlbumMounted(HLERequestContext& ctx) {
80 IPC::RequestParser rp{ctx};
81 const auto storage{rp.PopEnum<AlbumStorage>()};
82
83 LOG_INFO(Service_Capture, "called, storage={}", storage);
84
85 Result result = manager->IsAlbumMounted(storage);
86 const bool is_mounted = result.IsSuccess();
87 result = TranslateResult(result);
88
89 IPC::ResponseBuilder rb{ctx, 3};
90 rb.Push(result);
91 rb.Push<u8>(is_mounted);
92}
93
94void IAlbumAccessorService::Unknown18(HLERequestContext& ctx) {
95 struct UnknownBuffer {
96 INSERT_PADDING_BYTES(0x10);
97 };
98 static_assert(sizeof(UnknownBuffer) == 0x10, "UnknownBuffer is an invalid size");
99
100 LOG_WARNING(Service_Capture, "(STUBBED) called");
101
102 std::vector<UnknownBuffer> buffer{};
103
104 if (!buffer.empty()) {
105 ctx.WriteBuffer(buffer);
106 }
107
108 IPC::ResponseBuilder rb{ctx, 3};
109 rb.Push(ResultSuccess);
110 rb.Push(static_cast<u32>(buffer.size()));
111}
112
113void IAlbumAccessorService::GetAlbumFileListEx0(HLERequestContext& ctx) {
114 IPC::RequestParser rp{ctx};
115 const auto storage{rp.PopEnum<AlbumStorage>()};
116 const auto flags{rp.Pop<u8>()};
117 const auto album_entry_size{ctx.GetWriteBufferNumElements<AlbumEntry>()};
118
119 LOG_INFO(Service_Capture, "called, storage={}, flags={}", storage, flags);
120
121 std::vector<AlbumEntry> entries;
122 Result result = manager->GetAlbumFileList(entries, storage, flags);
123 result = TranslateResult(result);
124
125 entries.resize(std::min(album_entry_size, entries.size()));
126
127 if (!entries.empty()) {
128 ctx.WriteBuffer(entries);
129 }
130
131 IPC::ResponseBuilder rb{ctx, 4};
132 rb.Push(result);
133 rb.Push<u64>(entries.size());
134}
135
136void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) {
137 LOG_WARNING(Service_Capture, "(STUBBED) called");
138
139 bool is_autosaving{};
140 Result result = manager->GetAutoSavingStorage(is_autosaving);
141 result = TranslateResult(result);
142
143 IPC::ResponseBuilder rb{ctx, 3};
144 rb.Push(result);
145 rb.Push<u8>(is_autosaving);
146}
147
148void IAlbumAccessorService::LoadAlbumScreenShotImageEx1(HLERequestContext& ctx) {
149 IPC::RequestParser rp{ctx};
150 const auto file_id{rp.PopRaw<AlbumFileId>()};
151 const auto decoder_options{rp.PopRaw<ScreenShotDecodeOption>()};
152 const auto image_buffer_size{ctx.GetWriteBufferSize(1)};
153
154 LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}",
155 file_id.application_id, file_id.storage, file_id.type, decoder_options.flags);
156
157 std::vector<u8> image;
158 LoadAlbumScreenShotImageOutput image_output;
159 Result result =
160 manager->LoadAlbumScreenShotImage(image_output, image, file_id, decoder_options);
161 result = TranslateResult(result);
162
163 if (image.size() > image_buffer_size) {
164 result = ResultWorkMemoryError;
165 }
166
167 if (result.IsSuccess()) {
168 ctx.WriteBuffer(image_output, 0);
169 ctx.WriteBuffer(image, 1);
170 }
171
172 IPC::ResponseBuilder rb{ctx, 2};
173 rb.Push(result);
174}
175
176void IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx) {
177 IPC::RequestParser rp{ctx};
178 const auto file_id{rp.PopRaw<AlbumFileId>()};
179 const auto decoder_options{rp.PopRaw<ScreenShotDecodeOption>()};
180
181 LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}",
182 file_id.application_id, file_id.storage, file_id.type, decoder_options.flags);
183
184 std::vector<u8> image(ctx.GetWriteBufferSize(1));
185 LoadAlbumScreenShotImageOutput image_output;
186 Result result =
187 manager->LoadAlbumScreenShotThumbnail(image_output, image, file_id, decoder_options);
188 result = TranslateResult(result);
189
190 if (result.IsSuccess()) {
191 ctx.WriteBuffer(image_output, 0);
192 ctx.WriteBuffer(image, 1);
193 }
194
195 IPC::ResponseBuilder rb{ctx, 2};
196 rb.Push(result);
197}
198
199Result IAlbumAccessorService::TranslateResult(Result in_result) {
200 if (in_result.IsSuccess()) {
201 return in_result;
202 }
203
204 if ((in_result.raw & 0x3801ff) == ResultUnknown1024.raw) {
205 if (in_result.description - 0x514 < 100) {
206 return ResultInvalidFileData;
207 }
208 if (in_result.description - 0x5dc < 100) {
209 return ResultInvalidFileData;
210 }
211
212 if (in_result.description - 0x578 < 100) {
213 if (in_result == ResultFileCountLimit) {
214 return ResultUnknown22;
215 }
216 return ResultUnknown25;
217 }
218
219 if (in_result.raw < ResultUnknown1801.raw) {
220 if (in_result == ResultUnknown1202) {
221 return ResultUnknown810;
222 }
223 if (in_result == ResultUnknown1203) {
224 return ResultUnknown810;
225 }
226 if (in_result == ResultUnknown1701) {
227 return ResultUnknown5;
228 }
229 } else if (in_result.raw < ResultUnknown1803.raw) {
230 if (in_result == ResultUnknown1801) {
231 return ResultUnknown5;
232 }
233 if (in_result == ResultUnknown1802) {
234 return ResultUnknown6;
235 }
236 } else {
237 if (in_result == ResultUnknown1803) {
238 return ResultUnknown7;
239 }
240 if (in_result == ResultUnknown1804) {
241 return ResultOutOfRange;
242 }
243 }
244 return ResultUnknown1024;
245 }
246
247 if (in_result.module == ErrorModule::FS) {
248 if ((in_result.description >> 0xc < 0x7d) || (in_result.description - 1000 < 2000) ||
249 (((in_result.description - 3000) >> 3) < 0x271)) {
250 // TODO: Translate FS error
251 return in_result;
252 }
253 }
254
255 return in_result;
256}
78 257
79} // namespace Service::Capture 258} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_a.h b/src/core/hle/service/caps/caps_a.h
index 98a21a5ad..c90cff71e 100644
--- a/src/core/hle/service/caps/caps_a.h
+++ b/src/core/hle/service/caps/caps_a.h
@@ -10,11 +10,26 @@ class System;
10} 10}
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13class AlbumManager;
13 14
14class CAPS_A final : public ServiceFramework<CAPS_A> { 15class IAlbumAccessorService final : public ServiceFramework<IAlbumAccessorService> {
15public: 16public:
16 explicit CAPS_A(Core::System& system_); 17 explicit IAlbumAccessorService(Core::System& system_,
17 ~CAPS_A() override; 18 std::shared_ptr<AlbumManager> album_manager);
19 ~IAlbumAccessorService() override;
20
21private:
22 void DeleteAlbumFile(HLERequestContext& ctx);
23 void IsAlbumMounted(HLERequestContext& ctx);
24 void Unknown18(HLERequestContext& ctx);
25 void GetAlbumFileListEx0(HLERequestContext& ctx);
26 void GetAutoSavingStorage(HLERequestContext& ctx);
27 void LoadAlbumScreenShotImageEx1(HLERequestContext& ctx);
28 void LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx);
29
30 Result TranslateResult(Result in_result);
31
32 std::shared_ptr<AlbumManager> manager = nullptr;
18}; 33};
19 34
20} // namespace Service::Capture 35} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_c.cpp b/src/core/hle/service/caps/caps_c.cpp
index fc77e35cd..1e7fe6474 100644
--- a/src/core/hle/service/caps/caps_c.cpp
+++ b/src/core/hle/service/caps/caps_c.cpp
@@ -3,53 +3,21 @@
3 3
4#include "common/logging/log.h" 4#include "common/logging/log.h"
5#include "core/hle/service/caps/caps_c.h" 5#include "core/hle/service/caps/caps_c.h"
6#include "core/hle/service/caps/caps_manager.h"
7#include "core/hle/service/caps/caps_result.h"
8#include "core/hle/service/caps/caps_types.h"
6#include "core/hle/service/ipc_helpers.h" 9#include "core/hle/service/ipc_helpers.h"
7 10
8namespace Service::Capture { 11namespace Service::Capture {
9 12
10class IAlbumControlSession final : public ServiceFramework<IAlbumControlSession> { 13IAlbumControlService::IAlbumControlService(Core::System& system_,
11public: 14 std::shared_ptr<AlbumManager> album_manager)
12 explicit IAlbumControlSession(Core::System& system_) 15 : ServiceFramework{system_, "caps:c"}, manager{album_manager} {
13 : ServiceFramework{system_, "IAlbumControlSession"} {
14 // clang-format off
15 static const FunctionInfo functions[] = {
16 {2001, nullptr, "OpenAlbumMovieReadStream"},
17 {2002, nullptr, "CloseAlbumMovieReadStream"},
18 {2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
19 {2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
20 {2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
21 {2006, nullptr, "GetAlbumMovieReadStreamImageDataSize"},
22 {2007, nullptr, "ReadImageDataFromAlbumMovieReadStream"},
23 {2008, nullptr, "ReadFileAttributeFromAlbumMovieReadStream"},
24 {2401, nullptr, "OpenAlbumMovieWriteStream"},
25 {2402, nullptr, "FinishAlbumMovieWriteStream"},
26 {2403, nullptr, "CommitAlbumMovieWriteStream"},
27 {2404, nullptr, "DiscardAlbumMovieWriteStream"},
28 {2405, nullptr, "DiscardAlbumMovieWriteStreamNoDelete"},
29 {2406, nullptr, "CommitAlbumMovieWriteStreamEx"},
30 {2411, nullptr, "StartAlbumMovieWriteStreamDataSection"},
31 {2412, nullptr, "EndAlbumMovieWriteStreamDataSection"},
32 {2413, nullptr, "StartAlbumMovieWriteStreamMetaSection"},
33 {2414, nullptr, "EndAlbumMovieWriteStreamMetaSection"},
34 {2421, nullptr, "ReadDataFromAlbumMovieWriteStream"},
35 {2422, nullptr, "WriteDataToAlbumMovieWriteStream"},
36 {2424, nullptr, "WriteMetaToAlbumMovieWriteStream"},
37 {2431, nullptr, "GetAlbumMovieWriteStreamBrokenReason"},
38 {2433, nullptr, "GetAlbumMovieWriteStreamDataSize"},
39 {2434, nullptr, "SetAlbumMovieWriteStreamDataSize"},
40 };
41 // clang-format on
42
43 RegisterHandlers(functions);
44 }
45};
46
47CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} {
48 // clang-format off 16 // clang-format off
49 static const FunctionInfo functions[] = { 17 static const FunctionInfo functions[] = {
50 {1, nullptr, "CaptureRawImage"}, 18 {1, nullptr, "CaptureRawImage"},
51 {2, nullptr, "CaptureRawImageWithTimeout"}, 19 {2, nullptr, "CaptureRawImageWithTimeout"},
52 {33, &CAPS_C::SetShimLibraryVersion, "SetShimLibraryVersion"}, 20 {33, &IAlbumControlService::SetShimLibraryVersion, "SetShimLibraryVersion"},
53 {1001, nullptr, "RequestTakingScreenShot"}, 21 {1001, nullptr, "RequestTakingScreenShot"},
54 {1002, nullptr, "RequestTakingScreenShotWithTimeout"}, 22 {1002, nullptr, "RequestTakingScreenShotWithTimeout"},
55 {1011, nullptr, "NotifyTakingScreenShotRefused"}, 23 {1011, nullptr, "NotifyTakingScreenShotRefused"},
@@ -72,9 +40,9 @@ CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} {
72 RegisterHandlers(functions); 40 RegisterHandlers(functions);
73} 41}
74 42
75CAPS_C::~CAPS_C() = default; 43IAlbumControlService::~IAlbumControlService() = default;
76 44
77void CAPS_C::SetShimLibraryVersion(HLERequestContext& ctx) { 45void IAlbumControlService::SetShimLibraryVersion(HLERequestContext& ctx) {
78 IPC::RequestParser rp{ctx}; 46 IPC::RequestParser rp{ctx};
79 const auto library_version{rp.Pop<u64>()}; 47 const auto library_version{rp.Pop<u64>()};
80 const auto applet_resource_user_id{rp.Pop<u64>()}; 48 const auto applet_resource_user_id{rp.Pop<u64>()};
diff --git a/src/core/hle/service/caps/caps_c.h b/src/core/hle/service/caps/caps_c.h
index 537b3a2e3..92ba242db 100644
--- a/src/core/hle/service/caps/caps_c.h
+++ b/src/core/hle/service/caps/caps_c.h
@@ -10,14 +10,18 @@ class System;
10} 10}
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13class AlbumManager;
13 14
14class CAPS_C final : public ServiceFramework<CAPS_C> { 15class IAlbumControlService final : public ServiceFramework<IAlbumControlService> {
15public: 16public:
16 explicit CAPS_C(Core::System& system_); 17 explicit IAlbumControlService(Core::System& system_,
17 ~CAPS_C() override; 18 std::shared_ptr<AlbumManager> album_manager);
19 ~IAlbumControlService() override;
18 20
19private: 21private:
20 void SetShimLibraryVersion(HLERequestContext& ctx); 22 void SetShimLibraryVersion(HLERequestContext& ctx);
23
24 std::shared_ptr<AlbumManager> manager = nullptr;
21}; 25};
22 26
23} // namespace Service::Capture 27} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_manager.cpp b/src/core/hle/service/caps/caps_manager.cpp
new file mode 100644
index 000000000..2b4e3f076
--- /dev/null
+++ b/src/core/hle/service/caps/caps_manager.cpp
@@ -0,0 +1,386 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <sstream>
5#include <stb_image.h>
6#include <stb_image_resize.h>
7
8#include "common/fs/file.h"
9#include "common/fs/path_util.h"
10#include "common/logging/log.h"
11#include "core/core.h"
12#include "core/hle/service/caps/caps_manager.h"
13#include "core/hle/service/caps/caps_result.h"
14#include "core/hle/service/time/time_manager.h"
15#include "core/hle/service/time/time_zone_content_manager.h"
16
17namespace Service::Capture {
18
19AlbumManager::AlbumManager(Core::System& system_) : system{system_} {}
20
21AlbumManager::~AlbumManager() = default;
22
23Result AlbumManager::DeleteAlbumFile(const AlbumFileId& file_id) {
24 if (file_id.storage > AlbumStorage::Sd) {
25 return ResultInvalidStorage;
26 }
27
28 if (!is_mounted) {
29 return ResultIsNotMounted;
30 }
31
32 std::filesystem::path path;
33 const auto result = GetFile(path, file_id);
34
35 if (result.IsError()) {
36 return result;
37 }
38
39 if (!Common::FS::RemoveFile(path)) {
40 return ResultFileNotFound;
41 }
42
43 return ResultSuccess;
44}
45
46Result AlbumManager::IsAlbumMounted(AlbumStorage storage) {
47 if (storage > AlbumStorage::Sd) {
48 return ResultInvalidStorage;
49 }
50
51 is_mounted = true;
52
53 if (storage == AlbumStorage::Sd) {
54 FindScreenshots();
55 }
56
57 return is_mounted ? ResultSuccess : ResultIsNotMounted;
58}
59
60Result AlbumManager::GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
61 u8 flags) const {
62 if (storage > AlbumStorage::Sd) {
63 return ResultInvalidStorage;
64 }
65
66 if (!is_mounted) {
67 return ResultIsNotMounted;
68 }
69
70 for (auto& [file_id, path] : album_files) {
71 if (file_id.storage != storage) {
72 continue;
73 }
74 if (out_entries.size() >= SdAlbumFileLimit) {
75 break;
76 }
77
78 const auto entry_size = Common::FS::GetSize(path);
79 out_entries.push_back({
80 .entry_size = entry_size,
81 .file_id = file_id,
82 });
83 }
84
85 return ResultSuccess;
86}
87
88Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
89 ContentType contex_type, s64 start_posix_time,
90 s64 end_posix_time, u64 aruid) const {
91 if (!is_mounted) {
92 return ResultIsNotMounted;
93 }
94
95 std::vector<ApplicationAlbumEntry> album_entries;
96 const auto start_date = ConvertToAlbumDateTime(start_posix_time);
97 const auto end_date = ConvertToAlbumDateTime(end_posix_time);
98 const auto result = GetAlbumFileList(album_entries, contex_type, start_date, end_date, aruid);
99
100 if (result.IsError()) {
101 return result;
102 }
103
104 for (const auto& album_entry : album_entries) {
105 ApplicationAlbumFileEntry entry{
106 .entry = album_entry,
107 .datetime = album_entry.datetime,
108 .unknown = {},
109 };
110 out_entries.push_back(entry);
111 }
112
113 return ResultSuccess;
114}
115
116Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
117 ContentType contex_type, AlbumFileDateTime start_date,
118 AlbumFileDateTime end_date, u64 aruid) const {
119 if (!is_mounted) {
120 return ResultIsNotMounted;
121 }
122
123 for (auto& [file_id, path] : album_files) {
124 if (file_id.type != contex_type) {
125 continue;
126 }
127 if (file_id.date > start_date) {
128 continue;
129 }
130 if (file_id.date < end_date) {
131 continue;
132 }
133 if (out_entries.size() >= SdAlbumFileLimit) {
134 break;
135 }
136
137 const auto entry_size = Common::FS::GetSize(path);
138 ApplicationAlbumEntry entry{
139 .size = entry_size,
140 .hash{},
141 .datetime = file_id.date,
142 .storage = file_id.storage,
143 .content = contex_type,
144 .unknown = 1,
145 };
146 out_entries.push_back(entry);
147 }
148
149 return ResultSuccess;
150}
151
152Result AlbumManager::GetAutoSavingStorage(bool& out_is_autosaving) const {
153 out_is_autosaving = false;
154 return ResultSuccess;
155}
156
157Result AlbumManager::LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& out_image_output,
158 std::vector<u8>& out_image,
159 const AlbumFileId& file_id,
160 const ScreenShotDecodeOption& decoder_options) const {
161 if (file_id.storage > AlbumStorage::Sd) {
162 return ResultInvalidStorage;
163 }
164
165 if (!is_mounted) {
166 return ResultIsNotMounted;
167 }
168
169 out_image_output = {
170 .width = 1280,
171 .height = 720,
172 .attribute =
173 {
174 .unknown_0{},
175 .orientation = AlbumImageOrientation::None,
176 .unknown_1{},
177 .unknown_2{},
178 },
179 };
180
181 std::filesystem::path path;
182 const auto result = GetFile(path, file_id);
183
184 if (result.IsError()) {
185 return result;
186 }
187
188 out_image.resize(out_image_output.height * out_image_output.width * STBI_rgb_alpha);
189
190 return LoadImage(out_image, path, static_cast<int>(out_image_output.width),
191 +static_cast<int>(out_image_output.height), decoder_options.flags);
192}
193
194Result AlbumManager::LoadAlbumScreenShotThumbnail(
195 LoadAlbumScreenShotImageOutput& out_image_output, std::vector<u8>& out_image,
196 const AlbumFileId& file_id, const ScreenShotDecodeOption& decoder_options) const {
197 if (file_id.storage > AlbumStorage::Sd) {
198 return ResultInvalidStorage;
199 }
200
201 if (!is_mounted) {
202 return ResultIsNotMounted;
203 }
204
205 out_image_output = {
206 .width = 320,
207 .height = 180,
208 .attribute =
209 {
210 .unknown_0{},
211 .orientation = AlbumImageOrientation::None,
212 .unknown_1{},
213 .unknown_2{},
214 },
215 };
216
217 std::filesystem::path path;
218 const auto result = GetFile(path, file_id);
219
220 if (result.IsError()) {
221 return result;
222 }
223
224 out_image.resize(out_image_output.height * out_image_output.width * STBI_rgb_alpha);
225
226 return LoadImage(out_image, path, static_cast<int>(out_image_output.width),
227 +static_cast<int>(out_image_output.height), decoder_options.flags);
228}
229
230Result AlbumManager::GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const {
231 const auto file = album_files.find(file_id);
232
233 if (file == album_files.end()) {
234 return ResultFileNotFound;
235 }
236
237 out_path = file->second;
238 return ResultSuccess;
239}
240
241void AlbumManager::FindScreenshots() {
242 is_mounted = false;
243 album_files.clear();
244
245 // TODO: Swap this with a blocking operation.
246 const auto screenshots_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir);
247 Common::FS::IterateDirEntries(
248 screenshots_dir,
249 [this](const std::filesystem::path& full_path) {
250 AlbumEntry entry;
251 if (GetAlbumEntry(entry, full_path).IsError()) {
252 return true;
253 }
254 while (album_files.contains(entry.file_id)) {
255 if (++entry.file_id.date.unique_id == 0) {
256 break;
257 }
258 }
259 album_files[entry.file_id] = full_path;
260 return true;
261 },
262 Common::FS::DirEntryFilter::File);
263
264 is_mounted = true;
265}
266
267Result AlbumManager::GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const {
268 std::istringstream line_stream(path.filename().string());
269 std::string date;
270 std::string application;
271 std::string time;
272
273 // Parse filename to obtain entry properties
274 std::getline(line_stream, application, '_');
275 std::getline(line_stream, date, '_');
276 std::getline(line_stream, time, '_');
277
278 std::istringstream date_stream(date);
279 std::istringstream time_stream(time);
280 std::string year;
281 std::string month;
282 std::string day;
283 std::string hour;
284 std::string minute;
285 std::string second;
286
287 std::getline(date_stream, year, '-');
288 std::getline(date_stream, month, '-');
289 std::getline(date_stream, day, '-');
290
291 std::getline(time_stream, hour, '-');
292 std::getline(time_stream, minute, '-');
293 std::getline(time_stream, second, '-');
294
295 try {
296 out_entry = {
297 .entry_size = 1,
298 .file_id{
299 .application_id = static_cast<u64>(std::stoll(application, 0, 16)),
300 .date =
301 {
302 .year = static_cast<s16>(std::stoi(year)),
303 .month = static_cast<s8>(std::stoi(month)),
304 .day = static_cast<s8>(std::stoi(day)),
305 .hour = static_cast<s8>(std::stoi(hour)),
306 .minute = static_cast<s8>(std::stoi(minute)),
307 .second = static_cast<s8>(std::stoi(second)),
308 .unique_id = 0,
309 },
310 .storage = AlbumStorage::Sd,
311 .type = ContentType::Screenshot,
312 .unknown = 1,
313 },
314 };
315 } catch (const std::invalid_argument&) {
316 return ResultUnknown;
317 } catch (const std::out_of_range&) {
318 return ResultUnknown;
319 } catch (const std::exception&) {
320 return ResultUnknown;
321 }
322
323 return ResultSuccess;
324}
325
326Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::path& path,
327 int width, int height, ScreenShotDecoderFlag flag) const {
328 if (out_image.size() != static_cast<std::size_t>(width * height * STBI_rgb_alpha)) {
329 return ResultUnknown;
330 }
331
332 const Common::FS::IOFile db_file{path, Common::FS::FileAccessMode::Read,
333 Common::FS::FileType::BinaryFile};
334
335 std::vector<u8> raw_file(db_file.GetSize());
336 if (db_file.Read(raw_file) != raw_file.size()) {
337 return ResultUnknown;
338 }
339
340 int filter_flag = STBIR_FILTER_DEFAULT;
341 int original_width, original_height, color_channels;
342 const auto dbi_image =
343 stbi_load_from_memory(raw_file.data(), static_cast<int>(raw_file.size()), &original_width,
344 &original_height, &color_channels, STBI_rgb_alpha);
345
346 if (dbi_image == nullptr) {
347 return ResultUnknown;
348 }
349
350 switch (flag) {
351 case ScreenShotDecoderFlag::EnableFancyUpsampling:
352 filter_flag = STBIR_FILTER_TRIANGLE;
353 break;
354 case ScreenShotDecoderFlag::EnableBlockSmoothing:
355 filter_flag = STBIR_FILTER_BOX;
356 break;
357 default:
358 filter_flag = STBIR_FILTER_DEFAULT;
359 break;
360 }
361
362 stbir_resize_uint8_srgb(dbi_image, original_width, original_height, 0, out_image.data(), width,
363 height, 0, STBI_rgb_alpha, 3, filter_flag);
364
365 return ResultSuccess;
366}
367
368AlbumFileDateTime AlbumManager::ConvertToAlbumDateTime(u64 posix_time) const {
369 Time::TimeZone::CalendarInfo calendar_date{};
370 const auto& time_zone_manager =
371 system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
372
373 time_zone_manager.ToCalendarTimeWithMyRules(posix_time, calendar_date);
374
375 return {
376 .year = calendar_date.time.year,
377 .month = calendar_date.time.month,
378 .day = calendar_date.time.day,
379 .hour = calendar_date.time.hour,
380 .minute = calendar_date.time.minute,
381 .second = calendar_date.time.second,
382 .unique_id = 0,
383 };
384}
385
386} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_manager.h b/src/core/hle/service/caps/caps_manager.h
new file mode 100644
index 000000000..f65eb12c1
--- /dev/null
+++ b/src/core/hle/service/caps/caps_manager.h
@@ -0,0 +1,79 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <unordered_map>
7
8#include "common/fs/fs.h"
9#include "core/hle/result.h"
10#include "core/hle/service/caps/caps_types.h"
11
12namespace Core {
13class System;
14}
15
16namespace std {
17// Hash used to create lists from AlbumFileId data
18template <>
19struct hash<Service::Capture::AlbumFileId> {
20 size_t operator()(const Service::Capture::AlbumFileId& pad_id) const noexcept {
21 u64 hash_value = (static_cast<u64>(pad_id.date.year) << 8);
22 hash_value ^= (static_cast<u64>(pad_id.date.month) << 7);
23 hash_value ^= (static_cast<u64>(pad_id.date.day) << 6);
24 hash_value ^= (static_cast<u64>(pad_id.date.hour) << 5);
25 hash_value ^= (static_cast<u64>(pad_id.date.minute) << 4);
26 hash_value ^= (static_cast<u64>(pad_id.date.second) << 3);
27 hash_value ^= (static_cast<u64>(pad_id.date.unique_id) << 2);
28 hash_value ^= (static_cast<u64>(pad_id.storage) << 1);
29 hash_value ^= static_cast<u64>(pad_id.type);
30 return static_cast<size_t>(hash_value);
31 }
32};
33
34} // namespace std
35
36namespace Service::Capture {
37
38class AlbumManager {
39public:
40 explicit AlbumManager(Core::System& system_);
41 ~AlbumManager();
42
43 Result DeleteAlbumFile(const AlbumFileId& file_id);
44 Result IsAlbumMounted(AlbumStorage storage);
45 Result GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
46 u8 flags) const;
47 Result GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
48 ContentType contex_type, s64 start_posix_time, s64 end_posix_time,
49 u64 aruid) const;
50 Result GetAlbumFileList(std::vector<ApplicationAlbumEntry>& out_entries,
51 ContentType contex_type, AlbumFileDateTime start_date,
52 AlbumFileDateTime end_date, u64 aruid) const;
53 Result GetAutoSavingStorage(bool& out_is_autosaving) const;
54 Result LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& out_image_output,
55 std::vector<u8>& out_image, const AlbumFileId& file_id,
56 const ScreenShotDecodeOption& decoder_options) const;
57 Result LoadAlbumScreenShotThumbnail(LoadAlbumScreenShotImageOutput& out_image_output,
58 std::vector<u8>& out_image, const AlbumFileId& file_id,
59 const ScreenShotDecodeOption& decoder_options) const;
60
61private:
62 static constexpr std::size_t NandAlbumFileLimit = 1000;
63 static constexpr std::size_t SdAlbumFileLimit = 10000;
64
65 void FindScreenshots();
66 Result GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const;
67 Result GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const;
68 Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width,
69 int height, ScreenShotDecoderFlag flag) const;
70
71 AlbumFileDateTime ConvertToAlbumDateTime(u64 posix_time) const;
72
73 bool is_mounted{};
74 std::unordered_map<AlbumFileId, std::filesystem::path> album_files;
75
76 Core::System& system;
77};
78
79} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_result.h b/src/core/hle/service/caps/caps_result.h
new file mode 100644
index 000000000..c65e5fb9a
--- /dev/null
+++ b/src/core/hle/service/caps/caps_result.h
@@ -0,0 +1,35 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "core/hle/result.h"
7
8namespace Service::Capture {
9
10constexpr Result ResultWorkMemoryError(ErrorModule::Capture, 3);
11constexpr Result ResultUnknown5(ErrorModule::Capture, 5);
12constexpr Result ResultUnknown6(ErrorModule::Capture, 6);
13constexpr Result ResultUnknown7(ErrorModule::Capture, 7);
14constexpr Result ResultOutOfRange(ErrorModule::Capture, 8);
15constexpr Result ResulInvalidTimestamp(ErrorModule::Capture, 12);
16constexpr Result ResultInvalidStorage(ErrorModule::Capture, 13);
17constexpr Result ResultInvalidFileContents(ErrorModule::Capture, 14);
18constexpr Result ResultIsNotMounted(ErrorModule::Capture, 21);
19constexpr Result ResultUnknown22(ErrorModule::Capture, 22);
20constexpr Result ResultFileNotFound(ErrorModule::Capture, 23);
21constexpr Result ResultInvalidFileData(ErrorModule::Capture, 24);
22constexpr Result ResultUnknown25(ErrorModule::Capture, 25);
23constexpr Result ResultReadBufferShortage(ErrorModule::Capture, 30);
24constexpr Result ResultUnknown810(ErrorModule::Capture, 810);
25constexpr Result ResultUnknown1024(ErrorModule::Capture, 1024);
26constexpr Result ResultUnknown1202(ErrorModule::Capture, 1202);
27constexpr Result ResultUnknown1203(ErrorModule::Capture, 1203);
28constexpr Result ResultFileCountLimit(ErrorModule::Capture, 1401);
29constexpr Result ResultUnknown1701(ErrorModule::Capture, 1701);
30constexpr Result ResultUnknown1801(ErrorModule::Capture, 1801);
31constexpr Result ResultUnknown1802(ErrorModule::Capture, 1802);
32constexpr Result ResultUnknown1803(ErrorModule::Capture, 1803);
33constexpr Result ResultUnknown1804(ErrorModule::Capture, 1804);
34
35} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_sc.cpp b/src/core/hle/service/caps/caps_sc.cpp
index 395b13da7..6117cb7c6 100644
--- a/src/core/hle/service/caps/caps_sc.cpp
+++ b/src/core/hle/service/caps/caps_sc.cpp
@@ -5,7 +5,8 @@
5 5
6namespace Service::Capture { 6namespace Service::Capture {
7 7
8CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} { 8IScreenShotControlService::IScreenShotControlService(Core::System& system_)
9 : ServiceFramework{system_, "caps:sc"} {
9 // clang-format off 10 // clang-format off
10 static const FunctionInfo functions[] = { 11 static const FunctionInfo functions[] = {
11 {1, nullptr, "CaptureRawImage"}, 12 {1, nullptr, "CaptureRawImage"},
@@ -34,6 +35,6 @@ CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} {
34 RegisterHandlers(functions); 35 RegisterHandlers(functions);
35} 36}
36 37
37CAPS_SC::~CAPS_SC() = default; 38IScreenShotControlService::~IScreenShotControlService() = default;
38 39
39} // namespace Service::Capture 40} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_sc.h b/src/core/hle/service/caps/caps_sc.h
index e5600f6d7..d555f4979 100644
--- a/src/core/hle/service/caps/caps_sc.h
+++ b/src/core/hle/service/caps/caps_sc.h
@@ -11,10 +11,10 @@ class System;
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13 13
14class CAPS_SC final : public ServiceFramework<CAPS_SC> { 14class IScreenShotControlService final : public ServiceFramework<IScreenShotControlService> {
15public: 15public:
16 explicit CAPS_SC(Core::System& system_); 16 explicit IScreenShotControlService(Core::System& system_);
17 ~CAPS_SC() override; 17 ~IScreenShotControlService() override;
18}; 18};
19 19
20} // namespace Service::Capture 20} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_ss.cpp b/src/core/hle/service/caps/caps_ss.cpp
index 62b9edd41..d0d1b5425 100644
--- a/src/core/hle/service/caps/caps_ss.cpp
+++ b/src/core/hle/service/caps/caps_ss.cpp
@@ -5,7 +5,8 @@
5 5
6namespace Service::Capture { 6namespace Service::Capture {
7 7
8CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} { 8IScreenShotService::IScreenShotService(Core::System& system_)
9 : ServiceFramework{system_, "caps:ss"} {
9 // clang-format off 10 // clang-format off
10 static const FunctionInfo functions[] = { 11 static const FunctionInfo functions[] = {
11 {201, nullptr, "SaveScreenShot"}, 12 {201, nullptr, "SaveScreenShot"},
@@ -21,6 +22,6 @@ CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} {
21 RegisterHandlers(functions); 22 RegisterHandlers(functions);
22} 23}
23 24
24CAPS_SS::~CAPS_SS() = default; 25IScreenShotService::~IScreenShotService() = default;
25 26
26} // namespace Service::Capture 27} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_ss.h b/src/core/hle/service/caps/caps_ss.h
index 718ade485..381e44fd4 100644
--- a/src/core/hle/service/caps/caps_ss.h
+++ b/src/core/hle/service/caps/caps_ss.h
@@ -11,10 +11,10 @@ class System;
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13 13
14class CAPS_SS final : public ServiceFramework<CAPS_SS> { 14class IScreenShotService final : public ServiceFramework<IScreenShotService> {
15public: 15public:
16 explicit CAPS_SS(Core::System& system_); 16 explicit IScreenShotService(Core::System& system_);
17 ~CAPS_SS() override; 17 ~IScreenShotService() override;
18}; 18};
19 19
20} // namespace Service::Capture 20} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp
index 3b11cc95c..cad173dc7 100644
--- a/src/core/hle/service/caps/caps_su.cpp
+++ b/src/core/hle/service/caps/caps_su.cpp
@@ -7,10 +7,11 @@
7 7
8namespace Service::Capture { 8namespace Service::Capture {
9 9
10CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} { 10IScreenShotApplicationService::IScreenShotApplicationService(Core::System& system_)
11 : ServiceFramework{system_, "caps:su"} {
11 // clang-format off 12 // clang-format off
12 static const FunctionInfo functions[] = { 13 static const FunctionInfo functions[] = {
13 {32, &CAPS_SU::SetShimLibraryVersion, "SetShimLibraryVersion"}, 14 {32, &IScreenShotApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"},
14 {201, nullptr, "SaveScreenShot"}, 15 {201, nullptr, "SaveScreenShot"},
15 {203, nullptr, "SaveScreenShotEx0"}, 16 {203, nullptr, "SaveScreenShotEx0"},
16 {205, nullptr, "SaveScreenShotEx1"}, 17 {205, nullptr, "SaveScreenShotEx1"},
@@ -21,9 +22,9 @@ CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} {
21 RegisterHandlers(functions); 22 RegisterHandlers(functions);
22} 23}
23 24
24CAPS_SU::~CAPS_SU() = default; 25IScreenShotApplicationService::~IScreenShotApplicationService() = default;
25 26
26void CAPS_SU::SetShimLibraryVersion(HLERequestContext& ctx) { 27void IScreenShotApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
27 IPC::RequestParser rp{ctx}; 28 IPC::RequestParser rp{ctx};
28 const auto library_version{rp.Pop<u64>()}; 29 const auto library_version{rp.Pop<u64>()};
29 const auto applet_resource_user_id{rp.Pop<u64>()}; 30 const auto applet_resource_user_id{rp.Pop<u64>()};
diff --git a/src/core/hle/service/caps/caps_su.h b/src/core/hle/service/caps/caps_su.h
index c6398858d..647e3059d 100644
--- a/src/core/hle/service/caps/caps_su.h
+++ b/src/core/hle/service/caps/caps_su.h
@@ -11,10 +11,10 @@ class System;
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13 13
14class CAPS_SU final : public ServiceFramework<CAPS_SU> { 14class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> {
15public: 15public:
16 explicit CAPS_SU(Core::System& system_); 16 explicit IScreenShotApplicationService(Core::System& system_);
17 ~CAPS_SU() override; 17 ~IScreenShotApplicationService() override;
18 18
19private: 19private:
20 void SetShimLibraryVersion(HLERequestContext& ctx); 20 void SetShimLibraryVersion(HLERequestContext& ctx);
diff --git a/src/core/hle/service/caps/caps_types.h b/src/core/hle/service/caps/caps_types.h
new file mode 100644
index 000000000..7fd357954
--- /dev/null
+++ b/src/core/hle/service/caps/caps_types.h
@@ -0,0 +1,184 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/common_funcs.h"
7#include "common/common_types.h"
8
9namespace Service::Capture {
10
11// This is nn::album::ImageOrientation
12enum class AlbumImageOrientation {
13 None,
14 Rotate90,
15 Rotate180,
16 Rotate270,
17};
18
19// This is nn::album::AlbumReportOption
20enum class AlbumReportOption : s32 {
21 Disable,
22 Enable,
23};
24
25enum class ContentType : u8 {
26 Screenshot = 0,
27 Movie = 1,
28 ExtraMovie = 3,
29};
30
31enum class AlbumStorage : u8 {
32 Nand,
33 Sd,
34};
35
36enum class ScreenShotDecoderFlag : u64 {
37 None = 0,
38 EnableFancyUpsampling = 1 << 0,
39 EnableBlockSmoothing = 1 << 1,
40};
41
42// This is nn::capsrv::AlbumFileDateTime
43struct AlbumFileDateTime {
44 s16 year{};
45 s8 month{};
46 s8 day{};
47 s8 hour{};
48 s8 minute{};
49 s8 second{};
50 s8 unique_id{};
51
52 friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default;
53 friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
54 if (a.year > b.year) {
55 return true;
56 }
57 if (a.month > b.month) {
58 return true;
59 }
60 if (a.day > b.day) {
61 return true;
62 }
63 if (a.hour > b.hour) {
64 return true;
65 }
66 if (a.minute > b.minute) {
67 return true;
68 }
69 return a.second > b.second;
70 };
71 friend constexpr bool operator<(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
72 if (a.year < b.year) {
73 return true;
74 }
75 if (a.month < b.month) {
76 return true;
77 }
78 if (a.day < b.day) {
79 return true;
80 }
81 if (a.hour < b.hour) {
82 return true;
83 }
84 if (a.minute < b.minute) {
85 return true;
86 }
87 return a.second < b.second;
88 };
89};
90static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime has incorrect size.");
91
92// This is nn::album::AlbumEntry
93struct AlbumFileEntry {
94 u64 size{}; // Size of the entry
95 u64 hash{}; // AES256 with hardcoded key over AlbumEntry
96 AlbumFileDateTime datetime{};
97 AlbumStorage storage{};
98 ContentType content{};
99 INSERT_PADDING_BYTES(5);
100 u8 unknown{}; // Set to 1 on official SW
101};
102static_assert(sizeof(AlbumFileEntry) == 0x20, "AlbumFileEntry has incorrect size.");
103
104struct AlbumFileId {
105 u64 application_id{};
106 AlbumFileDateTime date{};
107 AlbumStorage storage{};
108 ContentType type{};
109 INSERT_PADDING_BYTES(0x5);
110 u8 unknown{};
111
112 friend constexpr bool operator==(const AlbumFileId&, const AlbumFileId&) = default;
113};
114static_assert(sizeof(AlbumFileId) == 0x18, "AlbumFileId is an invalid size");
115
116// This is nn::capsrv::AlbumEntry
117struct AlbumEntry {
118 u64 entry_size{};
119 AlbumFileId file_id{};
120};
121static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry has incorrect size.");
122
123// This is nn::capsrv::ApplicationAlbumEntry
124struct ApplicationAlbumEntry {
125 u64 size{}; // Size of the entry
126 u64 hash{}; // AES256 with hardcoded key over AlbumEntry
127 AlbumFileDateTime datetime{};
128 AlbumStorage storage{};
129 ContentType content{};
130 INSERT_PADDING_BYTES(5);
131 u8 unknown{1}; // Set to 1 on official SW
132};
133static_assert(sizeof(ApplicationAlbumEntry) == 0x20, "ApplicationAlbumEntry has incorrect size.");
134
135// This is nn::capsrv::ApplicationAlbumFileEntry
136struct ApplicationAlbumFileEntry {
137 ApplicationAlbumEntry entry{};
138 AlbumFileDateTime datetime{};
139 u64 unknown{};
140};
141static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30,
142 "ApplicationAlbumFileEntry has incorrect size.");
143
144struct ApplicationData {
145 std::array<u8, 0x400> data{};
146 u32 data_size{};
147};
148static_assert(sizeof(ApplicationData) == 0x404, "ApplicationData is an invalid size");
149
150struct ScreenShotAttribute {
151 u32 unknown_0{};
152 AlbumImageOrientation orientation{};
153 u32 unknown_1{};
154 u32 unknown_2{};
155 INSERT_PADDING_BYTES(0x30);
156};
157static_assert(sizeof(ScreenShotAttribute) == 0x40, "ScreenShotAttribute is an invalid size");
158
159struct ScreenShotDecodeOption {
160 ScreenShotDecoderFlag flags{};
161 INSERT_PADDING_BYTES(0x18);
162};
163static_assert(sizeof(ScreenShotDecodeOption) == 0x20, "ScreenShotDecodeOption is an invalid size");
164
165struct LoadAlbumScreenShotImageOutput {
166 s64 width{};
167 s64 height{};
168 ScreenShotAttribute attribute{};
169 INSERT_PADDING_BYTES(0x400);
170};
171static_assert(sizeof(LoadAlbumScreenShotImageOutput) == 0x450,
172 "LoadAlbumScreenShotImageOutput is an invalid size");
173
174struct LoadAlbumScreenShotImageOutputForApplication {
175 s64 width{};
176 s64 height{};
177 ScreenShotAttribute attribute{};
178 ApplicationData data{};
179 INSERT_PADDING_BYTES(0xAC);
180};
181static_assert(sizeof(LoadAlbumScreenShotImageOutputForApplication) == 0x500,
182 "LoadAlbumScreenShotImageOutput is an invalid size");
183
184} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_u.cpp b/src/core/hle/service/caps/caps_u.cpp
index bffe0f8d0..b6b33fb2f 100644
--- a/src/core/hle/service/caps/caps_u.cpp
+++ b/src/core/hle/service/caps/caps_u.cpp
@@ -2,45 +2,29 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "common/logging/log.h" 4#include "common/logging/log.h"
5#include "core/hle/service/caps/caps.h" 5#include "core/hle/service/caps/caps_manager.h"
6#include "core/hle/service/caps/caps_types.h"
6#include "core/hle/service/caps/caps_u.h" 7#include "core/hle/service/caps/caps_u.h"
7#include "core/hle/service/ipc_helpers.h" 8#include "core/hle/service/ipc_helpers.h"
8 9
9namespace Service::Capture { 10namespace Service::Capture {
10 11
11class IAlbumAccessorApplicationSession final 12IAlbumApplicationService::IAlbumApplicationService(Core::System& system_,
12 : public ServiceFramework<IAlbumAccessorApplicationSession> { 13 std::shared_ptr<AlbumManager> album_manager)
13public: 14 : ServiceFramework{system_, "caps:u"}, manager{album_manager} {
14 explicit IAlbumAccessorApplicationSession(Core::System& system_)
15 : ServiceFramework{system_, "IAlbumAccessorApplicationSession"} {
16 // clang-format off
17 static const FunctionInfo functions[] = {
18 {2001, nullptr, "OpenAlbumMovieReadStream"},
19 {2002, nullptr, "CloseAlbumMovieReadStream"},
20 {2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
21 {2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
22 {2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
23 };
24 // clang-format on
25
26 RegisterHandlers(functions);
27 }
28};
29
30CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} {
31 // clang-format off 15 // clang-format off
32 static const FunctionInfo functions[] = { 16 static const FunctionInfo functions[] = {
33 {32, &CAPS_U::SetShimLibraryVersion, "SetShimLibraryVersion"}, 17 {32, &IAlbumApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"},
34 {102, &CAPS_U::GetAlbumContentsFileListForApplication, "GetAlbumContentsFileListForApplication"}, 18 {102, &IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated, "GetAlbumFileList0AafeAruidDeprecated"},
35 {103, nullptr, "DeleteAlbumContentsFileForApplication"}, 19 {103, nullptr, "DeleteAlbumFileByAruid"},
36 {104, nullptr, "GetAlbumContentsFileSizeForApplication"}, 20 {104, nullptr, "GetAlbumFileSizeByAruid"},
37 {105, nullptr, "DeleteAlbumFileByAruidForDebug"}, 21 {105, nullptr, "DeleteAlbumFileByAruidForDebug"},
38 {110, nullptr, "LoadAlbumContentsFileScreenShotImageForApplication"}, 22 {110, nullptr, "LoadAlbumScreenShotImageByAruid"},
39 {120, nullptr, "LoadAlbumContentsFileThumbnailImageForApplication"}, 23 {120, nullptr, "LoadAlbumScreenShotThumbnailImageByAruid"},
40 {130, nullptr, "PrecheckToCreateContentsForApplication"}, 24 {130, nullptr, "PrecheckToCreateContentsByAruid"},
41 {140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"}, 25 {140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"},
42 {141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"}, 26 {141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"},
43 {142, &CAPS_U::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"}, 27 {142, &IAlbumApplicationService::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"},
44 {143, nullptr, "GetAlbumFileList4AaeUidAruid"}, 28 {143, nullptr, "GetAlbumFileList4AaeUidAruid"},
45 {144, nullptr, "GetAllAlbumFileList3AaeAruid"}, 29 {144, nullptr, "GetAllAlbumFileList3AaeAruid"},
46 {60002, nullptr, "OpenAccessorSessionForApplication"}, 30 {60002, nullptr, "OpenAccessorSessionForApplication"},
@@ -50,9 +34,9 @@ CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} {
50 RegisterHandlers(functions); 34 RegisterHandlers(functions);
51} 35}
52 36
53CAPS_U::~CAPS_U() = default; 37IAlbumApplicationService::~IAlbumApplicationService() = default;
54 38
55void CAPS_U::SetShimLibraryVersion(HLERequestContext& ctx) { 39void IAlbumApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
56 IPC::RequestParser rp{ctx}; 40 IPC::RequestParser rp{ctx};
57 const auto library_version{rp.Pop<u64>()}; 41 const auto library_version{rp.Pop<u64>()};
58 const auto applet_resource_user_id{rp.Pop<u64>()}; 42 const auto applet_resource_user_id{rp.Pop<u64>()};
@@ -64,37 +48,89 @@ void CAPS_U::SetShimLibraryVersion(HLERequestContext& ctx) {
64 rb.Push(ResultSuccess); 48 rb.Push(ResultSuccess);
65} 49}
66 50
67void CAPS_U::GetAlbumContentsFileListForApplication(HLERequestContext& ctx) { 51void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) {
68 // Takes a type-0x6 output buffer containing an array of ApplicationAlbumFileEntry, a PID, an
69 // u8 ContentType, two s64s, and an u64 AppletResourceUserId. Returns an output u64 for total
70 // output entries (which is copied to a s32 by official SW).
71 IPC::RequestParser rp{ctx}; 52 IPC::RequestParser rp{ctx};
72 const auto pid{rp.Pop<s32>()}; 53 struct Parameters {
73 const auto content_type{rp.PopEnum<ContentType>()}; 54 ContentType content_type;
74 const auto start_posix_time{rp.Pop<s64>()}; 55 INSERT_PADDING_BYTES(7);
75 const auto end_posix_time{rp.Pop<s64>()}; 56 s64 start_posix_time;
76 const auto applet_resource_user_id{rp.Pop<u64>()}; 57 s64 end_posix_time;
58 u64 applet_resource_user_id;
59 };
60 static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
77 61
78 // TODO: Update this when we implement the album. 62 const auto parameters{rp.PopRaw<Parameters>()};
79 // Currently we do not have a method of accessing album entries, set this to 0 for now.
80 constexpr u32 total_entries_1{};
81 constexpr u32 total_entries_2{};
82 63
83 LOG_WARNING( 64 LOG_WARNING(Service_Capture,
84 Service_Capture, 65 "(STUBBED) called. content_type={}, start_posix_time={}, end_posix_time={}, "
85 "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, " 66 "applet_resource_user_id={}",
86 "end_posix_time={}, applet_resource_user_id={}, total_entries_1={}, total_entries_2={}", 67 parameters.content_type, parameters.start_posix_time, parameters.end_posix_time,
87 pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id, 68 parameters.applet_resource_user_id);
88 total_entries_1, total_entries_2); 69
70 Result result = ResultSuccess;
71
72 if (result.IsSuccess()) {
73 result = manager->IsAlbumMounted(AlbumStorage::Sd);
74 }
75
76 std::vector<ApplicationAlbumFileEntry> entries;
77 if (result.IsSuccess()) {
78 result = manager->GetAlbumFileList(entries, parameters.content_type,
79 parameters.start_posix_time, parameters.end_posix_time,
80 parameters.applet_resource_user_id);
81 }
82
83 if (!entries.empty()) {
84 ctx.WriteBuffer(entries);
85 }
89 86
90 IPC::ResponseBuilder rb{ctx, 4}; 87 IPC::ResponseBuilder rb{ctx, 4};
91 rb.Push(ResultSuccess); 88 rb.Push(result);
92 rb.Push(total_entries_1); 89 rb.Push<u64>(entries.size());
93 rb.Push(total_entries_2);
94} 90}
95 91
96void CAPS_U::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) { 92void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
97 GetAlbumContentsFileListForApplication(ctx); 93 IPC::RequestParser rp{ctx};
94 struct Parameters {
95 ContentType content_type;
96 INSERT_PADDING_BYTES(1);
97 AlbumFileDateTime start_date_time;
98 AlbumFileDateTime end_date_time;
99 INSERT_PADDING_BYTES(6);
100 u64 applet_resource_user_id;
101 };
102 static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
103
104 const auto parameters{rp.PopRaw<Parameters>()};
105
106 LOG_WARNING(Service_Capture,
107 "(STUBBED) called. content_type={}, start_date={}/{}/{}, "
108 "end_date={}/{}/{}, applet_resource_user_id={}",
109 parameters.content_type, parameters.start_date_time.year,
110 parameters.start_date_time.month, parameters.start_date_time.day,
111 parameters.end_date_time.year, parameters.end_date_time.month,
112 parameters.end_date_time.day, parameters.applet_resource_user_id);
113
114 Result result = ResultSuccess;
115
116 if (result.IsSuccess()) {
117 result = manager->IsAlbumMounted(AlbumStorage::Sd);
118 }
119
120 std::vector<ApplicationAlbumEntry> entries;
121 if (result.IsSuccess()) {
122 result =
123 manager->GetAlbumFileList(entries, parameters.content_type, parameters.start_date_time,
124 parameters.end_date_time, parameters.applet_resource_user_id);
125 }
126
127 if (!entries.empty()) {
128 ctx.WriteBuffer(entries);
129 }
130
131 IPC::ResponseBuilder rb{ctx, 4};
132 rb.Push(result);
133 rb.Push<u64>(entries.size());
98} 134}
99 135
100} // namespace Service::Capture 136} // namespace Service::Capture
diff --git a/src/core/hle/service/caps/caps_u.h b/src/core/hle/service/caps/caps_u.h
index e8dd037d7..9458c128e 100644
--- a/src/core/hle/service/caps/caps_u.h
+++ b/src/core/hle/service/caps/caps_u.h
@@ -10,16 +10,20 @@ class System;
10} 10}
11 11
12namespace Service::Capture { 12namespace Service::Capture {
13class AlbumManager;
13 14
14class CAPS_U final : public ServiceFramework<CAPS_U> { 15class IAlbumApplicationService final : public ServiceFramework<IAlbumApplicationService> {
15public: 16public:
16 explicit CAPS_U(Core::System& system_); 17 explicit IAlbumApplicationService(Core::System& system_,
17 ~CAPS_U() override; 18 std::shared_ptr<AlbumManager> album_manager);
19 ~IAlbumApplicationService() override;
18 20
19private: 21private:
20 void SetShimLibraryVersion(HLERequestContext& ctx); 22 void SetShimLibraryVersion(HLERequestContext& ctx);
21 void GetAlbumContentsFileListForApplication(HLERequestContext& ctx); 23 void GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx);
22 void GetAlbumFileList3AaeAruid(HLERequestContext& ctx); 24 void GetAlbumFileList3AaeAruid(HLERequestContext& ctx);
25
26 std::shared_ptr<AlbumManager> manager = nullptr;
23}; 27};
24 28
25} // namespace Service::Capture 29} // namespace Service::Capture
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index c2054e8a0..126cd6ffd 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -855,6 +855,9 @@ FSP_SRV::FSP_SRV(Core::System& system_)
855 if (Settings::values.enable_fs_access_log) { 855 if (Settings::values.enable_fs_access_log) {
856 access_log_mode = AccessLogMode::SdCard; 856 access_log_mode = AccessLogMode::SdCard;
857 } 857 }
858
859 // This should be true on creation
860 fsc.SetAutoSaveDataCreation(true);
858} 861}
859 862
860FSP_SRV::~FSP_SRV() = default; 863FSP_SRV::~FSP_SRV() = default;
diff --git a/src/core/hle/service/jit/jit_context.cpp b/src/core/hle/service/jit/jit_context.cpp
index 4ed3f02e2..0090e8568 100644
--- a/src/core/hle/service/jit/jit_context.cpp
+++ b/src/core/hle/service/jit/jit_context.cpp
@@ -156,6 +156,8 @@ public:
156 156
157 bool LoadNRO(std::span<const u8> data) { 157 bool LoadNRO(std::span<const u8> data) {
158 local_memory.clear(); 158 local_memory.clear();
159
160 relocbase = local_memory.size();
159 local_memory.insert(local_memory.end(), data.begin(), data.end()); 161 local_memory.insert(local_memory.end(), data.begin(), data.end());
160 162
161 if (FixupRelocations()) { 163 if (FixupRelocations()) {
@@ -181,8 +183,8 @@ public:
181 // https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html 183 // https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
182 // https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html 184 // https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html
183 VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)}; 185 VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)};
184 VAddr rela_dyn = 0; 186 VAddr rela_dyn = 0, relr_dyn = 0;
185 size_t num_rela = 0; 187 size_t num_rela = 0, num_relr = 0;
186 while (true) { 188 while (true) {
187 const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)}; 189 const auto dyn{callbacks->ReadMemory<Elf64_Dyn>(dynamic_offset)};
188 dynamic_offset += sizeof(Elf64_Dyn); 190 dynamic_offset += sizeof(Elf64_Dyn);
@@ -196,6 +198,12 @@ public:
196 if (dyn.d_tag == ElfDtRelasz) { 198 if (dyn.d_tag == ElfDtRelasz) {
197 num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela); 199 num_rela = dyn.d_un.d_val / sizeof(Elf64_Rela);
198 } 200 }
201 if (dyn.d_tag == ElfDtRelr) {
202 relr_dyn = dyn.d_un.d_ptr;
203 }
204 if (dyn.d_tag == ElfDtRelrsz) {
205 num_relr = dyn.d_un.d_val / sizeof(Elf64_Relr);
206 }
199 } 207 }
200 208
201 for (size_t i = 0; i < num_rela; i++) { 209 for (size_t i = 0; i < num_rela; i++) {
@@ -207,6 +215,29 @@ public:
207 callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend); 215 callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend);
208 } 216 }
209 217
218 VAddr relr_where = 0;
219 for (size_t i = 0; i < num_relr; i++) {
220 const auto relr{callbacks->ReadMemory<Elf64_Relr>(relr_dyn + i * sizeof(Elf64_Relr))};
221 const auto incr{[&](VAddr where) {
222 callbacks->MemoryWrite64(where, callbacks->MemoryRead64(where) + relocbase);
223 }};
224
225 if ((relr & 1) == 0) {
226 // where pointer
227 relr_where = relocbase + relr;
228 incr(relr_where);
229 relr_where += sizeof(Elf64_Addr);
230 } else {
231 // bitmap
232 for (int bit = 1; bit < 64; bit++) {
233 if ((relr & (1ULL << bit)) != 0) {
234 incr(relr_where + i * sizeof(Elf64_Addr));
235 }
236 }
237 relr_where += 63 * sizeof(Elf64_Addr);
238 }
239 }
240
210 return true; 241 return true;
211 } 242 }
212 243
@@ -313,6 +344,7 @@ public:
313 Core::Memory::Memory& memory; 344 Core::Memory::Memory& memory;
314 VAddr top_of_stack; 345 VAddr top_of_stack;
315 VAddr heap_pointer; 346 VAddr heap_pointer;
347 VAddr relocbase;
316}; 348};
317 349
318void DynarmicCallbacks64::CallSVC(u32 swi) { 350void DynarmicCallbacks64::CallSVC(u32 swi) {
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index 9d149a7cd..7927f8264 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -23,19 +23,39 @@ public:
23 explicit IMonitorService(Core::System& system_) : ServiceFramework{system_, "IMonitorService"} { 23 explicit IMonitorService(Core::System& system_) : ServiceFramework{system_, "IMonitorService"} {
24 // clang-format off 24 // clang-format off
25 static const FunctionInfo functions[] = { 25 static const FunctionInfo functions[] = {
26 {0, nullptr, "GetStateForMonitor"}, 26 {0, &IMonitorService::GetStateForMonitor, "GetStateForMonitor"},
27 {1, nullptr, "GetNetworkInfoForMonitor"}, 27 {1, nullptr, "GetNetworkInfoForMonitor"},
28 {2, nullptr, "GetIpv4AddressForMonitor"}, 28 {2, nullptr, "GetIpv4AddressForMonitor"},
29 {3, nullptr, "GetDisconnectReasonForMonitor"}, 29 {3, nullptr, "GetDisconnectReasonForMonitor"},
30 {4, nullptr, "GetSecurityParameterForMonitor"}, 30 {4, nullptr, "GetSecurityParameterForMonitor"},
31 {5, nullptr, "GetNetworkConfigForMonitor"}, 31 {5, nullptr, "GetNetworkConfigForMonitor"},
32 {100, nullptr, "InitializeMonitor"}, 32 {100, &IMonitorService::InitializeMonitor, "InitializeMonitor"},
33 {101, nullptr, "FinalizeMonitor"}, 33 {101, nullptr, "FinalizeMonitor"},
34 }; 34 };
35 // clang-format on 35 // clang-format on
36 36
37 RegisterHandlers(functions); 37 RegisterHandlers(functions);
38 } 38 }
39
40private:
41 void GetStateForMonitor(HLERequestContext& ctx) {
42 LOG_INFO(Service_LDN, "called");
43
44 IPC::ResponseBuilder rb{ctx, 3};
45 rb.Push(ResultSuccess);
46 rb.PushEnum(state);
47 }
48
49 void InitializeMonitor(HLERequestContext& ctx) {
50 LOG_INFO(Service_LDN, "called");
51
52 state = State::Initialized;
53
54 IPC::ResponseBuilder rb{ctx, 2};
55 rb.Push(ResultSuccess);
56 }
57
58 State state{State::None};
39}; 59};
40 60
41class LDNM final : public ServiceFramework<LDNM> { 61class LDNM final : public ServiceFramework<LDNM> {
@@ -731,14 +751,81 @@ public:
731 } 751 }
732}; 752};
733 753
754class ISfMonitorService final : public ServiceFramework<ISfMonitorService> {
755public:
756 explicit ISfMonitorService(Core::System& system_)
757 : ServiceFramework{system_, "ISfMonitorService"} {
758 // clang-format off
759 static const FunctionInfo functions[] = {
760 {0, &ISfMonitorService::Initialize, "Initialize"},
761 {288, &ISfMonitorService::GetGroupInfo, "GetGroupInfo"},
762 {320, nullptr, "GetLinkLevel"},
763 };
764 // clang-format on
765
766 RegisterHandlers(functions);
767 }
768
769private:
770 void Initialize(HLERequestContext& ctx) {
771 LOG_WARNING(Service_LDN, "(STUBBED) called");
772
773 IPC::ResponseBuilder rb{ctx, 3};
774 rb.Push(ResultSuccess);
775 rb.Push(0);
776 }
777
778 void GetGroupInfo(HLERequestContext& ctx) {
779 LOG_WARNING(Service_LDN, "(STUBBED) called");
780
781 struct GroupInfo {
782 std::array<u8, 0x200> info;
783 };
784
785 GroupInfo group_info{};
786
787 ctx.WriteBuffer(group_info);
788 IPC::ResponseBuilder rb{ctx, 2};
789 rb.Push(ResultSuccess);
790 }
791};
792
793class LP2PM final : public ServiceFramework<LP2PM> {
794public:
795 explicit LP2PM(Core::System& system_) : ServiceFramework{system_, "lp2p:m"} {
796 // clang-format off
797 static const FunctionInfo functions[] = {
798 {0, &LP2PM::CreateMonitorService, "CreateMonitorService"},
799 };
800 // clang-format on
801
802 RegisterHandlers(functions);
803 }
804
805private:
806 void CreateMonitorService(HLERequestContext& ctx) {
807 IPC::RequestParser rp{ctx};
808 const u64 reserved_input = rp.Pop<u64>();
809
810 LOG_INFO(Service_LDN, "called, reserved_input={}", reserved_input);
811
812 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
813 rb.Push(ResultSuccess);
814 rb.PushIpcInterface<ISfMonitorService>(system);
815 }
816};
817
734void LoopProcess(Core::System& system) { 818void LoopProcess(Core::System& system) {
735 auto server_manager = std::make_unique<ServerManager>(system); 819 auto server_manager = std::make_unique<ServerManager>(system);
736 820
737 server_manager->RegisterNamedService("ldn:m", std::make_shared<LDNM>(system)); 821 server_manager->RegisterNamedService("ldn:m", std::make_shared<LDNM>(system));
738 server_manager->RegisterNamedService("ldn:s", std::make_shared<LDNS>(system)); 822 server_manager->RegisterNamedService("ldn:s", std::make_shared<LDNS>(system));
739 server_manager->RegisterNamedService("ldn:u", std::make_shared<LDNU>(system)); 823 server_manager->RegisterNamedService("ldn:u", std::make_shared<LDNU>(system));
824
740 server_manager->RegisterNamedService("lp2p:app", std::make_shared<LP2PAPP>(system)); 825 server_manager->RegisterNamedService("lp2p:app", std::make_shared<LP2PAPP>(system));
741 server_manager->RegisterNamedService("lp2p:sys", std::make_shared<LP2PSYS>(system)); 826 server_manager->RegisterNamedService("lp2p:sys", std::make_shared<LP2PSYS>(system));
827 server_manager->RegisterNamedService("lp2p:m", std::make_shared<LP2PM>(system));
828
742 ServerManager::RunServer(std::move(server_manager)); 829 ServerManager::RunServer(std::move(server_manager));
743} 830}
744 831
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index 68c407f81..e7a00deb3 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -830,11 +830,6 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
830 return ResultWrongDeviceState; 830 return ResultWrongDeviceState;
831 } 831 }
832 832
833 Service::Mii::StoreData store_data{};
834 Service::Mii::NfpStoreDataExtension extension{};
835 store_data.BuildBase(Mii::Gender::Male);
836 extension.SetFromStoreData(store_data);
837
838 auto& settings = tag_data.settings; 833 auto& settings = tag_data.settings;
839 834
840 if (tag_data.settings.settings.amiibo_initialized == 0) { 835 if (tag_data.settings.settings.amiibo_initialized == 0) {
@@ -843,8 +838,8 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
843 } 838 }
844 839
845 SetAmiiboName(settings, register_info.amiibo_name); 840 SetAmiiboName(settings, register_info.amiibo_name);
846 tag_data.owner_mii.BuildFromStoreData(store_data); 841 tag_data.owner_mii.BuildFromStoreData(register_info.mii_store_data);
847 tag_data.mii_extension = extension; 842 tag_data.mii_extension.SetFromStoreData(register_info.mii_store_data);
848 tag_data.unknown = 0; 843 tag_data.unknown = 0;
849 tag_data.unknown2 = {}; 844 tag_data.unknown2 = {};
850 settings.country_code_id = 0; 845 settings.country_code_id = 0;
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index 21b06d10b..22dc55a6d 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -545,6 +545,16 @@ void IGeneralService::IsAnyInternetRequestAccepted(HLERequestContext& ctx) {
545 } 545 }
546} 546}
547 547
548void IGeneralService::IsAnyForegroundRequestAccepted(HLERequestContext& ctx) {
549 const bool is_accepted{};
550
551 LOG_WARNING(Service_NIFM, "(STUBBED) called, is_accepted={}", is_accepted);
552
553 IPC::ResponseBuilder rb{ctx, 3};
554 rb.Push(ResultSuccess);
555 rb.Push<u8>(is_accepted);
556}
557
548IGeneralService::IGeneralService(Core::System& system_) 558IGeneralService::IGeneralService(Core::System& system_)
549 : ServiceFramework{system_, "IGeneralService"}, network{system_.GetRoomNetwork()} { 559 : ServiceFramework{system_, "IGeneralService"}, network{system_.GetRoomNetwork()} {
550 // clang-format off 560 // clang-format off
@@ -569,7 +579,7 @@ IGeneralService::IGeneralService(Core::System& system_)
569 {19, nullptr, "SetEthernetCommunicationEnabled"}, 579 {19, nullptr, "SetEthernetCommunicationEnabled"},
570 {20, &IGeneralService::IsEthernetCommunicationEnabled, "IsEthernetCommunicationEnabled"}, 580 {20, &IGeneralService::IsEthernetCommunicationEnabled, "IsEthernetCommunicationEnabled"},
571 {21, &IGeneralService::IsAnyInternetRequestAccepted, "IsAnyInternetRequestAccepted"}, 581 {21, &IGeneralService::IsAnyInternetRequestAccepted, "IsAnyInternetRequestAccepted"},
572 {22, nullptr, "IsAnyForegroundRequestAccepted"}, 582 {22, &IGeneralService::IsAnyForegroundRequestAccepted, "IsAnyForegroundRequestAccepted"},
573 {23, nullptr, "PutToSleep"}, 583 {23, nullptr, "PutToSleep"},
574 {24, nullptr, "WakeUp"}, 584 {24, nullptr, "WakeUp"},
575 {25, nullptr, "GetSsidListVersion"}, 585 {25, nullptr, "GetSsidListVersion"},
diff --git a/src/core/hle/service/nifm/nifm.h b/src/core/hle/service/nifm/nifm.h
index ae99c4695..b74b66438 100644
--- a/src/core/hle/service/nifm/nifm.h
+++ b/src/core/hle/service/nifm/nifm.h
@@ -35,6 +35,7 @@ private:
35 void GetInternetConnectionStatus(HLERequestContext& ctx); 35 void GetInternetConnectionStatus(HLERequestContext& ctx);
36 void IsEthernetCommunicationEnabled(HLERequestContext& ctx); 36 void IsEthernetCommunicationEnabled(HLERequestContext& ctx);
37 void IsAnyInternetRequestAccepted(HLERequestContext& ctx); 37 void IsAnyInternetRequestAccepted(HLERequestContext& ctx);
38 void IsAnyForegroundRequestAccepted(HLERequestContext& ctx);
38 39
39 Network::RoomNetwork& network; 40 Network::RoomNetwork& network;
40}; 41};
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 6e0baf0be..f9e0e272d 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -7,6 +7,7 @@
7#include "core/file_sys/control_metadata.h" 7#include "core/file_sys/control_metadata.h"
8#include "core/file_sys/patch_manager.h" 8#include "core/file_sys/patch_manager.h"
9#include "core/file_sys/vfs.h" 9#include "core/file_sys/vfs.h"
10#include "core/hle/service/filesystem/filesystem.h"
10#include "core/hle/service/glue/glue_manager.h" 11#include "core/hle/service/glue/glue_manager.h"
11#include "core/hle/service/ipc_helpers.h" 12#include "core/hle/service/ipc_helpers.h"
12#include "core/hle/service/ns/errors.h" 13#include "core/hle/service/ns/errors.h"
@@ -502,8 +503,8 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_)
502 static const FunctionInfo functions[] = { 503 static const FunctionInfo functions[] = {
503 {11, nullptr, "CalculateApplicationOccupiedSize"}, 504 {11, nullptr, "CalculateApplicationOccupiedSize"},
504 {43, nullptr, "CheckSdCardMountStatus"}, 505 {43, nullptr, "CheckSdCardMountStatus"},
505 {47, nullptr, "GetTotalSpaceSize"}, 506 {47, &IContentManagementInterface::GetTotalSpaceSize, "GetTotalSpaceSize"},
506 {48, nullptr, "GetFreeSpaceSize"}, 507 {48, &IContentManagementInterface::GetFreeSpaceSize, "GetFreeSpaceSize"},
507 {600, nullptr, "CountApplicationContentMeta"}, 508 {600, nullptr, "CountApplicationContentMeta"},
508 {601, nullptr, "ListApplicationContentMetaStatus"}, 509 {601, nullptr, "ListApplicationContentMetaStatus"},
509 {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"}, 510 {605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
@@ -516,6 +517,28 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_)
516 517
517IContentManagementInterface::~IContentManagementInterface() = default; 518IContentManagementInterface::~IContentManagementInterface() = default;
518 519
520void IContentManagementInterface::GetTotalSpaceSize(HLERequestContext& ctx) {
521 IPC::RequestParser rp{ctx};
522 const auto storage{rp.PopEnum<FileSys::StorageId>()};
523
524 LOG_INFO(Service_Capture, "called, storage={}", storage);
525
526 IPC::ResponseBuilder rb{ctx, 4};
527 rb.Push(ResultSuccess);
528 rb.Push<u64>(system.GetFileSystemController().GetTotalSpaceSize(storage));
529}
530
531void IContentManagementInterface::GetFreeSpaceSize(HLERequestContext& ctx) {
532 IPC::RequestParser rp{ctx};
533 const auto storage{rp.PopEnum<FileSys::StorageId>()};
534
535 LOG_INFO(Service_Capture, "called, storage={}", storage);
536
537 IPC::ResponseBuilder rb{ctx, 4};
538 rb.Push(ResultSuccess);
539 rb.Push<u64>(system.GetFileSystemController().GetFreeSpaceSize(storage));
540}
541
519IDocumentInterface::IDocumentInterface(Core::System& system_) 542IDocumentInterface::IDocumentInterface(Core::System& system_)
520 : ServiceFramework{system_, "IDocumentInterface"} { 543 : ServiceFramework{system_, "IDocumentInterface"} {
521 // clang-format off 544 // clang-format off
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
index 175dad780..34d2a45dc 100644
--- a/src/core/hle/service/ns/ns.h
+++ b/src/core/hle/service/ns/ns.h
@@ -48,6 +48,10 @@ class IContentManagementInterface final : public ServiceFramework<IContentManage
48public: 48public:
49 explicit IContentManagementInterface(Core::System& system_); 49 explicit IContentManagementInterface(Core::System& system_);
50 ~IContentManagementInterface() override; 50 ~IContentManagementInterface() override;
51
52private:
53 void GetTotalSpaceSize(HLERequestContext& ctx);
54 void GetFreeSpaceSize(HLERequestContext& ctx);
51}; 55};
52 56
53class IDocumentInterface final : public ServiceFramework<IDocumentInterface> { 57class IDocumentInterface final : public ServiceFramework<IDocumentInterface> {
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h
index 40c65b430..4c0cc71cd 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.h
+++ b/src/core/hle/service/nvdrv/devices/nvmap.h
@@ -45,13 +45,6 @@ public:
45 IsSharedMemMapped = 6 45 IsSharedMemMapped = 6
46 }; 46 };
47 47
48private:
49 /// Id to use for the next handle that is created.
50 u32 next_handle = 0;
51
52 /// Id to use for the next object that is created.
53 u32 next_id = 0;
54
55 struct IocCreateParams { 48 struct IocCreateParams {
56 // Input 49 // Input
57 u32_le size{}; 50 u32_le size{};
@@ -113,6 +106,13 @@ private:
113 NvResult IocParam(std::span<const u8> input, std::span<u8> output); 106 NvResult IocParam(std::span<const u8> input, std::span<u8> output);
114 NvResult IocFree(std::span<const u8> input, std::span<u8> output); 107 NvResult IocFree(std::span<const u8> input, std::span<u8> output);
115 108
109private:
110 /// Id to use for the next handle that is created.
111 u32 next_handle = 0;
112
113 /// Id to use for the next object that is created.
114 u32 next_id = 0;
115
116 NvCore::Container& container; 116 NvCore::Container& container;
117 NvCore::NvMap& file; 117 NvCore::NvMap& file;
118}; 118};
diff --git a/src/core/hle/service/nvnflinger/buffer_item.h b/src/core/hle/service/nvnflinger/buffer_item.h
index 7fd808f54..3da8cc3aa 100644
--- a/src/core/hle/service/nvnflinger/buffer_item.h
+++ b/src/core/hle/service/nvnflinger/buffer_item.h
@@ -15,7 +15,7 @@
15 15
16namespace Service::android { 16namespace Service::android {
17 17
18class GraphicBuffer; 18struct GraphicBuffer;
19 19
20class BufferItem final { 20class BufferItem final {
21public: 21public:
diff --git a/src/core/hle/service/nvnflinger/buffer_slot.h b/src/core/hle/service/nvnflinger/buffer_slot.h
index d25bca049..d8c9dec3b 100644
--- a/src/core/hle/service/nvnflinger/buffer_slot.h
+++ b/src/core/hle/service/nvnflinger/buffer_slot.h
@@ -13,7 +13,7 @@
13 13
14namespace Service::android { 14namespace Service::android {
15 15
16class GraphicBuffer; 16struct GraphicBuffer;
17 17
18enum class BufferState : u32 { 18enum class BufferState : u32 {
19 Free = 0, 19 Free = 0,
diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
new file mode 100644
index 000000000..469a53244
--- /dev/null
+++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.cpp
@@ -0,0 +1,351 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <random>
5
6#include "core/core.h"
7#include "core/hle/kernel/k_process.h"
8#include "core/hle/kernel/k_system_resource.h"
9#include "core/hle/service/nvdrv/devices/nvmap.h"
10#include "core/hle/service/nvdrv/nvdrv.h"
11#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
12#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
13#include "core/hle/service/nvnflinger/pixel_format.h"
14#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
15#include "core/hle/service/vi/layer/vi_layer.h"
16#include "core/hle/service/vi/vi_results.h"
17
18namespace Service::Nvnflinger {
19
20namespace {
21
22Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address,
23 std::unique_ptr<Kernel::KPageGroup>* out_page_group,
24 Core::System& system, u32 size) {
25 using Core::Memory::YUZU_PAGESIZE;
26
27 // Allocate memory for the system shared buffer.
28 // FIXME: Because the gmmu can only point to cpu addresses, we need
29 // to map this in the application space to allow it to be used.
30 // FIXME: Add proper smmu emulation.
31 // FIXME: This memory belongs to vi's .data section.
32 auto& kernel = system.Kernel();
33 auto* process = system.ApplicationProcess();
34 auto& page_table = process->GetPageTable();
35
36 // Hold a temporary page group reference while we try to map it.
37 auto pg = std::make_unique<Kernel::KPageGroup>(
38 kernel, std::addressof(kernel.GetSystemSystemResource().GetBlockInfoManager()));
39
40 // Allocate memory from secure pool.
41 R_TRY(kernel.MemoryManager().AllocateAndOpen(
42 pg.get(), size / YUZU_PAGESIZE,
43 Kernel::KMemoryManager::EncodeOption(Kernel::KMemoryManager::Pool::Secure,
44 Kernel::KMemoryManager::Direction::FromBack)));
45
46 // Get bounds of where mapping is possible.
47 const VAddr alias_code_begin = GetInteger(page_table.GetAliasCodeRegionStart());
48 const VAddr alias_code_size = page_table.GetAliasCodeRegionSize() / YUZU_PAGESIZE;
49 const auto state = Kernel::KMemoryState::Io;
50 const auto perm = Kernel::KMemoryPermission::UserReadWrite;
51 std::mt19937_64 rng{process->GetRandomEntropy(0)};
52
53 // Retry up to 64 times to map into alias code range.
54 Result res = ResultSuccess;
55 int i;
56 for (i = 0; i < 64; i++) {
57 *out_map_address = alias_code_begin + ((rng() % alias_code_size) * YUZU_PAGESIZE);
58 res = page_table.MapPageGroup(*out_map_address, *pg, state, perm);
59 if (R_SUCCEEDED(res)) {
60 break;
61 }
62 }
63
64 // Return failure, if necessary
65 R_UNLESS(i < 64, res);
66
67 // Return the mapped page group.
68 *out_page_group = std::move(pg);
69
70 // We succeeded.
71 R_SUCCEED();
72}
73
74template <typename T>
75std::span<u8> SerializeIoc(T& params) {
76 return std::span(reinterpret_cast<u8*>(std::addressof(params)), sizeof(T));
77}
78
79Result CreateNvMapHandle(u32* out_nv_map_handle, Nvidia::Devices::nvmap& nvmap, u32 size) {
80 // Create a handle.
81 Nvidia::Devices::nvmap::IocCreateParams create_in_params{
82 .size = size,
83 .handle = 0,
84 };
85 Nvidia::Devices::nvmap::IocCreateParams create_out_params{};
86 R_UNLESS(nvmap.IocCreate(SerializeIoc(create_in_params), SerializeIoc(create_out_params)) ==
87 Nvidia::NvResult::Success,
88 VI::ResultOperationFailed);
89
90 // Assign the output handle.
91 *out_nv_map_handle = create_out_params.handle;
92
93 // We succeeded.
94 R_SUCCEED();
95}
96
97Result FreeNvMapHandle(Nvidia::Devices::nvmap& nvmap, u32 handle) {
98 // Free the handle.
99 Nvidia::Devices::nvmap::IocFreeParams free_in_params{
100 .handle = handle,
101 };
102 Nvidia::Devices::nvmap::IocFreeParams free_out_params{};
103 R_UNLESS(nvmap.IocFree(SerializeIoc(free_in_params), SerializeIoc(free_out_params)) ==
104 Nvidia::NvResult::Success,
105 VI::ResultOperationFailed);
106
107 // We succeeded.
108 R_SUCCEED();
109}
110
111Result AllocNvMapHandle(Nvidia::Devices::nvmap& nvmap, u32 handle, Common::ProcessAddress buffer,
112 u32 size) {
113 // Assign the allocated memory to the handle.
114 Nvidia::Devices::nvmap::IocAllocParams alloc_in_params{
115 .handle = handle,
116 .heap_mask = 0,
117 .flags = {},
118 .align = 0,
119 .kind = 0,
120 .address = GetInteger(buffer),
121 };
122 Nvidia::Devices::nvmap::IocAllocParams alloc_out_params{};
123 R_UNLESS(nvmap.IocAlloc(SerializeIoc(alloc_in_params), SerializeIoc(alloc_out_params)) ==
124 Nvidia::NvResult::Success,
125 VI::ResultOperationFailed);
126
127 // We succeeded.
128 R_SUCCEED();
129}
130
131Result AllocateHandleForBuffer(u32* out_handle, Nvidia::Module& nvdrv,
132 Common::ProcessAddress buffer, u32 size) {
133 // Get the nvmap device.
134 auto nvmap_fd = nvdrv.Open("/dev/nvmap");
135 auto nvmap = nvdrv.GetDevice<Nvidia::Devices::nvmap>(nvmap_fd);
136 ASSERT(nvmap != nullptr);
137
138 // Create a handle.
139 R_TRY(CreateNvMapHandle(out_handle, *nvmap, size));
140
141 // Ensure we maintain a clean state on failure.
142 ON_RESULT_FAILURE {
143 ASSERT(R_SUCCEEDED(FreeNvMapHandle(*nvmap, *out_handle)));
144 };
145
146 // Assign the allocated memory to the handle.
147 R_RETURN(AllocNvMapHandle(*nvmap, *out_handle, buffer, size));
148}
149
150constexpr auto SharedBufferBlockLinearFormat = android::PixelFormat::Rgba8888;
151constexpr u32 SharedBufferBlockLinearBpp = 4;
152
153constexpr u32 SharedBufferBlockLinearWidth = 1280;
154constexpr u32 SharedBufferBlockLinearHeight = 768;
155constexpr u32 SharedBufferBlockLinearStride =
156 SharedBufferBlockLinearWidth * SharedBufferBlockLinearBpp;
157constexpr u32 SharedBufferNumSlots = 7;
158
159constexpr u32 SharedBufferWidth = 1280;
160constexpr u32 SharedBufferHeight = 720;
161constexpr u32 SharedBufferAsync = false;
162
163constexpr u32 SharedBufferSlotSize =
164 SharedBufferBlockLinearWidth * SharedBufferBlockLinearHeight * SharedBufferBlockLinearBpp;
165constexpr u32 SharedBufferSize = SharedBufferSlotSize * SharedBufferNumSlots;
166
167constexpr SharedMemoryPoolLayout SharedBufferPoolLayout = [] {
168 SharedMemoryPoolLayout layout{};
169 layout.num_slots = SharedBufferNumSlots;
170
171 for (u32 i = 0; i < SharedBufferNumSlots; i++) {
172 layout.slots[i].buffer_offset = i * SharedBufferSlotSize;
173 layout.slots[i].size = SharedBufferSlotSize;
174 layout.slots[i].width = SharedBufferWidth;
175 layout.slots[i].height = SharedBufferHeight;
176 }
177
178 return layout;
179}();
180
181void MakeGraphicBuffer(android::BufferQueueProducer& producer, u32 slot, u32 handle) {
182 auto buffer = std::make_shared<android::GraphicBuffer>();
183 buffer->width = SharedBufferWidth;
184 buffer->height = SharedBufferHeight;
185 buffer->stride = SharedBufferBlockLinearStride;
186 buffer->format = SharedBufferBlockLinearFormat;
187 buffer->buffer_id = handle;
188 buffer->offset = slot * SharedBufferSlotSize;
189 ASSERT(producer.SetPreallocatedBuffer(slot, buffer) == android::Status::NoError);
190}
191
192} // namespace
193
194FbShareBufferManager::FbShareBufferManager(Core::System& system, Nvnflinger& flinger,
195 std::shared_ptr<Nvidia::Module> nvdrv)
196 : m_system(system), m_flinger(flinger), m_nvdrv(std::move(nvdrv)) {}
197
198FbShareBufferManager::~FbShareBufferManager() = default;
199
200Result FbShareBufferManager::Initialize(u64* out_buffer_id, u64* out_layer_id, u64 display_id) {
201 std::scoped_lock lk{m_guard};
202
203 // Ensure we have not already created a buffer.
204 R_UNLESS(m_buffer_id == 0, VI::ResultOperationFailed);
205
206 // Allocate memory and space for the shared buffer.
207 Common::ProcessAddress map_address;
208 R_TRY(AllocateIoForProcessAddressSpace(std::addressof(map_address),
209 std::addressof(m_buffer_page_group), m_system,
210 SharedBufferSize));
211
212 // Create an nvmap handle for the buffer and assign the memory to it.
213 R_TRY(AllocateHandleForBuffer(std::addressof(m_buffer_nvmap_handle), *m_nvdrv, map_address,
214 SharedBufferSize));
215
216 // Record the display id.
217 m_display_id = display_id;
218
219 // Create a layer for the display.
220 m_layer_id = m_flinger.CreateLayer(m_display_id).value();
221
222 // Set up the buffer.
223 m_buffer_id = m_next_buffer_id++;
224
225 // Get the layer.
226 VI::Layer* layer = m_flinger.FindLayer(m_display_id, m_layer_id);
227 ASSERT(layer != nullptr);
228
229 // Get the producer and set preallocated buffers.
230 auto& producer = layer->GetBufferQueue();
231 MakeGraphicBuffer(producer, 0, m_buffer_nvmap_handle);
232 MakeGraphicBuffer(producer, 1, m_buffer_nvmap_handle);
233
234 // Assign outputs.
235 *out_buffer_id = m_buffer_id;
236 *out_layer_id = m_layer_id;
237
238 // We succeeded.
239 R_SUCCEED();
240}
241
242Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size,
243 s32* out_nvmap_handle,
244 SharedMemoryPoolLayout* out_pool_layout,
245 u64 buffer_id,
246 u64 applet_resource_user_id) {
247 std::scoped_lock lk{m_guard};
248
249 R_UNLESS(m_buffer_id > 0, VI::ResultNotFound);
250 R_UNLESS(buffer_id == m_buffer_id, VI::ResultNotFound);
251
252 *out_pool_layout = SharedBufferPoolLayout;
253 *out_buffer_size = SharedBufferSize;
254 *out_nvmap_handle = m_buffer_nvmap_handle;
255
256 R_SUCCEED();
257}
258
259Result FbShareBufferManager::GetLayerFromId(VI::Layer** out_layer, u64 layer_id) {
260 // Ensure the layer id is valid.
261 R_UNLESS(m_layer_id > 0 && layer_id == m_layer_id, VI::ResultNotFound);
262
263 // Get the layer.
264 VI::Layer* layer = m_flinger.FindLayer(m_display_id, layer_id);
265 R_UNLESS(layer != nullptr, VI::ResultNotFound);
266
267 // We succeeded.
268 *out_layer = layer;
269 R_SUCCEED();
270}
271
272Result FbShareBufferManager::AcquireSharedFrameBuffer(android::Fence* out_fence,
273 std::array<s32, 4>& out_slot_indexes,
274 s64* out_target_slot, u64 layer_id) {
275 std::scoped_lock lk{m_guard};
276
277 // Get the layer.
278 VI::Layer* layer;
279 R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id));
280
281 // Get the producer.
282 auto& producer = layer->GetBufferQueue();
283
284 // Get the next buffer from the producer.
285 s32 slot;
286 R_UNLESS(producer.DequeueBuffer(std::addressof(slot), out_fence, SharedBufferAsync != 0,
287 SharedBufferWidth, SharedBufferHeight,
288 SharedBufferBlockLinearFormat, 0) == android::Status::NoError,
289 VI::ResultOperationFailed);
290
291 // Assign remaining outputs.
292 *out_target_slot = slot;
293 out_slot_indexes = {0, 1, -1, -1};
294
295 // We succeeded.
296 R_SUCCEED();
297}
298
299Result FbShareBufferManager::PresentSharedFrameBuffer(android::Fence fence,
300 Common::Rectangle<s32> crop_region,
301 u32 transform, s32 swap_interval,
302 u64 layer_id, s64 slot) {
303 std::scoped_lock lk{m_guard};
304
305 // Get the layer.
306 VI::Layer* layer;
307 R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id));
308
309 // Get the producer.
310 auto& producer = layer->GetBufferQueue();
311
312 // Request to queue the buffer.
313 std::shared_ptr<android::GraphicBuffer> buffer;
314 R_UNLESS(producer.RequestBuffer(static_cast<s32>(slot), std::addressof(buffer)) ==
315 android::Status::NoError,
316 VI::ResultOperationFailed);
317
318 // Queue the buffer to the producer.
319 android::QueueBufferInput input{};
320 android::QueueBufferOutput output{};
321 input.crop = crop_region;
322 input.fence = fence;
323 input.transform = static_cast<android::NativeWindowTransform>(transform);
324 input.swap_interval = swap_interval;
325 R_UNLESS(producer.QueueBuffer(static_cast<s32>(slot), input, std::addressof(output)) ==
326 android::Status::NoError,
327 VI::ResultOperationFailed);
328
329 // We succeeded.
330 R_SUCCEED();
331}
332
333Result FbShareBufferManager::GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event,
334 u64 layer_id) {
335 std::scoped_lock lk{m_guard};
336
337 // Get the layer.
338 VI::Layer* layer;
339 R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id));
340
341 // Get the producer.
342 auto& producer = layer->GetBufferQueue();
343
344 // Set the event.
345 *out_event = std::addressof(producer.GetNativeHandle());
346
347 // We succeeded.
348 R_SUCCEED();
349}
350
351} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
new file mode 100644
index 000000000..c809c01b4
--- /dev/null
+++ b/src/core/hle/service/nvnflinger/fb_share_buffer_manager.h
@@ -0,0 +1,65 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include "common/math_util.h"
7#include "core/hle/service/nvnflinger/nvnflinger.h"
8#include "core/hle/service/nvnflinger/ui/fence.h"
9
10namespace Kernel {
11class KPageGroup;
12}
13
14namespace Service::Nvnflinger {
15
16struct SharedMemorySlot {
17 u64 buffer_offset;
18 u64 size;
19 s32 width;
20 s32 height;
21};
22static_assert(sizeof(SharedMemorySlot) == 0x18, "SharedMemorySlot has wrong size");
23
24struct SharedMemoryPoolLayout {
25 s32 num_slots;
26 std::array<SharedMemorySlot, 0x10> slots;
27};
28static_assert(sizeof(SharedMemoryPoolLayout) == 0x188, "SharedMemoryPoolLayout has wrong size");
29
30class FbShareBufferManager final {
31public:
32 explicit FbShareBufferManager(Core::System& system, Nvnflinger& flinger,
33 std::shared_ptr<Nvidia::Module> nvdrv);
34 ~FbShareBufferManager();
35
36 Result Initialize(u64* out_buffer_id, u64* out_layer_handle, u64 display_id);
37 Result GetSharedBufferMemoryHandleId(u64* out_buffer_size, s32* out_nvmap_handle,
38 SharedMemoryPoolLayout* out_pool_layout, u64 buffer_id,
39 u64 applet_resource_user_id);
40 Result AcquireSharedFrameBuffer(android::Fence* out_fence, std::array<s32, 4>& out_slots,
41 s64* out_target_slot, u64 layer_id);
42 Result PresentSharedFrameBuffer(android::Fence fence, Common::Rectangle<s32> crop_region,
43 u32 transform, s32 swap_interval, u64 layer_id, s64 slot);
44 Result GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event, u64 layer_id);
45
46private:
47 Result GetLayerFromId(VI::Layer** out_layer, u64 layer_id);
48
49private:
50 u64 m_next_buffer_id = 1;
51 u64 m_display_id = 0;
52 u64 m_buffer_id = 0;
53 u64 m_layer_id = 0;
54 u32 m_buffer_nvmap_handle = 0;
55 SharedMemoryPoolLayout m_pool_layout = {};
56
57 std::unique_ptr<Kernel::KPageGroup> m_buffer_page_group;
58
59 std::mutex m_guard;
60 Core::System& m_system;
61 Nvnflinger& m_flinger;
62 std::shared_ptr<Nvidia::Module> m_nvdrv;
63};
64
65} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/graphic_buffer_producer.h b/src/core/hle/service/nvnflinger/graphic_buffer_producer.h
index 21d7b31f3..5d7cff7d3 100644
--- a/src/core/hle/service/nvnflinger/graphic_buffer_producer.h
+++ b/src/core/hle/service/nvnflinger/graphic_buffer_producer.h
@@ -19,6 +19,7 @@ class InputParcel;
19#pragma pack(push, 1) 19#pragma pack(push, 1)
20struct QueueBufferInput final { 20struct QueueBufferInput final {
21 explicit QueueBufferInput(InputParcel& parcel); 21 explicit QueueBufferInput(InputParcel& parcel);
22 explicit QueueBufferInput() = default;
22 23
23 void Deflate(s64* timestamp_, bool* is_auto_timestamp_, Common::Rectangle<s32>* crop_, 24 void Deflate(s64* timestamp_, bool* is_auto_timestamp_, Common::Rectangle<s32>* crop_,
24 NativeWindowScalingMode* scaling_mode_, NativeWindowTransform* transform_, 25 NativeWindowScalingMode* scaling_mode_, NativeWindowTransform* transform_,
@@ -34,7 +35,6 @@ struct QueueBufferInput final {
34 *fence_ = fence; 35 *fence_ = fence;
35 } 36 }
36 37
37private:
38 s64 timestamp{}; 38 s64 timestamp{};
39 s32 is_auto_timestamp{}; 39 s32 is_auto_timestamp{};
40 Common::Rectangle<s32> crop{}; 40 Common::Rectangle<s32> crop{};
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp
index 21f31f7a0..a07c621d9 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.cpp
+++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp
@@ -17,6 +17,7 @@
17#include "core/hle/service/nvdrv/nvdrv.h" 17#include "core/hle/service/nvdrv/nvdrv.h"
18#include "core/hle/service/nvnflinger/buffer_item_consumer.h" 18#include "core/hle/service/nvnflinger/buffer_item_consumer.h"
19#include "core/hle/service/nvnflinger/buffer_queue_core.h" 19#include "core/hle/service/nvnflinger/buffer_queue_core.h"
20#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
20#include "core/hle/service/nvnflinger/hos_binder_driver_server.h" 21#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
21#include "core/hle/service/nvnflinger/nvnflinger.h" 22#include "core/hle/service/nvnflinger/nvnflinger.h"
22#include "core/hle/service/nvnflinger/ui/graphic_buffer.h" 23#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
@@ -331,4 +332,14 @@ s64 Nvnflinger::GetNextTicks() const {
331 return static_cast<s64>(speed_scale * (1000000000.f / effective_fps)); 332 return static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
332} 333}
333 334
335FbShareBufferManager& Nvnflinger::GetSystemBufferManager() {
336 const auto lock_guard = Lock();
337
338 if (!system_buffer_manager) {
339 system_buffer_manager = std::make_unique<FbShareBufferManager>(system, *this, nvdrv);
340 }
341
342 return *system_buffer_manager;
343}
344
334} // namespace Service::Nvnflinger 345} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h
index f478c2bc6..14c783582 100644
--- a/src/core/hle/service/nvnflinger/nvnflinger.h
+++ b/src/core/hle/service/nvnflinger/nvnflinger.h
@@ -45,6 +45,9 @@ class BufferQueueProducer;
45 45
46namespace Service::Nvnflinger { 46namespace Service::Nvnflinger {
47 47
48class FbShareBufferManager;
49class HosBinderDriverServer;
50
48class Nvnflinger final { 51class Nvnflinger final {
49public: 52public:
50 explicit Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_); 53 explicit Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_);
@@ -90,12 +93,16 @@ public:
90 93
91 [[nodiscard]] s64 GetNextTicks() const; 94 [[nodiscard]] s64 GetNextTicks() const;
92 95
96 FbShareBufferManager& GetSystemBufferManager();
97
93private: 98private:
94 struct Layer { 99 struct Layer {
95 std::unique_ptr<android::BufferQueueCore> core; 100 std::unique_ptr<android::BufferQueueCore> core;
96 std::unique_ptr<android::BufferQueueProducer> producer; 101 std::unique_ptr<android::BufferQueueProducer> producer;
97 }; 102 };
98 103
104 friend class FbShareBufferManager;
105
99private: 106private:
100 [[nodiscard]] std::unique_lock<std::mutex> Lock() const { 107 [[nodiscard]] std::unique_lock<std::mutex> Lock() const {
101 return std::unique_lock{*guard}; 108 return std::unique_lock{*guard};
@@ -140,6 +147,8 @@ private:
140 std::shared_ptr<Core::Timing::EventType> multi_composition_event; 147 std::shared_ptr<Core::Timing::EventType> multi_composition_event;
141 std::shared_ptr<Core::Timing::EventType> single_composition_event; 148 std::shared_ptr<Core::Timing::EventType> single_composition_event;
142 149
150 std::unique_ptr<FbShareBufferManager> system_buffer_manager;
151
143 std::shared_ptr<std::mutex> guard; 152 std::shared_ptr<std::mutex> guard;
144 153
145 Core::System& system; 154 Core::System& system;
diff --git a/src/core/hle/service/nvnflinger/ui/fence.h b/src/core/hle/service/nvnflinger/ui/fence.h
index 536e8156d..177aed758 100644
--- a/src/core/hle/service/nvnflinger/ui/fence.h
+++ b/src/core/hle/service/nvnflinger/ui/fence.h
@@ -20,6 +20,9 @@ public:
20 static constexpr Fence NoFence() { 20 static constexpr Fence NoFence() {
21 Fence fence; 21 Fence fence;
22 fence.fences[0].id = -1; 22 fence.fences[0].id = -1;
23 fence.fences[1].id = -1;
24 fence.fences[2].id = -1;
25 fence.fences[3].id = -1;
23 return fence; 26 return fence;
24 } 27 }
25 28
diff --git a/src/core/hle/service/nvnflinger/ui/graphic_buffer.h b/src/core/hle/service/nvnflinger/ui/graphic_buffer.h
index 75d1705a8..3eac5cedd 100644
--- a/src/core/hle/service/nvnflinger/ui/graphic_buffer.h
+++ b/src/core/hle/service/nvnflinger/ui/graphic_buffer.h
@@ -12,8 +12,7 @@
12 12
13namespace Service::android { 13namespace Service::android {
14 14
15class GraphicBuffer final { 15struct GraphicBuffer final {
16public:
17 constexpr GraphicBuffer() = default; 16 constexpr GraphicBuffer() = default;
18 17
19 constexpr GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_) 18 constexpr GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_)
@@ -77,7 +76,6 @@ public:
77 return false; 76 return false;
78 } 77 }
79 78
80private:
81 u32 magic{}; 79 u32 magic{};
82 s32 width{}; 80 s32 width{};
83 s32 height{}; 81 s32 height{};
diff --git a/src/core/hle/service/pctl/pctl_module.cpp b/src/core/hle/service/pctl/pctl_module.cpp
index 5db1703d1..938330dd0 100644
--- a/src/core/hle/service/pctl/pctl_module.cpp
+++ b/src/core/hle/service/pctl/pctl_module.cpp
@@ -33,7 +33,7 @@ public:
33 {1001, &IParentalControlService::CheckFreeCommunicationPermission, "CheckFreeCommunicationPermission"}, 33 {1001, &IParentalControlService::CheckFreeCommunicationPermission, "CheckFreeCommunicationPermission"},
34 {1002, nullptr, "ConfirmLaunchApplicationPermission"}, 34 {1002, nullptr, "ConfirmLaunchApplicationPermission"},
35 {1003, nullptr, "ConfirmResumeApplicationPermission"}, 35 {1003, nullptr, "ConfirmResumeApplicationPermission"},
36 {1004, nullptr, "ConfirmSnsPostPermission"}, 36 {1004, &IParentalControlService::ConfirmSnsPostPermission, "ConfirmSnsPostPermission"},
37 {1005, nullptr, "ConfirmSystemSettingsPermission"}, 37 {1005, nullptr, "ConfirmSystemSettingsPermission"},
38 {1006, &IParentalControlService::IsRestrictionTemporaryUnlocked, "IsRestrictionTemporaryUnlocked"}, 38 {1006, &IParentalControlService::IsRestrictionTemporaryUnlocked, "IsRestrictionTemporaryUnlocked"},
39 {1007, nullptr, "RevertRestrictionTemporaryUnlocked"}, 39 {1007, nullptr, "RevertRestrictionTemporaryUnlocked"},
@@ -236,6 +236,13 @@ private:
236 states.free_communication = true; 236 states.free_communication = true;
237 } 237 }
238 238
239 void ConfirmSnsPostPermission(HLERequestContext& ctx) {
240 LOG_WARNING(Service_PCTL, "(STUBBED) called");
241
242 IPC::ResponseBuilder rb{ctx, 2};
243 rb.Push(Error::ResultNoFreeCommunication);
244 }
245
239 void IsRestrictionTemporaryUnlocked(HLERequestContext& ctx) { 246 void IsRestrictionTemporaryUnlocked(HLERequestContext& ctx) {
240 const bool is_temporary_unlocked = false; 247 const bool is_temporary_unlocked = false;
241 248
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 2eb978379..b1bfb9898 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -20,9 +20,12 @@
20#include "core/hle/kernel/k_readable_event.h" 20#include "core/hle/kernel/k_readable_event.h"
21#include "core/hle/kernel/k_thread.h" 21#include "core/hle/kernel/k_thread.h"
22#include "core/hle/service/ipc_helpers.h" 22#include "core/hle/service/ipc_helpers.h"
23#include "core/hle/service/nvdrv/devices/nvmap.h"
23#include "core/hle/service/nvdrv/nvdata.h" 24#include "core/hle/service/nvdrv/nvdata.h"
25#include "core/hle/service/nvdrv/nvdrv.h"
24#include "core/hle/service/nvnflinger/binder.h" 26#include "core/hle/service/nvnflinger/binder.h"
25#include "core/hle/service/nvnflinger/buffer_queue_producer.h" 27#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
28#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
26#include "core/hle/service/nvnflinger/hos_binder_driver_server.h" 29#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
27#include "core/hle/service/nvnflinger/nvnflinger.h" 30#include "core/hle/service/nvnflinger/nvnflinger.h"
28#include "core/hle/service/nvnflinger/parcel.h" 31#include "core/hle/service/nvnflinger/parcel.h"
@@ -131,8 +134,9 @@ private:
131 134
132class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> { 135class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> {
133public: 136public:
134 explicit ISystemDisplayService(Core::System& system_) 137 explicit ISystemDisplayService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger_)
135 : ServiceFramework{system_, "ISystemDisplayService"} { 138 : ServiceFramework{system_, "ISystemDisplayService"}, nvnflinger{nvnflinger_} {
139 // clang-format off
136 static const FunctionInfo functions[] = { 140 static const FunctionInfo functions[] = {
137 {1200, nullptr, "GetZOrderCountMin"}, 141 {1200, nullptr, "GetZOrderCountMin"},
138 {1202, nullptr, "GetZOrderCountMax"}, 142 {1202, nullptr, "GetZOrderCountMax"},
@@ -170,22 +174,126 @@ public:
170 {3217, nullptr, "SetDisplayCmuLuma"}, 174 {3217, nullptr, "SetDisplayCmuLuma"},
171 {3218, nullptr, "SetDisplayCrcMode"}, 175 {3218, nullptr, "SetDisplayCrcMode"},
172 {6013, nullptr, "GetLayerPresentationSubmissionTimestamps"}, 176 {6013, nullptr, "GetLayerPresentationSubmissionTimestamps"},
173 {8225, nullptr, "GetSharedBufferMemoryHandleId"}, 177 {8225, &ISystemDisplayService::GetSharedBufferMemoryHandleId, "GetSharedBufferMemoryHandleId"},
174 {8250, nullptr, "OpenSharedLayer"}, 178 {8250, &ISystemDisplayService::OpenSharedLayer, "OpenSharedLayer"},
175 {8251, nullptr, "CloseSharedLayer"}, 179 {8251, nullptr, "CloseSharedLayer"},
176 {8252, nullptr, "ConnectSharedLayer"}, 180 {8252, &ISystemDisplayService::ConnectSharedLayer, "ConnectSharedLayer"},
177 {8253, nullptr, "DisconnectSharedLayer"}, 181 {8253, nullptr, "DisconnectSharedLayer"},
178 {8254, nullptr, "AcquireSharedFrameBuffer"}, 182 {8254, &ISystemDisplayService::AcquireSharedFrameBuffer, "AcquireSharedFrameBuffer"},
179 {8255, nullptr, "PresentSharedFrameBuffer"}, 183 {8255, &ISystemDisplayService::PresentSharedFrameBuffer, "PresentSharedFrameBuffer"},
180 {8256, nullptr, "GetSharedFrameBufferAcquirableEvent"}, 184 {8256, &ISystemDisplayService::GetSharedFrameBufferAcquirableEvent, "GetSharedFrameBufferAcquirableEvent"},
181 {8257, nullptr, "FillSharedFrameBufferColor"}, 185 {8257, nullptr, "FillSharedFrameBufferColor"},
182 {8258, nullptr, "CancelSharedFrameBuffer"}, 186 {8258, nullptr, "CancelSharedFrameBuffer"},
183 {9000, nullptr, "GetDp2hdmiController"}, 187 {9000, nullptr, "GetDp2hdmiController"},
184 }; 188 };
189 // clang-format on
185 RegisterHandlers(functions); 190 RegisterHandlers(functions);
186 } 191 }
187 192
188private: 193private:
194 void GetSharedBufferMemoryHandleId(HLERequestContext& ctx) {
195 IPC::RequestParser rp{ctx};
196 const u64 buffer_id = rp.PopRaw<u64>();
197
198 LOG_INFO(Service_VI, "called. buffer_id={:#x}", buffer_id);
199
200 struct OutputParameters {
201 s32 nvmap_handle;
202 u64 size;
203 };
204
205 OutputParameters out{};
206 Nvnflinger::SharedMemoryPoolLayout layout{};
207 const auto result = nvnflinger.GetSystemBufferManager().GetSharedBufferMemoryHandleId(
208 &out.size, &out.nvmap_handle, &layout, buffer_id, 0);
209
210 ctx.WriteBuffer(&layout, sizeof(layout));
211
212 IPC::ResponseBuilder rb{ctx, 6};
213 rb.Push(result);
214 rb.PushRaw(out);
215 }
216
217 void OpenSharedLayer(HLERequestContext& ctx) {
218 IPC::RequestParser rp{ctx};
219 const u64 layer_id = rp.PopRaw<u64>();
220
221 LOG_INFO(Service_VI, "(STUBBED) called. layer_id={:#x}", layer_id);
222
223 IPC::ResponseBuilder rb{ctx, 2};
224 rb.Push(ResultSuccess);
225 }
226
227 void ConnectSharedLayer(HLERequestContext& ctx) {
228 IPC::RequestParser rp{ctx};
229 const u64 layer_id = rp.PopRaw<u64>();
230
231 LOG_INFO(Service_VI, "(STUBBED) called. layer_id={:#x}", layer_id);
232
233 IPC::ResponseBuilder rb{ctx, 2};
234 rb.Push(ResultSuccess);
235 }
236
237 void GetSharedFrameBufferAcquirableEvent(HLERequestContext& ctx) {
238 LOG_DEBUG(Service_VI, "called");
239
240 IPC::RequestParser rp{ctx};
241 const u64 layer_id = rp.PopRaw<u64>();
242
243 Kernel::KReadableEvent* event{};
244 const auto result = nvnflinger.GetSystemBufferManager().GetSharedFrameBufferAcquirableEvent(
245 &event, layer_id);
246
247 IPC::ResponseBuilder rb{ctx, 2, 1};
248 rb.Push(result);
249 rb.PushCopyObjects(event);
250 }
251
252 void AcquireSharedFrameBuffer(HLERequestContext& ctx) {
253 LOG_DEBUG(Service_VI, "called");
254
255 IPC::RequestParser rp{ctx};
256 const u64 layer_id = rp.PopRaw<u64>();
257
258 struct OutputParameters {
259 android::Fence fence;
260 std::array<s32, 4> slots;
261 s64 target_slot;
262 };
263 static_assert(sizeof(OutputParameters) == 0x40, "OutputParameters has wrong size");
264
265 OutputParameters out{};
266 const auto result = nvnflinger.GetSystemBufferManager().AcquireSharedFrameBuffer(
267 &out.fence, out.slots, &out.target_slot, layer_id);
268
269 IPC::ResponseBuilder rb{ctx, 18};
270 rb.Push(result);
271 rb.PushRaw(out);
272 }
273
274 void PresentSharedFrameBuffer(HLERequestContext& ctx) {
275 LOG_DEBUG(Service_VI, "called");
276
277 struct InputParameters {
278 android::Fence fence;
279 Common::Rectangle<s32> crop_region;
280 u32 window_transform;
281 s32 swap_interval;
282 u64 layer_id;
283 s64 surface_id;
284 };
285 static_assert(sizeof(InputParameters) == 0x50, "InputParameters has wrong size");
286
287 IPC::RequestParser rp{ctx};
288 auto input = rp.PopRaw<InputParameters>();
289
290 const auto result = nvnflinger.GetSystemBufferManager().PresentSharedFrameBuffer(
291 input.fence, input.crop_region, input.window_transform, input.swap_interval,
292 input.layer_id, input.surface_id);
293 IPC::ResponseBuilder rb{ctx, 2};
294 rb.Push(result);
295 }
296
189 void SetLayerZ(HLERequestContext& ctx) { 297 void SetLayerZ(HLERequestContext& ctx) {
190 IPC::RequestParser rp{ctx}; 298 IPC::RequestParser rp{ctx};
191 const u64 layer_id = rp.Pop<u64>(); 299 const u64 layer_id = rp.Pop<u64>();
@@ -228,6 +336,9 @@ private:
228 rb.PushRaw<float>(60.0f); // This wouldn't seem to be correct for 30 fps games. 336 rb.PushRaw<float>(60.0f); // This wouldn't seem to be correct for 30 fps games.
229 rb.Push<u32>(0); 337 rb.Push<u32>(0);
230 } 338 }
339
340private:
341 Nvnflinger::Nvnflinger& nvnflinger;
231}; 342};
232 343
233class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> { 344class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> {
@@ -453,7 +564,7 @@ private:
453 564
454 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 565 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
455 rb.Push(ResultSuccess); 566 rb.Push(ResultSuccess);
456 rb.PushIpcInterface<ISystemDisplayService>(system); 567 rb.PushIpcInterface<ISystemDisplayService>(system, nv_flinger);
457 } 568 }
458 569
459 void GetManagerDisplayService(HLERequestContext& ctx) { 570 void GetManagerDisplayService(HLERequestContext& ctx) {
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 5a42dea48..5c36b71e5 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -118,7 +118,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
118 return {ResultStatus::ErrorMissingNPDM, {}}; 118 return {ResultStatus::ErrorMissingNPDM, {}};
119 } 119 }
120 120
121 const ResultStatus result2 = metadata.Load(npdm); 121 const ResultStatus result2 = metadata.Reload(npdm);
122 if (result2 != ResultStatus::Success) { 122 if (result2 != ResultStatus::Success) {
123 return {result2, {}}; 123 return {result2, {}};
124 } 124 }
diff --git a/src/input_common/drivers/virtual_gamepad.h b/src/input_common/drivers/virtual_gamepad.h
index dfbc45a28..3a40e3fd3 100644
--- a/src/input_common/drivers/virtual_gamepad.h
+++ b/src/input_common/drivers/virtual_gamepad.h
@@ -67,7 +67,7 @@ public:
67 * @param player_index the player number that will take this action 67 * @param player_index the player number that will take this action
68 * @param delta_timestamp time passed since last reading 68 * @param delta_timestamp time passed since last reading
69 * @param gyro_x,gyro_y,gyro_z the gyro sensor readings 69 * @param gyro_x,gyro_y,gyro_z the gyro sensor readings
70 * @param accel_x,accel_y,accel_z the acelerometer reading 70 * @param accel_x,accel_y,accel_z the accelerometer reading
71 */ 71 */
72 void SetMotionState(std::size_t player_index, u64 delta_timestamp, float gyro_x, float gyro_y, 72 void SetMotionState(std::size_t player_index, u64 delta_timestamp, float gyro_x, float gyro_y,
73 float gyro_z, float accel_x, float accel_y, float accel_z); 73 float gyro_z, float accel_x, float accel_y, float accel_z);
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.h b/src/input_common/helpers/joycon_protocol/generic_functions.h
index 90fcd17f6..b94567f82 100644
--- a/src/input_common/helpers/joycon_protocol/generic_functions.h
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.h
@@ -55,7 +55,7 @@ public:
55 55
56 /** 56 /**
57 * Configures the motion sensor with the specified parameters 57 * Configures the motion sensor with the specified parameters
58 * @param gsen gyroscope sensor sensitvity in degrees per second 58 * @param gsen gyroscope sensor sensitivity in degrees per second
59 * @param gfrec gyroscope sensor frequency in hertz 59 * @param gfrec gyroscope sensor frequency in hertz
60 * @param asen accelerometer sensitivity in G force 60 * @param asen accelerometer sensitivity in G force
61 * @param afrec accelerometer frequency in hertz 61 * @param afrec accelerometer frequency in hertz
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp
index d508ee567..4e8ba4ae6 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_composite.cpp
@@ -55,7 +55,7 @@ void CompositeInsert(EmitContext& ctx, IR::Inst& inst, Register composite, Objec
55 "MOV.{} {}.{},{};", 55 "MOV.{} {}.{},{};",
56 type, ret, composite, type, ret, swizzle, object); 56 type, ret, composite, type, ret, swizzle, object);
57 } else { 57 } else {
58 // The return value is alised so we can just insert the object, it doesn't matter if it's 58 // The return value is aliased so we can just insert the object, it doesn't matter if it's
59 // aliased 59 // aliased
60 ctx.Add("MOV.{} {}.{},{};", type, ret, swizzle, object); 60 ctx.Add("MOV.{} {}.{},{};", type, ret, swizzle, object);
61 } 61 }
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 9e90c587c..9b2698fad 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -544,7 +544,7 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
544 it++; 544 it++;
545 } 545 }
546 546
547 boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads; 547 boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> downloads;
548 u64 total_size_bytes = 0; 548 u64 total_size_bytes = 0;
549 u64 largest_copy = 0; 549 u64 largest_copy = 0;
550 for (const IntervalSet& intervals : committed_ranges) { 550 for (const IntervalSet& intervals : committed_ranges) {
@@ -914,6 +914,11 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
914 914
915 const u32 offset = buffer.Offset(binding.cpu_addr); 915 const u32 offset = buffer.Offset(binding.cpu_addr);
916 const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0; 916 const bool is_written = ((channel_state->written_storage_buffers[stage] >> index) & 1) != 0;
917
918 if (is_written) {
919 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
920 }
921
917 if constexpr (NEEDS_BIND_STORAGE_INDEX) { 922 if constexpr (NEEDS_BIND_STORAGE_INDEX) {
918 runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written); 923 runtime.BindStorageBuffer(stage, binding_index, buffer, offset, size, is_written);
919 ++binding_index; 924 ++binding_index;
@@ -931,6 +936,11 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
931 const u32 size = binding.size; 936 const u32 size = binding.size;
932 SynchronizeBuffer(buffer, binding.cpu_addr, size); 937 SynchronizeBuffer(buffer, binding.cpu_addr, size);
933 938
939 const bool is_written = ((channel_state->written_texture_buffers[stage] >> index) & 1) != 0;
940 if (is_written) {
941 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
942 }
943
934 const u32 offset = buffer.Offset(binding.cpu_addr); 944 const u32 offset = buffer.Offset(binding.cpu_addr);
935 const PixelFormat format = binding.format; 945 const PixelFormat format = binding.format;
936 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { 946 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
@@ -962,6 +972,8 @@ void BufferCache<P>::BindHostTransformFeedbackBuffers() {
962 const u32 size = binding.size; 972 const u32 size = binding.size;
963 SynchronizeBuffer(buffer, binding.cpu_addr, size); 973 SynchronizeBuffer(buffer, binding.cpu_addr, size);
964 974
975 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
976
965 const u32 offset = buffer.Offset(binding.cpu_addr); 977 const u32 offset = buffer.Offset(binding.cpu_addr);
966 host_bindings.buffers.push_back(&buffer); 978 host_bindings.buffers.push_back(&buffer);
967 host_bindings.offsets.push_back(offset); 979 host_bindings.offsets.push_back(offset);
@@ -1011,6 +1023,11 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
1011 const u32 offset = buffer.Offset(binding.cpu_addr); 1023 const u32 offset = buffer.Offset(binding.cpu_addr);
1012 const bool is_written = 1024 const bool is_written =
1013 ((channel_state->written_compute_storage_buffers >> index) & 1) != 0; 1025 ((channel_state->written_compute_storage_buffers >> index) & 1) != 0;
1026
1027 if (is_written) {
1028 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
1029 }
1030
1014 if constexpr (NEEDS_BIND_STORAGE_INDEX) { 1031 if constexpr (NEEDS_BIND_STORAGE_INDEX) {
1015 runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written); 1032 runtime.BindComputeStorageBuffer(binding_index, buffer, offset, size, is_written);
1016 ++binding_index; 1033 ++binding_index;
@@ -1028,6 +1045,12 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
1028 const u32 size = binding.size; 1045 const u32 size = binding.size;
1029 SynchronizeBuffer(buffer, binding.cpu_addr, size); 1046 SynchronizeBuffer(buffer, binding.cpu_addr, size);
1030 1047
1048 const bool is_written =
1049 ((channel_state->written_compute_texture_buffers >> index) & 1) != 0;
1050 if (is_written) {
1051 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, size);
1052 }
1053
1031 const u32 offset = buffer.Offset(binding.cpu_addr); 1054 const u32 offset = buffer.Offset(binding.cpu_addr);
1032 const PixelFormat format = binding.format; 1055 const PixelFormat format = binding.format;
1033 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) { 1056 if constexpr (SEPARATE_IMAGE_BUFFERS_BINDINGS) {
@@ -1201,16 +1224,11 @@ void BufferCache<P>::UpdateUniformBuffers(size_t stage) {
1201 1224
1202template <class P> 1225template <class P>
1203void BufferCache<P>::UpdateStorageBuffers(size_t stage) { 1226void BufferCache<P>::UpdateStorageBuffers(size_t stage) {
1204 const u32 written_mask = channel_state->written_storage_buffers[stage];
1205 ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) { 1227 ForEachEnabledBit(channel_state->enabled_storage_buffers[stage], [&](u32 index) {
1206 // Resolve buffer 1228 // Resolve buffer
1207 Binding& binding = channel_state->storage_buffers[stage][index]; 1229 Binding& binding = channel_state->storage_buffers[stage][index];
1208 const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1230 const BufferId buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1209 binding.buffer_id = buffer_id; 1231 binding.buffer_id = buffer_id;
1210 // Mark buffer as written if needed
1211 if (((written_mask >> index) & 1) != 0) {
1212 MarkWrittenBuffer(buffer_id, binding.cpu_addr, binding.size);
1213 }
1214 }); 1232 });
1215} 1233}
1216 1234
@@ -1219,10 +1237,6 @@ void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
1219 ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) { 1237 ForEachEnabledBit(channel_state->enabled_texture_buffers[stage], [&](u32 index) {
1220 Binding& binding = channel_state->texture_buffers[stage][index]; 1238 Binding& binding = channel_state->texture_buffers[stage][index];
1221 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1239 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1222 // Mark buffer as written if needed
1223 if (((channel_state->written_texture_buffers[stage] >> index) & 1) != 0) {
1224 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
1225 }
1226 }); 1240 });
1227} 1241}
1228 1242
@@ -1252,7 +1266,6 @@ void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
1252 .size = size, 1266 .size = size,
1253 .buffer_id = buffer_id, 1267 .buffer_id = buffer_id,
1254 }; 1268 };
1255 MarkWrittenBuffer(buffer_id, *cpu_addr, size);
1256} 1269}
1257 1270
1258template <class P> 1271template <class P>
@@ -1279,10 +1292,6 @@ void BufferCache<P>::UpdateComputeStorageBuffers() {
1279 // Resolve buffer 1292 // Resolve buffer
1280 Binding& binding = channel_state->compute_storage_buffers[index]; 1293 Binding& binding = channel_state->compute_storage_buffers[index];
1281 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1294 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1282 // Mark as written if needed
1283 if (((channel_state->written_compute_storage_buffers >> index) & 1) != 0) {
1284 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
1285 }
1286 }); 1295 });
1287} 1296}
1288 1297
@@ -1291,18 +1300,11 @@ void BufferCache<P>::UpdateComputeTextureBuffers() {
1291 ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) { 1300 ForEachEnabledBit(channel_state->enabled_compute_texture_buffers, [&](u32 index) {
1292 Binding& binding = channel_state->compute_texture_buffers[index]; 1301 Binding& binding = channel_state->compute_texture_buffers[index];
1293 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size); 1302 binding.buffer_id = FindBuffer(binding.cpu_addr, binding.size);
1294 // Mark as written if needed
1295 if (((channel_state->written_compute_texture_buffers >> index) & 1) != 0) {
1296 MarkWrittenBuffer(binding.buffer_id, binding.cpu_addr, binding.size);
1297 }
1298 }); 1303 });
1299} 1304}
1300 1305
1301template <class P> 1306template <class P>
1302void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) { 1307void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) {
1303 if (memory_tracker.IsRegionCpuModified(cpu_addr, size)) {
1304 SynchronizeBuffer(slot_buffers[buffer_id], cpu_addr, size);
1305 }
1306 memory_tracker.MarkRegionAsGpuModified(cpu_addr, size); 1308 memory_tracker.MarkRegionAsGpuModified(cpu_addr, size);
1307 1309
1308 const IntervalType base_interval{cpu_addr, cpu_addr + size}; 1310 const IntervalType base_interval{cpu_addr, cpu_addr + size};
diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h
index c9fab2d90..e46a8fa5c 100644
--- a/src/video_core/dma_pusher.h
+++ b/src/video_core/dma_pusher.h
@@ -161,7 +161,7 @@ private:
161 u32 method_count; ///< Current method count 161 u32 method_count; ///< Current method count
162 u32 length_pending; ///< Large NI command length pending 162 u32 length_pending; ///< Large NI command length pending
163 GPUVAddr dma_get; ///< Currently read segment 163 GPUVAddr dma_get; ///< Currently read segment
164 u64 dma_word_offset; ///< Current word ofset from address 164 u64 dma_word_offset; ///< Current word offset from address
165 bool non_incrementing; ///< Current command's NI flag 165 bool non_incrementing; ///< Current command's NI flag
166 bool is_last_call; 166 bool is_last_call;
167 }; 167 };
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
index 6b912027f..8bb429578 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -19,6 +19,7 @@ set(SHADER_FILES
19 block_linear_unswizzle_2d.comp 19 block_linear_unswizzle_2d.comp
20 block_linear_unswizzle_3d.comp 20 block_linear_unswizzle_3d.comp
21 convert_abgr8_to_d24s8.frag 21 convert_abgr8_to_d24s8.frag
22 convert_d32f_to_abgr8.frag
22 convert_d24s8_to_abgr8.frag 23 convert_d24s8_to_abgr8.frag
23 convert_depth_to_float.frag 24 convert_depth_to_float.frag
24 convert_float_to_depth.frag 25 convert_float_to_depth.frag
diff --git a/src/video_core/host_shaders/convert_d32f_to_abgr8.frag b/src/video_core/host_shaders/convert_d32f_to_abgr8.frag
new file mode 100644
index 000000000..04cfef8b5
--- /dev/null
+++ b/src/video_core/host_shaders/convert_d32f_to_abgr8.frag
@@ -0,0 +1,14 @@
1// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#version 450
5
6layout(binding = 0) uniform sampler2D depth_tex;
7
8layout(location = 0) out vec4 color;
9
10void main() {
11 ivec2 coord = ivec2(gl_FragCoord.xy);
12 float depth = textureLod(depth_tex, coord, 0).r;
13 color = vec4(depth, depth, depth, 1.0);
14}
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 3e12a8813..78ea5208b 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -89,9 +89,6 @@ public:
89 void RequestScreenshot(void* data, std::function<void(bool)> callback, 89 void RequestScreenshot(void* data, std::function<void(bool)> callback,
90 const Layout::FramebufferLayout& layout); 90 const Layout::FramebufferLayout& layout);
91 91
92 /// This is called to notify the rendering backend of a surface change
93 virtual void NotifySurfaceChanged() {}
94
95protected: 92protected:
96 Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle. 93 Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
97 std::unique_ptr<Core::Frontend::GraphicsContext> context; 94 std::unique_ptr<Core::Frontend::GraphicsContext> context;
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 9cafd2983..512eef575 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -1048,6 +1048,10 @@ void Image::Scale(bool up_scale) {
1048} 1048}
1049 1049
1050bool Image::ScaleUp(bool ignore) { 1050bool Image::ScaleUp(bool ignore) {
1051 const auto& resolution = runtime->resolution;
1052 if (!resolution.active) {
1053 return false;
1054 }
1051 if (True(flags & ImageFlagBits::Rescaled)) { 1055 if (True(flags & ImageFlagBits::Rescaled)) {
1052 return false; 1056 return false;
1053 } 1057 }
@@ -1060,9 +1064,6 @@ bool Image::ScaleUp(bool ignore) {
1060 return false; 1064 return false;
1061 } 1065 }
1062 flags |= ImageFlagBits::Rescaled; 1066 flags |= ImageFlagBits::Rescaled;
1063 if (!runtime->resolution.active) {
1064 return false;
1065 }
1066 has_scaled = true; 1067 has_scaled = true;
1067 if (ignore) { 1068 if (ignore) {
1068 current_texture = upscaled_backup.handle; 1069 current_texture = upscaled_backup.handle;
@@ -1073,13 +1074,14 @@ bool Image::ScaleUp(bool ignore) {
1073} 1074}
1074 1075
1075bool Image::ScaleDown(bool ignore) { 1076bool Image::ScaleDown(bool ignore) {
1076 if (False(flags & ImageFlagBits::Rescaled)) { 1077 const auto& resolution = runtime->resolution;
1078 if (!resolution.active) {
1077 return false; 1079 return false;
1078 } 1080 }
1079 flags &= ~ImageFlagBits::Rescaled; 1081 if (False(flags & ImageFlagBits::Rescaled)) {
1080 if (!runtime->resolution.active) {
1081 return false; 1082 return false;
1082 } 1083 }
1084 flags &= ~ImageFlagBits::Rescaled;
1083 if (ignore) { 1085 if (ignore) {
1084 current_texture = texture.handle; 1086 current_texture = texture.handle;
1085 return true; 1087 return true;
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index 3676eaaa9..e71b87e99 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -118,6 +118,8 @@ public:
118 118
119 void InsertUploadMemoryBarrier(); 119 void InsertUploadMemoryBarrier();
120 120
121 void TransitionImageLayout(Image& image) {}
122
121 FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const; 123 FormatProperties FormatInfo(VideoCommon::ImageType type, GLenum internal_format) const;
122 124
123 bool HasNativeBgr() const noexcept { 125 bool HasNativeBgr() const noexcept {
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
index c7dc7e0a1..5ea9e2378 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -116,6 +116,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB
116 {GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT 116 {GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT
117 {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT 117 {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT
118 {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM 118 {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM
119 {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT_24_8}, // X8_D24_UNORM
119 {GL_STENCIL_INDEX8, GL_STENCIL, GL_UNSIGNED_BYTE}, // S8_UINT 120 {GL_STENCIL_INDEX8, GL_STENCIL, GL_UNSIGNED_BYTE}, // S8_UINT
120 {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT 121 {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT
121 {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM 122 {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM
diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp
index 1032c9d12..f01d2394e 100644
--- a/src/video_core/renderer_vulkan/blit_image.cpp
+++ b/src/video_core/renderer_vulkan/blit_image.cpp
@@ -9,6 +9,7 @@
9#include "video_core/host_shaders/blit_color_float_frag_spv.h" 9#include "video_core/host_shaders/blit_color_float_frag_spv.h"
10#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h" 10#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h"
11#include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h" 11#include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h"
12#include "video_core/host_shaders/convert_d32f_to_abgr8_frag_spv.h"
12#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h" 13#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
13#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h" 14#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h"
14#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h" 15#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h"
@@ -433,6 +434,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
433 convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)), 434 convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)),
434 convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)), 435 convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)),
435 convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)), 436 convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)),
437 convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)),
436 convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)), 438 convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
437 convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)), 439 convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
438 linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)), 440 linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
@@ -557,6 +559,13 @@ void BlitImageHelper::ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer,
557 Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view); 559 Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view);
558} 560}
559 561
562void BlitImageHelper::ConvertD32FToABGR8(const Framebuffer* dst_framebuffer,
563 ImageView& src_image_view) {
564 ConvertPipelineColorTargetEx(convert_d32f_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
565 convert_d32f_to_abgr8_frag);
566 ConvertDepthStencil(*convert_d32f_to_abgr8_pipeline, dst_framebuffer, src_image_view);
567}
568
560void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, 569void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer,
561 ImageView& src_image_view) { 570 ImageView& src_image_view) {
562 ConvertPipelineColorTargetEx(convert_d24s8_to_abgr8_pipeline, dst_framebuffer->RenderPass(), 571 ConvertPipelineColorTargetEx(convert_d24s8_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
@@ -609,6 +618,8 @@ void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool
609 const VkPipelineLayout layout = *clear_color_pipeline_layout; 618 const VkPipelineLayout layout = *clear_color_pipeline_layout;
610 scheduler.RequestRenderpass(dst_framebuffer); 619 scheduler.RequestRenderpass(dst_framebuffer);
611 scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) { 620 scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) {
621 constexpr std::array blend_constants{0.0f, 0.0f, 0.0f, 0.0f};
622 cmdbuf.SetBlendConstants(blend_constants.data());
612 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); 623 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
613 BindBlitState(cmdbuf, dst_region); 624 BindBlitState(cmdbuf, dst_region);
614 cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth); 625 cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth);
@@ -865,7 +876,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
865 .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, 876 .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
866 .pNext = nullptr, 877 .pNext = nullptr,
867 .flags = 0, 878 .flags = 0,
868 .depthTestEnable = VK_FALSE, 879 .depthTestEnable = key.depth_clear,
869 .depthWriteEnable = key.depth_clear, 880 .depthWriteEnable = key.depth_clear,
870 .depthCompareOp = VK_COMPARE_OP_ALWAYS, 881 .depthCompareOp = VK_COMPARE_OP_ALWAYS,
871 .depthBoundsTestEnable = VK_FALSE, 882 .depthBoundsTestEnable = VK_FALSE,
diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h
index dcfe217aa..a032c71fb 100644
--- a/src/video_core/renderer_vulkan/blit_image.h
+++ b/src/video_core/renderer_vulkan/blit_image.h
@@ -67,6 +67,8 @@ public:
67 67
68 void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); 68 void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
69 69
70 void ConvertD32FToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
71
70 void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view); 72 void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
71 73
72 void ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view); 74 void ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
@@ -128,6 +130,7 @@ private:
128 vk::ShaderModule convert_depth_to_float_frag; 130 vk::ShaderModule convert_depth_to_float_frag;
129 vk::ShaderModule convert_float_to_depth_frag; 131 vk::ShaderModule convert_float_to_depth_frag;
130 vk::ShaderModule convert_abgr8_to_d24s8_frag; 132 vk::ShaderModule convert_abgr8_to_d24s8_frag;
133 vk::ShaderModule convert_d32f_to_abgr8_frag;
131 vk::ShaderModule convert_d24s8_to_abgr8_frag; 134 vk::ShaderModule convert_d24s8_to_abgr8_frag;
132 vk::ShaderModule convert_s8d24_to_abgr8_frag; 135 vk::ShaderModule convert_s8d24_to_abgr8_frag;
133 vk::Sampler linear_sampler; 136 vk::Sampler linear_sampler;
@@ -146,6 +149,7 @@ private:
146 vk::Pipeline convert_d16_to_r16_pipeline; 149 vk::Pipeline convert_d16_to_r16_pipeline;
147 vk::Pipeline convert_r16_to_d16_pipeline; 150 vk::Pipeline convert_r16_to_d16_pipeline;
148 vk::Pipeline convert_abgr8_to_d24s8_pipeline; 151 vk::Pipeline convert_abgr8_to_d24s8_pipeline;
152 vk::Pipeline convert_d32f_to_abgr8_pipeline;
149 vk::Pipeline convert_d24s8_to_abgr8_pipeline; 153 vk::Pipeline convert_d24s8_to_abgr8_pipeline;
150 vk::Pipeline convert_s8d24_to_abgr8_pipeline; 154 vk::Pipeline convert_s8d24_to_abgr8_pipeline;
151}; 155};
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index 208e88533..a08f2f67f 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -214,8 +214,9 @@ struct FormatTuple {
214 {VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT 214 {VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT
215 215
216 // Depth formats 216 // Depth formats
217 {VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT 217 {VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT
218 {VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM 218 {VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM
219 {VK_FORMAT_X8_D24_UNORM_PACK32, Attachable}, // X8_D24_UNORM
219 220
220 // Stencil formats 221 // Stencil formats
221 {VK_FORMAT_S8_UINT, Attachable}, // S8_UINT 222 {VK_FORMAT_S8_UINT, Attachable}, // S8_UINT
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index 590bc1c64..14e257cf7 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -56,10 +56,6 @@ public:
56 return device.GetDriverName(); 56 return device.GetDriverName();
57 } 57 }
58 58
59 void NotifySurfaceChanged() override {
60 present_manager.NotifySurfaceChanged();
61 }
62
63private: 59private:
64 void Report() const; 60 void Report() const;
65 61
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 31928bb94..52fc142d1 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -96,6 +96,7 @@ std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) {
96VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) { 96VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
97 switch (framebuffer.pixel_format) { 97 switch (framebuffer.pixel_format) {
98 case Service::android::PixelFormat::Rgba8888: 98 case Service::android::PixelFormat::Rgba8888:
99 case Service::android::PixelFormat::Rgbx8888:
99 return VK_FORMAT_A8B8G8R8_UNORM_PACK32; 100 return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
100 case Service::android::PixelFormat::Rgb565: 101 case Service::android::PixelFormat::Rgb565:
101 return VK_FORMAT_R5G6B5_UNORM_PACK16; 102 return VK_FORMAT_R5G6B5_UNORM_PACK16;
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp
index d681bd22a..2ef36583b 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp
@@ -103,8 +103,7 @@ PresentManager::PresentManager(const vk::Instance& instance_,
103 surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(), 103 surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(),
104 swapchain.GetImageViewFormat())}, 104 swapchain.GetImageViewFormat())},
105 use_present_thread{Settings::values.async_presentation.GetValue()}, 105 use_present_thread{Settings::values.async_presentation.GetValue()},
106 image_count{swapchain.GetImageCount()}, last_render_surface{ 106 image_count{swapchain.GetImageCount()} {
107 render_window_.GetWindowInfo().render_surface} {
108 107
109 auto& dld = device.GetLogical(); 108 auto& dld = device.GetLogical();
110 cmdpool = dld.CreateCommandPool({ 109 cmdpool = dld.CreateCommandPool({
@@ -289,44 +288,36 @@ void PresentManager::PresentThread(std::stop_token token) {
289 } 288 }
290} 289}
291 290
292void PresentManager::NotifySurfaceChanged() { 291void PresentManager::RecreateSwapchain(Frame* frame) {
293#ifdef ANDROID 292 swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb);
294 std::scoped_lock lock{recreate_surface_mutex}; 293 image_count = swapchain.GetImageCount();
295 recreate_surface_cv.notify_one();
296#endif
297} 294}
298 295
299void PresentManager::CopyToSwapchain(Frame* frame) { 296void PresentManager::CopyToSwapchain(Frame* frame) {
300 MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain); 297 bool requires_recreation = false;
301 298
302 const auto recreate_swapchain = [&] { 299 while (true) {
303 swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb); 300 try {
304 image_count = swapchain.GetImageCount(); 301 // Recreate surface and swapchain if needed.
305 }; 302 if (requires_recreation) {
306 303 surface = CreateSurface(instance, render_window.GetWindowInfo());
307#ifdef ANDROID 304 RecreateSwapchain(frame);
308 std::unique_lock lock{recreate_surface_mutex}; 305 }
309 306
310 const auto needs_recreation = [&] { 307 // Draw to swapchain.
311 if (last_render_surface != render_window.GetWindowInfo().render_surface) { 308 return CopyToSwapchainImpl(frame);
312 return true; 309 } catch (const vk::Exception& except) {
313 } 310 if (except.GetResult() != VK_ERROR_SURFACE_LOST_KHR) {
314 if (swapchain.NeedsRecreation(frame->is_srgb)) { 311 throw;
315 return true; 312 }
313
314 requires_recreation = true;
316 } 315 }
317 return false;
318 };
319
320 recreate_surface_cv.wait_for(lock, std::chrono::milliseconds(400),
321 [&]() { return !needs_recreation(); });
322
323 // If the frontend recreated the surface, recreate the renderer surface and swapchain.
324 if (last_render_surface != render_window.GetWindowInfo().render_surface) {
325 last_render_surface = render_window.GetWindowInfo().render_surface;
326 surface = CreateSurface(instance, render_window.GetWindowInfo());
327 recreate_swapchain();
328 } 316 }
329#endif 317}
318
319void PresentManager::CopyToSwapchainImpl(Frame* frame) {
320 MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
330 321
331 // If the size or colorspace of the incoming frames has changed, recreate the swapchain 322 // If the size or colorspace of the incoming frames has changed, recreate the swapchain
332 // to account for that. 323 // to account for that.
@@ -334,11 +325,11 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
334 const bool size_changed = 325 const bool size_changed =
335 swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height; 326 swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height;
336 if (srgb_changed || size_changed) { 327 if (srgb_changed || size_changed) {
337 recreate_swapchain(); 328 RecreateSwapchain(frame);
338 } 329 }
339 330
340 while (swapchain.AcquireNextImage()) { 331 while (swapchain.AcquireNextImage()) {
341 recreate_swapchain(); 332 RecreateSwapchain(frame);
342 } 333 }
343 334
344 const vk::CommandBuffer cmdbuf{frame->cmdbuf}; 335 const vk::CommandBuffer cmdbuf{frame->cmdbuf};
@@ -488,4 +479,4 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
488 swapchain.Present(render_semaphore); 479 swapchain.Present(render_semaphore);
489} 480}
490 481
491} // namespace Vulkan \ No newline at end of file 482} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h
index 83e859416..a3d825fe6 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.h
+++ b/src/video_core/renderer_vulkan/vk_present_manager.h
@@ -54,14 +54,15 @@ public:
54 /// Waits for the present thread to finish presenting all queued frames. 54 /// Waits for the present thread to finish presenting all queued frames.
55 void WaitPresent(); 55 void WaitPresent();
56 56
57 /// This is called to notify the rendering backend of a surface change
58 void NotifySurfaceChanged();
59
60private: 57private:
61 void PresentThread(std::stop_token token); 58 void PresentThread(std::stop_token token);
62 59
63 void CopyToSwapchain(Frame* frame); 60 void CopyToSwapchain(Frame* frame);
64 61
62 void CopyToSwapchainImpl(Frame* frame);
63
64 void RecreateSwapchain(Frame* frame);
65
65private: 66private:
66 const vk::Instance& instance; 67 const vk::Instance& instance;
67 Core::Frontend::EmuWindow& render_window; 68 Core::Frontend::EmuWindow& render_window;
@@ -76,16 +77,13 @@ private:
76 std::queue<Frame*> free_queue; 77 std::queue<Frame*> free_queue;
77 std::condition_variable_any frame_cv; 78 std::condition_variable_any frame_cv;
78 std::condition_variable free_cv; 79 std::condition_variable free_cv;
79 std::condition_variable recreate_surface_cv;
80 std::mutex swapchain_mutex; 80 std::mutex swapchain_mutex;
81 std::mutex recreate_surface_mutex;
82 std::mutex queue_mutex; 81 std::mutex queue_mutex;
83 std::mutex free_mutex; 82 std::mutex free_mutex;
84 std::jthread present_thread; 83 std::jthread present_thread;
85 bool blit_supported; 84 bool blit_supported;
86 bool use_present_thread; 85 bool use_present_thread;
87 std::size_t image_count{}; 86 std::size_t image_count{};
88 void* last_render_surface{};
89}; 87};
90 88
91} // namespace Vulkan 89} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 1628d76d6..83f2b6045 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -422,7 +422,8 @@ void RasterizerVulkan::Clear(u32 layer_count) {
422 return; 422 return;
423 } 423 }
424 424
425 if (use_stencil && regs.stencil_front_mask != 0xFF && regs.stencil_front_mask != 0) { 425 if (use_stencil && framebuffer->HasAspectStencilBit() && regs.stencil_front_mask != 0xFF &&
426 regs.stencil_front_mask != 0) {
426 Region2D dst_region = { 427 Region2D dst_region = {
427 Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y}, 428 Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y},
428 Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width), 429 Offset2D{.x = clear_rect.rect.offset.x + static_cast<s32>(clear_rect.rect.extent.width),
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
index ce92f66ab..b278614e6 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
@@ -24,25 +24,38 @@ using namespace Common::Literals;
24 24
25// Maximum potential alignment of a Vulkan buffer 25// Maximum potential alignment of a Vulkan buffer
26constexpr VkDeviceSize MAX_ALIGNMENT = 256; 26constexpr VkDeviceSize MAX_ALIGNMENT = 256;
27// Maximum size to put elements in the stream buffer
28constexpr VkDeviceSize MAX_STREAM_BUFFER_REQUEST_SIZE = 8_MiB;
29// Stream buffer size in bytes 27// Stream buffer size in bytes
30constexpr VkDeviceSize STREAM_BUFFER_SIZE = 128_MiB; 28constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB;
31constexpr VkDeviceSize REGION_SIZE = STREAM_BUFFER_SIZE / StagingBufferPool::NUM_SYNCS;
32 29
33size_t Region(size_t iterator) noexcept { 30size_t GetStreamBufferSize(const Device& device) {
34 return iterator / REGION_SIZE; 31 VkDeviceSize size{0};
32 if (device.HasDebuggingToolAttached()) {
33 ForEachDeviceLocalHostVisibleHeap(device, [&size](size_t index, VkMemoryHeap& heap) {
34 size = std::max(size, heap.size);
35 });
36 // If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be
37 // loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue
38 // as the heap will be much larger.
39 if (size <= 256_MiB) {
40 size = size * 40 / 100;
41 }
42 } else {
43 size = MAX_STREAM_BUFFER_SIZE;
44 }
45 return std::min(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE);
35} 46}
36} // Anonymous namespace 47} // Anonymous namespace
37 48
38StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_, 49StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_,
39 Scheduler& scheduler_) 50 Scheduler& scheduler_)
40 : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_} { 51 : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
52 stream_buffer_size{GetStreamBufferSize(device)}, region_size{stream_buffer_size /
53 StagingBufferPool::NUM_SYNCS} {
41 VkBufferCreateInfo stream_ci = { 54 VkBufferCreateInfo stream_ci = {
42 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 55 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
43 .pNext = nullptr, 56 .pNext = nullptr,
44 .flags = 0, 57 .flags = 0,
45 .size = STREAM_BUFFER_SIZE, 58 .size = stream_buffer_size,
46 .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | 59 .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
47 VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, 60 VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
48 .sharingMode = VK_SHARING_MODE_EXCLUSIVE, 61 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
@@ -63,7 +76,7 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem
63StagingBufferPool::~StagingBufferPool() = default; 76StagingBufferPool::~StagingBufferPool() = default;
64 77
65StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) { 78StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) {
66 if (!deferred && usage == MemoryUsage::Upload && size <= MAX_STREAM_BUFFER_REQUEST_SIZE) { 79 if (!deferred && usage == MemoryUsage::Upload && size <= region_size) {
67 return GetStreamBuffer(size); 80 return GetStreamBuffer(size);
68 } 81 }
69 return GetStagingBuffer(size, usage, deferred); 82 return GetStagingBuffer(size, usage, deferred);
@@ -101,7 +114,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) {
101 used_iterator = iterator; 114 used_iterator = iterator;
102 free_iterator = std::max(free_iterator, iterator + size); 115 free_iterator = std::max(free_iterator, iterator + size);
103 116
104 if (iterator + size >= STREAM_BUFFER_SIZE) { 117 if (iterator + size >= stream_buffer_size) {
105 std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS, 118 std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS,
106 current_tick); 119 current_tick);
107 used_iterator = 0; 120 used_iterator = 0;
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
index 5f69f08b1..d3deb9072 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
@@ -90,6 +90,9 @@ private:
90 void ReleaseCache(MemoryUsage usage); 90 void ReleaseCache(MemoryUsage usage);
91 91
92 void ReleaseLevel(StagingBuffersCache& cache, size_t log2); 92 void ReleaseLevel(StagingBuffersCache& cache, size_t log2);
93 size_t Region(size_t iter) const noexcept {
94 return iter / region_size;
95 }
93 96
94 const Device& device; 97 const Device& device;
95 MemoryAllocator& memory_allocator; 98 MemoryAllocator& memory_allocator;
@@ -97,6 +100,8 @@ private:
97 100
98 vk::Buffer stream_buffer; 101 vk::Buffer stream_buffer;
99 std::span<u8> stream_pointer; 102 std::span<u8> stream_pointer;
103 VkDeviceSize stream_buffer_size;
104 VkDeviceSize region_size;
100 105
101 size_t iterator = 0; 106 size_t iterator = 0;
102 size_t used_iterator = 0; 107 size_t used_iterator = 0;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 71fdec809..93773a69f 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -238,6 +238,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
238 return any_r ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT; 238 return any_r ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
239 case PixelFormat::D16_UNORM: 239 case PixelFormat::D16_UNORM:
240 case PixelFormat::D32_FLOAT: 240 case PixelFormat::D32_FLOAT:
241 case PixelFormat::X8_D24_UNORM:
241 return VK_IMAGE_ASPECT_DEPTH_BIT; 242 return VK_IMAGE_ASPECT_DEPTH_BIT;
242 case PixelFormat::S8_UINT: 243 case PixelFormat::S8_UINT:
243 return VK_IMAGE_ASPECT_STENCIL_BIT; 244 return VK_IMAGE_ASPECT_STENCIL_BIT;
@@ -1200,6 +1201,9 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
1200 if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) { 1201 if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) {
1201 return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view); 1202 return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view);
1202 } 1203 }
1204 if (src_view.format == PixelFormat::D32_FLOAT) {
1205 return blit_image_helper.ConvertD32FToABGR8(dst, src_view);
1206 }
1203 break; 1207 break;
1204 case PixelFormat::R32_FLOAT: 1208 case PixelFormat::R32_FLOAT:
1205 if (src_view.format == PixelFormat::D32_FLOAT) { 1209 if (src_view.format == PixelFormat::D32_FLOAT) {
@@ -1526,15 +1530,15 @@ bool Image::IsRescaled() const noexcept {
1526} 1530}
1527 1531
1528bool Image::ScaleUp(bool ignore) { 1532bool Image::ScaleUp(bool ignore) {
1533 const auto& resolution = runtime->resolution;
1534 if (!resolution.active) {
1535 return false;
1536 }
1529 if (True(flags & ImageFlagBits::Rescaled)) { 1537 if (True(flags & ImageFlagBits::Rescaled)) {
1530 return false; 1538 return false;
1531 } 1539 }
1532 ASSERT(info.type != ImageType::Linear); 1540 ASSERT(info.type != ImageType::Linear);
1533 flags |= ImageFlagBits::Rescaled; 1541 flags |= ImageFlagBits::Rescaled;
1534 const auto& resolution = runtime->resolution;
1535 if (!resolution.active) {
1536 return false;
1537 }
1538 has_scaled = true; 1542 has_scaled = true;
1539 if (!scaled_image) { 1543 if (!scaled_image) {
1540 const bool is_2d = info.type == ImageType::e2D; 1544 const bool is_2d = info.type == ImageType::e2D;
@@ -1563,15 +1567,15 @@ bool Image::ScaleUp(bool ignore) {
1563} 1567}
1564 1568
1565bool Image::ScaleDown(bool ignore) { 1569bool Image::ScaleDown(bool ignore) {
1570 const auto& resolution = runtime->resolution;
1571 if (!resolution.active) {
1572 return false;
1573 }
1566 if (False(flags & ImageFlagBits::Rescaled)) { 1574 if (False(flags & ImageFlagBits::Rescaled)) {
1567 return false; 1575 return false;
1568 } 1576 }
1569 ASSERT(info.type != ImageType::Linear); 1577 ASSERT(info.type != ImageType::Linear);
1570 flags &= ~ImageFlagBits::Rescaled; 1578 flags &= ~ImageFlagBits::Rescaled;
1571 const auto& resolution = runtime->resolution;
1572 if (!resolution.active) {
1573 return false;
1574 }
1575 current_image = *original_image; 1579 current_image = *original_image;
1576 if (ignore) { 1580 if (ignore) {
1577 return true; 1581 return true;
@@ -2009,4 +2013,32 @@ void TextureCacheRuntime::AccelerateImageUpload(
2009 ASSERT(false); 2013 ASSERT(false);
2010} 2014}
2011 2015
2016void TextureCacheRuntime::TransitionImageLayout(Image& image) {
2017 if (!image.ExchangeInitialization()) {
2018 VkImageMemoryBarrier barrier{
2019 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
2020 .pNext = nullptr,
2021 .srcAccessMask = VK_ACCESS_NONE,
2022 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
2023 .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
2024 .newLayout = VK_IMAGE_LAYOUT_GENERAL,
2025 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
2026 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
2027 .image = image.Handle(),
2028 .subresourceRange{
2029 .aspectMask = image.AspectMask(),
2030 .baseMipLevel = 0,
2031 .levelCount = VK_REMAINING_MIP_LEVELS,
2032 .baseArrayLayer = 0,
2033 .layerCount = VK_REMAINING_ARRAY_LAYERS,
2034 },
2035 };
2036 scheduler.RequestOutsideRenderPassOperationContext();
2037 scheduler.Record([barrier = barrier](vk::CommandBuffer cmdbuf) {
2038 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
2039 VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, barrier);
2040 });
2041 }
2042}
2043
2012} // namespace Vulkan 2044} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index d6c5a15cc..7a0807709 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -92,6 +92,8 @@ public:
92 92
93 void InsertUploadMemoryBarrier() {} 93 void InsertUploadMemoryBarrier() {}
94 94
95 void TransitionImageLayout(Image& image);
96
95 bool HasBrokenTextureViewFormats() const noexcept { 97 bool HasBrokenTextureViewFormats() const noexcept {
96 // No known Vulkan driver has broken image views 98 // No known Vulkan driver has broken image views
97 return false; 99 return false;
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index e16cd5e73..5b3c7aa5a 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -85,6 +85,8 @@ PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) {
85 return PixelFormat::S8_UINT; 85 return PixelFormat::S8_UINT;
86 case Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT: 86 case Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT:
87 return PixelFormat::D32_FLOAT_S8_UINT; 87 return PixelFormat::D32_FLOAT_S8_UINT;
88 case Tegra::DepthFormat::X8Z24_UNORM:
89 return PixelFormat::X8_D24_UNORM;
88 default: 90 default:
89 UNIMPLEMENTED_MSG("Unimplemented format={}", format); 91 UNIMPLEMENTED_MSG("Unimplemented format={}", format);
90 return PixelFormat::S8_UINT_D24_UNORM; 92 return PixelFormat::S8_UINT_D24_UNORM;
@@ -202,6 +204,7 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format)
202PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format) { 204PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format) {
203 switch (format) { 205 switch (format) {
204 case Service::android::PixelFormat::Rgba8888: 206 case Service::android::PixelFormat::Rgba8888:
207 case Service::android::PixelFormat::Rgbx8888:
205 return PixelFormat::A8B8G8R8_UNORM; 208 return PixelFormat::A8B8G8R8_UNORM;
206 case Service::android::PixelFormat::Rgb565: 209 case Service::android::PixelFormat::Rgb565:
207 return PixelFormat::R5G6B5_UNORM; 210 return PixelFormat::R5G6B5_UNORM;
diff --git a/src/video_core/surface.h b/src/video_core/surface.h
index 9b9c4d9bc..a5e8e2f62 100644
--- a/src/video_core/surface.h
+++ b/src/video_core/surface.h
@@ -115,6 +115,7 @@ enum class PixelFormat {
115 // Depth formats 115 // Depth formats
116 D32_FLOAT = MaxColorFormat, 116 D32_FLOAT = MaxColorFormat,
117 D16_UNORM, 117 D16_UNORM,
118 X8_D24_UNORM,
118 119
119 MaxDepthFormat, 120 MaxDepthFormat,
120 121
@@ -251,6 +252,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{
251 1, // E5B9G9R9_FLOAT 252 1, // E5B9G9R9_FLOAT
252 1, // D32_FLOAT 253 1, // D32_FLOAT
253 1, // D16_UNORM 254 1, // D16_UNORM
255 1, // X8_D24_UNORM
254 1, // S8_UINT 256 1, // S8_UINT
255 1, // D24_UNORM_S8_UINT 257 1, // D24_UNORM_S8_UINT
256 1, // S8_UINT_D24_UNORM 258 1, // S8_UINT_D24_UNORM
@@ -360,6 +362,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{
360 1, // E5B9G9R9_FLOAT 362 1, // E5B9G9R9_FLOAT
361 1, // D32_FLOAT 363 1, // D32_FLOAT
362 1, // D16_UNORM 364 1, // D16_UNORM
365 1, // X8_D24_UNORM
363 1, // S8_UINT 366 1, // S8_UINT
364 1, // D24_UNORM_S8_UINT 367 1, // D24_UNORM_S8_UINT
365 1, // S8_UINT_D24_UNORM 368 1, // S8_UINT_D24_UNORM
@@ -469,6 +472,7 @@ constexpr std::array<u8, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{
469 32, // E5B9G9R9_FLOAT 472 32, // E5B9G9R9_FLOAT
470 32, // D32_FLOAT 473 32, // D32_FLOAT
471 16, // D16_UNORM 474 16, // D16_UNORM
475 32, // X8_D24_UNORM
472 8, // S8_UINT 476 8, // S8_UINT
473 32, // D24_UNORM_S8_UINT 477 32, // D24_UNORM_S8_UINT
474 32, // S8_UINT_D24_UNORM 478 32, // S8_UINT_D24_UNORM
diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp
index 56307d030..8c774f512 100644
--- a/src/video_core/texture_cache/format_lookup_table.cpp
+++ b/src/video_core/texture_cache/format_lookup_table.cpp
@@ -138,10 +138,16 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
138 return PixelFormat::E5B9G9R9_FLOAT; 138 return PixelFormat::E5B9G9R9_FLOAT;
139 case Hash(TextureFormat::Z32, FLOAT): 139 case Hash(TextureFormat::Z32, FLOAT):
140 return PixelFormat::D32_FLOAT; 140 return PixelFormat::D32_FLOAT;
141 case Hash(TextureFormat::Z32, FLOAT, UINT, UINT, UINT, LINEAR):
142 return PixelFormat::D32_FLOAT;
141 case Hash(TextureFormat::Z16, UNORM): 143 case Hash(TextureFormat::Z16, UNORM):
142 return PixelFormat::D16_UNORM; 144 return PixelFormat::D16_UNORM;
143 case Hash(TextureFormat::Z16, UNORM, UINT, UINT, UINT, LINEAR): 145 case Hash(TextureFormat::Z16, UNORM, UINT, UINT, UINT, LINEAR):
144 return PixelFormat::D16_UNORM; 146 return PixelFormat::D16_UNORM;
147 case Hash(TextureFormat::X8Z24, UNORM):
148 return PixelFormat::X8_D24_UNORM;
149 case Hash(TextureFormat::X8Z24, UNORM, UINT, UINT, UINT, LINEAR):
150 return PixelFormat::X8_D24_UNORM;
145 case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR): 151 case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR):
146 return PixelFormat::S8_UINT_D24_UNORM; 152 return PixelFormat::S8_UINT_D24_UNORM;
147 case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR): 153 case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR):
diff --git a/src/video_core/texture_cache/formatter.h b/src/video_core/texture_cache/formatter.h
index 9ee57a076..cabbfcb2d 100644
--- a/src/video_core/texture_cache/formatter.h
+++ b/src/video_core/texture_cache/formatter.h
@@ -211,6 +211,8 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str
211 return "D32_FLOAT"; 211 return "D32_FLOAT";
212 case PixelFormat::D16_UNORM: 212 case PixelFormat::D16_UNORM:
213 return "D16_UNORM"; 213 return "D16_UNORM";
214 case PixelFormat::X8_D24_UNORM:
215 return "X8_D24_UNORM";
214 case PixelFormat::S8_UINT: 216 case PixelFormat::S8_UINT:
215 return "S8_UINT"; 217 return "S8_UINT";
216 case PixelFormat::D24_UNORM_S8_UINT: 218 case PixelFormat::D24_UNORM_S8_UINT:
diff --git a/src/video_core/texture_cache/image_base.h b/src/video_core/texture_cache/image_base.h
index 55d49d017..0587d7b72 100644
--- a/src/video_core/texture_cache/image_base.h
+++ b/src/video_core/texture_cache/image_base.h
@@ -41,7 +41,7 @@ enum class ImageFlagBits : u32 {
41 IsRescalable = 1 << 15, 41 IsRescalable = 1 << 15,
42 42
43 AsynchronousDecode = 1 << 16, 43 AsynchronousDecode = 1 << 16,
44 IsDecoding = 1 << 17, ///< Is currently being decoded asynchornously. 44 IsDecoding = 1 << 17, ///< Is currently being decoded asynchronously.
45}; 45};
46DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits) 46DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)
47 47
diff --git a/src/video_core/texture_cache/image_view_base.cpp b/src/video_core/texture_cache/image_view_base.cpp
index 0c5f4450d..18b9250f9 100644
--- a/src/video_core/texture_cache/image_view_base.cpp
+++ b/src/video_core/texture_cache/image_view_base.cpp
@@ -85,6 +85,7 @@ bool ImageViewBase::SupportsAnisotropy() const noexcept {
85 // Depth formats 85 // Depth formats
86 case PixelFormat::D32_FLOAT: 86 case PixelFormat::D32_FLOAT:
87 case PixelFormat::D16_UNORM: 87 case PixelFormat::D16_UNORM:
88 case PixelFormat::X8_D24_UNORM:
88 // Stencil formats 89 // Stencil formats
89 case PixelFormat::S8_UINT: 90 case PixelFormat::S8_UINT:
90 // DepthStencil formats 91 // DepthStencil formats
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 1bdb0def5..d575c57ca 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -1016,6 +1016,7 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
1016 1016
1017 if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) { 1017 if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) {
1018 LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented"); 1018 LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented");
1019 runtime.TransitionImageLayout(image);
1019 return; 1020 return;
1020 } 1021 }
1021 if (True(image.flags & ImageFlagBits::AsynchronousDecode)) { 1022 if (True(image.flags & ImageFlagBits::AsynchronousDecode)) {
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index a83f5d41c..8151cabf0 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -68,6 +68,7 @@ struct LevelInfo {
68 Extent2D tile_size; 68 Extent2D tile_size;
69 u32 bpp_log2; 69 u32 bpp_log2;
70 u32 tile_width_spacing; 70 u32 tile_width_spacing;
71 u32 num_levels;
71}; 72};
72 73
73[[nodiscard]] constexpr u32 AdjustTileSize(u32 shift, u32 unit_factor, u32 dimension) { 74[[nodiscard]] constexpr u32 AdjustTileSize(u32 shift, u32 unit_factor, u32 dimension) {
@@ -118,11 +119,11 @@ template <u32 GOB_EXTENT>
118} 119}
119 120
120[[nodiscard]] constexpr Extent3D AdjustMipBlockSize(Extent3D num_tiles, Extent3D block_size, 121[[nodiscard]] constexpr Extent3D AdjustMipBlockSize(Extent3D num_tiles, Extent3D block_size,
121 u32 level) { 122 u32 level, u32 num_levels) {
122 return { 123 return {
123 .width = AdjustMipBlockSize<GOB_SIZE_X>(num_tiles.width, block_size.width, level), 124 .width = AdjustMipBlockSize<GOB_SIZE_X>(num_tiles.width, block_size.width, level),
124 .height = AdjustMipBlockSize<GOB_SIZE_Y>(num_tiles.height, block_size.height, level), 125 .height = AdjustMipBlockSize<GOB_SIZE_Y>(num_tiles.height, block_size.height, level),
125 .depth = level == 0 126 .depth = level == 0 && num_levels == 1
126 ? block_size.depth 127 ? block_size.depth
127 : AdjustMipBlockSize<GOB_SIZE_Z>(num_tiles.depth, block_size.depth, level), 128 : AdjustMipBlockSize<GOB_SIZE_Z>(num_tiles.depth, block_size.depth, level),
128 }; 129 };
@@ -166,13 +167,6 @@ template <u32 GOB_EXTENT>
166} 167}
167 168
168[[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) { 169[[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) {
169 if (level == 0) {
170 return Extent3D{
171 .width = info.block.width,
172 .height = info.block.height,
173 .depth = info.block.depth,
174 };
175 }
176 const Extent3D blocks = NumLevelBlocks(info, level); 170 const Extent3D blocks = NumLevelBlocks(info, level);
177 return Extent3D{ 171 return Extent3D{
178 .width = AdjustTileSize(info.block.width, GOB_SIZE_X, blocks.width), 172 .width = AdjustTileSize(info.block.width, GOB_SIZE_X, blocks.width),
@@ -257,7 +251,7 @@ template <u32 GOB_EXTENT>
257} 251}
258 252
259[[nodiscard]] constexpr LevelInfo MakeLevelInfo(PixelFormat format, Extent3D size, Extent3D block, 253[[nodiscard]] constexpr LevelInfo MakeLevelInfo(PixelFormat format, Extent3D size, Extent3D block,
260 u32 tile_width_spacing) { 254 u32 tile_width_spacing, u32 num_levels) {
261 const u32 bytes_per_block = BytesPerBlock(format); 255 const u32 bytes_per_block = BytesPerBlock(format);
262 return { 256 return {
263 .size = 257 .size =
@@ -270,16 +264,18 @@ template <u32 GOB_EXTENT>
270 .tile_size = DefaultBlockSize(format), 264 .tile_size = DefaultBlockSize(format),
271 .bpp_log2 = BytesPerBlockLog2(bytes_per_block), 265 .bpp_log2 = BytesPerBlockLog2(bytes_per_block),
272 .tile_width_spacing = tile_width_spacing, 266 .tile_width_spacing = tile_width_spacing,
267 .num_levels = num_levels,
273 }; 268 };
274} 269}
275 270
276[[nodiscard]] constexpr LevelInfo MakeLevelInfo(const ImageInfo& info) { 271[[nodiscard]] constexpr LevelInfo MakeLevelInfo(const ImageInfo& info) {
277 return MakeLevelInfo(info.format, info.size, info.block, info.tile_width_spacing); 272 return MakeLevelInfo(info.format, info.size, info.block, info.tile_width_spacing,
273 info.resources.levels);
278} 274}
279 275
280[[nodiscard]] constexpr u32 CalculateLevelOffset(PixelFormat format, Extent3D size, Extent3D block, 276[[nodiscard]] constexpr u32 CalculateLevelOffset(PixelFormat format, Extent3D size, Extent3D block,
281 u32 tile_width_spacing, u32 level) { 277 u32 tile_width_spacing, u32 level) {
282 const LevelInfo info = MakeLevelInfo(format, size, block, tile_width_spacing); 278 const LevelInfo info = MakeLevelInfo(format, size, block, tile_width_spacing, level);
283 u32 offset = 0; 279 u32 offset = 0;
284 for (u32 current_level = 0; current_level < level; ++current_level) { 280 for (u32 current_level = 0; current_level < level; ++current_level) {
285 offset += CalculateLevelSize(info, current_level); 281 offset += CalculateLevelSize(info, current_level);
@@ -466,7 +462,7 @@ template <u32 GOB_EXTENT>
466 }; 462 };
467 const u32 bpp_log2 = BytesPerBlockLog2(info.format); 463 const u32 bpp_log2 = BytesPerBlockLog2(info.format);
468 const u32 alignment = StrideAlignment(num_tiles, info.block, bpp_log2, info.tile_width_spacing); 464 const u32 alignment = StrideAlignment(num_tiles, info.block, bpp_log2, info.tile_width_spacing);
469 const Extent3D mip_block = AdjustMipBlockSize(num_tiles, info.block, 0); 465 const Extent3D mip_block = AdjustMipBlockSize(num_tiles, info.block, 0, info.resources.levels);
470 return Extent3D{ 466 return Extent3D{
471 .width = Common::AlignUpLog2(num_tiles.width, alignment), 467 .width = Common::AlignUpLog2(num_tiles.width, alignment),
472 .height = Common::AlignUpLog2(num_tiles.height, GOB_SIZE_Y_SHIFT + mip_block.height), 468 .height = Common::AlignUpLog2(num_tiles.height, GOB_SIZE_Y_SHIFT + mip_block.height),
@@ -533,7 +529,8 @@ void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr
533 UNIMPLEMENTED_IF(copy.image_extent != level_size); 529 UNIMPLEMENTED_IF(copy.image_extent != level_size);
534 530
535 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); 531 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
536 const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level); 532 const Extent3D block =
533 AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
537 534
538 size_t host_offset = copy.buffer_offset; 535 size_t host_offset = copy.buffer_offset;
539 536
@@ -698,7 +695,7 @@ u32 CalculateLevelStrideAlignment(const ImageInfo& info, u32 level) {
698 const Extent2D tile_size = DefaultBlockSize(info.format); 695 const Extent2D tile_size = DefaultBlockSize(info.format);
699 const Extent3D level_size = AdjustMipSize(info.size, level); 696 const Extent3D level_size = AdjustMipSize(info.size, level);
700 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); 697 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
701 const Extent3D block = AdjustMipBlockSize(num_tiles, info.block, level); 698 const Extent3D block = AdjustMipBlockSize(num_tiles, info.block, level, info.resources.levels);
702 const u32 bpp_log2 = BytesPerBlockLog2(info.format); 699 const u32 bpp_log2 = BytesPerBlockLog2(info.format);
703 return StrideAlignment(num_tiles, block, bpp_log2, info.tile_width_spacing); 700 return StrideAlignment(num_tiles, block, bpp_log2, info.tile_width_spacing);
704} 701}
@@ -887,7 +884,8 @@ boost::container::small_vector<BufferImageCopy, 16> UnswizzleImage(Tegra::Memory
887 .image_extent = level_size, 884 .image_extent = level_size,
888 }; 885 };
889 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); 886 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
890 const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level); 887 const Extent3D block =
888 AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
891 const u32 stride_alignment = StrideAlignment(num_tiles, info.block, gob, bpp_log2); 889 const u32 stride_alignment = StrideAlignment(num_tiles, info.block, gob, bpp_log2);
892 size_t guest_layer_offset = 0; 890 size_t guest_layer_offset = 0;
893 891
@@ -1041,7 +1039,7 @@ Extent3D MipBlockSize(const ImageInfo& info, u32 level) {
1041 const Extent2D tile_size = DefaultBlockSize(info.format); 1039 const Extent2D tile_size = DefaultBlockSize(info.format);
1042 const Extent3D level_size = AdjustMipSize(info.size, level); 1040 const Extent3D level_size = AdjustMipSize(info.size, level);
1043 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); 1041 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
1044 return AdjustMipBlockSize(num_tiles, level_info.block, level); 1042 return AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
1045} 1043}
1046 1044
1047boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const ImageInfo& info) { 1045boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const ImageInfo& info) {
@@ -1063,7 +1061,8 @@ boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const I
1063 for (s32 level = 0; level < num_levels; ++level) { 1061 for (s32 level = 0; level < num_levels; ++level) {
1064 const Extent3D level_size = AdjustMipSize(size, level); 1062 const Extent3D level_size = AdjustMipSize(size, level);
1065 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size); 1063 const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
1066 const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level); 1064 const Extent3D block =
1065 AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
1067 params[level] = SwizzleParameters{ 1066 params[level] = SwizzleParameters{
1068 .num_tiles = num_tiles, 1067 .num_tiles = num_tiles,
1069 .block = block, 1068 .block = block,
@@ -1195,7 +1194,7 @@ std::optional<SubresourceBase> FindSubresource(const ImageInfo& candidate, const
1195 return std::nullopt; 1194 return std::nullopt;
1196 } 1195 }
1197 } else { 1196 } else {
1198 // Format comaptibility is not relaxed, ensure we are creating a view on a compatible format 1197 // Format compatibility is not relaxed, ensure we are creating a view on a compatible format
1199 if (!IsViewCompatible(existing.format, candidate.format, broken_views, native_bgr)) { 1198 if (!IsViewCompatible(existing.format, candidate.format, broken_views, native_bgr)) {
1200 return std::nullopt; 1199 return std::nullopt;
1201 } 1200 }
@@ -1292,11 +1291,11 @@ u32 MapSizeBytes(const ImageBase& image) {
1292 } 1291 }
1293} 1292}
1294 1293
1295static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0}, 0) == 1294static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0, 1}, 0) ==
1296 0x7f8000); 1295 0x7f8000);
1297static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0}, 0) == 0x40000); 1296static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0, 1}, 0) == 0x4000);
1298 1297
1299static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0}, 0) == 0x40000); 1298static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0, 1}, 0) == 0x4000);
1300 1299
1301static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 0, 7) == 1300static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 0, 7) ==
1302 0x2afc00); 1301 0x2afc00);
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 3960b135a..876cec2e8 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -84,9 +84,12 @@ constexpr std::array VK_FORMAT_A4B4G4R4_UNORM_PACK16{
84} // namespace Alternatives 84} // namespace Alternatives
85 85
86enum class NvidiaArchitecture { 86enum class NvidiaArchitecture {
87 AmpereOrNewer, 87 KeplerOrOlder,
88 Maxwell,
89 Pascal,
90 Volta,
88 Turing, 91 Turing,
89 VoltaOrOlder, 92 AmpereOrNewer,
90}; 93};
91 94
92template <typename T> 95template <typename T>
@@ -200,6 +203,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
200 VK_FORMAT_BC7_UNORM_BLOCK, 203 VK_FORMAT_BC7_UNORM_BLOCK,
201 VK_FORMAT_D16_UNORM, 204 VK_FORMAT_D16_UNORM,
202 VK_FORMAT_D16_UNORM_S8_UINT, 205 VK_FORMAT_D16_UNORM_S8_UINT,
206 VK_FORMAT_X8_D24_UNORM_PACK32,
203 VK_FORMAT_D24_UNORM_S8_UINT, 207 VK_FORMAT_D24_UNORM_S8_UINT,
204 VK_FORMAT_D32_SFLOAT, 208 VK_FORMAT_D32_SFLOAT,
205 VK_FORMAT_D32_SFLOAT_S8_UINT, 209 VK_FORMAT_D32_SFLOAT_S8_UINT,
@@ -321,13 +325,38 @@ NvidiaArchitecture GetNvidiaArchitecture(vk::PhysicalDevice physical,
321 physical.GetProperties2(physical_properties); 325 physical.GetProperties2(physical_properties);
322 if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) { 326 if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) {
323 // Only Ampere and newer support this feature 327 // Only Ampere and newer support this feature
328 // TODO: Find a way to differentiate Ampere and Ada
324 return NvidiaArchitecture::AmpereOrNewer; 329 return NvidiaArchitecture::AmpereOrNewer;
325 } 330 }
326 }
327 if (exts.contains(VK_NV_SHADING_RATE_IMAGE_EXTENSION_NAME)) {
328 return NvidiaArchitecture::Turing; 331 return NvidiaArchitecture::Turing;
329 } 332 }
330 return NvidiaArchitecture::VoltaOrOlder; 333
334 if (exts.contains(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME)) {
335 VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT advanced_blending_props{};
336 advanced_blending_props.sType =
337 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_PROPERTIES_EXT;
338 VkPhysicalDeviceProperties2 physical_properties{};
339 physical_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
340 physical_properties.pNext = &advanced_blending_props;
341 physical.GetProperties2(physical_properties);
342 if (advanced_blending_props.advancedBlendMaxColorAttachments == 1) {
343 return NvidiaArchitecture::Maxwell;
344 }
345
346 if (exts.contains(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME)) {
347 VkPhysicalDeviceConservativeRasterizationPropertiesEXT conservative_raster_props{};
348 conservative_raster_props.sType =
349 VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONSERVATIVE_RASTERIZATION_PROPERTIES_EXT;
350 physical_properties.pNext = &conservative_raster_props;
351 physical.GetProperties2(physical_properties);
352 if (conservative_raster_props.degenerateLinesRasterized) {
353 return NvidiaArchitecture::Volta;
354 }
355 return NvidiaArchitecture::Pascal;
356 }
357 }
358
359 return NvidiaArchitecture::KeplerOrOlder;
331} 360}
332 361
333std::vector<const char*> ExtensionListForVulkan( 362std::vector<const char*> ExtensionListForVulkan(
@@ -504,19 +533,14 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
504 if (is_nvidia) { 533 if (is_nvidia) {
505 const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff; 534 const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff;
506 const auto arch = GetNvidiaArchitecture(physical, supported_extensions); 535 const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
507 switch (arch) { 536 if (arch >= NvidiaArchitecture::AmpereOrNewer) {
508 case NvidiaArchitecture::AmpereOrNewer:
509 LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math"); 537 LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math");
510 features.shader_float16_int8.shaderFloat16 = false; 538 features.shader_float16_int8.shaderFloat16 = false;
511 break; 539 } else if (arch <= NvidiaArchitecture::Volta) {
512 case NvidiaArchitecture::Turing:
513 break;
514 case NvidiaArchitecture::VoltaOrOlder:
515 if (nv_major_version < 527) { 540 if (nv_major_version < 527) {
516 LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor"); 541 LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor");
517 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); 542 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
518 } 543 }
519 break;
520 } 544 }
521 if (nv_major_version >= 510) { 545 if (nv_major_version >= 510) {
522 LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits"); 546 LOG_WARNING(Render_Vulkan, "NVIDIA Drivers >= 510 do not support MSAA image blits");
@@ -661,7 +685,15 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
661 "ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor"); 685 "ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor");
662 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME); 686 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
663 } 687 }
688 } else if (extensions.push_descriptor && is_nvidia) {
689 const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
690 if (arch <= NvidiaArchitecture::Pascal) {
691 LOG_WARNING(Render_Vulkan,
692 "Pascal and older architectures have broken VK_KHR_push_descriptor");
693 RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
694 }
664 } 695 }
696
665 if (is_mvk) { 697 if (is_mvk) {
666 LOG_WARNING(Render_Vulkan, 698 LOG_WARNING(Render_Vulkan,
667 "MVK driver breaks when using more than 16 vertex attributes/bindings"); 699 "MVK driver breaks when using more than 16 vertex attributes/bindings");
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 9be612392..282a2925d 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -314,7 +314,7 @@ public:
314 return GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY; 314 return GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY;
315 } 315 }
316 316
317 /// Returns true if the device suppors float64 natively. 317 /// Returns true if the device supports float64 natively.
318 bool IsFloat64Supported() const { 318 bool IsFloat64Supported() const {
319 return features.features.shaderFloat64; 319 return features.features.shaderFloat64;
320 } 320 }
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index 3ef381a38..8dd1667f3 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -9,6 +9,7 @@
9#include "common/alignment.h" 9#include "common/alignment.h"
10#include "common/assert.h" 10#include "common/assert.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/literals.h"
12#include "common/logging/log.h" 13#include "common/logging/log.h"
13#include "common/polyfill_ranges.h" 14#include "common/polyfill_ranges.h"
14#include "video_core/vulkan_common/vma.h" 15#include "video_core/vulkan_common/vma.h"
@@ -65,12 +66,12 @@ struct Range {
65 switch (usage) { 66 switch (usage) {
66 case MemoryUsage::Upload: 67 case MemoryUsage::Upload:
67 case MemoryUsage::Stream: 68 case MemoryUsage::Stream:
68 return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; 69 return VMA_ALLOCATION_CREATE_MAPPED_BIT |
70 VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
69 case MemoryUsage::Download: 71 case MemoryUsage::Download:
70 return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; 72 return VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
71 case MemoryUsage::DeviceLocal: 73 case MemoryUsage::DeviceLocal:
72 return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | 74 return {};
73 VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT;
74 } 75 }
75 return {}; 76 return {};
76} 77}
@@ -212,7 +213,20 @@ MemoryAllocator::MemoryAllocator(const Device& device_)
212 : device{device_}, allocator{device.GetAllocator()}, 213 : device{device_}, allocator{device.GetAllocator()},
213 properties{device_.GetPhysical().GetMemoryProperties().memoryProperties}, 214 properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
214 buffer_image_granularity{ 215 buffer_image_granularity{
215 device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {} 216 device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {
217 // GPUs not supporting rebar may only have a region with less than 256MB host visible/device
218 // local memory. In that case, opening 2 RenderDoc captures side-by-side is not possible due to
219 // the heap running out of memory. With RenderDoc attached and only a small host/device region,
220 // only allow the stream buffer in this memory heap.
221 if (device.HasDebuggingToolAttached()) {
222 using namespace Common::Literals;
223 ForEachDeviceLocalHostVisibleHeap(device, [this](size_t index, VkMemoryHeap& heap) {
224 if (heap.size <= 256_MiB) {
225 valid_memory_types &= ~(1u << index);
226 }
227 });
228 }
229}
216 230
217MemoryAllocator::~MemoryAllocator() = default; 231MemoryAllocator::~MemoryAllocator() = default;
218 232
@@ -239,12 +253,11 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
239 253
240vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const { 254vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const {
241 const VmaAllocationCreateInfo alloc_ci = { 255 const VmaAllocationCreateInfo alloc_ci = {
242 .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT | 256 .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
243 MemoryUsageVmaFlags(usage),
244 .usage = MemoryUsageVma(usage), 257 .usage = MemoryUsageVma(usage),
245 .requiredFlags = 0, 258 .requiredFlags = 0,
246 .preferredFlags = MemoryUsagePreferedVmaFlags(usage), 259 .preferredFlags = MemoryUsagePreferedVmaFlags(usage),
247 .memoryTypeBits = 0, 260 .memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types,
248 .pool = VK_NULL_HANDLE, 261 .pool = VK_NULL_HANDLE,
249 .pUserData = nullptr, 262 .pUserData = nullptr,
250 .priority = 0.f, 263 .priority = 0.f,
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h
index f449bc8d0..38a182bcb 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.h
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h
@@ -7,6 +7,7 @@
7#include <span> 7#include <span>
8#include <vector> 8#include <vector>
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "video_core/vulkan_common/vulkan_device.h"
10#include "video_core/vulkan_common/vulkan_wrapper.h" 11#include "video_core/vulkan_common/vulkan_wrapper.h"
11 12
12VK_DEFINE_HANDLE(VmaAllocator) 13VK_DEFINE_HANDLE(VmaAllocator)
@@ -26,6 +27,18 @@ enum class MemoryUsage {
26 Stream, ///< Requests device local host visible buffer, falling back host memory. 27 Stream, ///< Requests device local host visible buffer, falling back host memory.
27}; 28};
28 29
30template <typename F>
31void ForEachDeviceLocalHostVisibleHeap(const Device& device, F&& f) {
32 auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
33 for (size_t i = 0; i < memory_props.memoryTypeCount; i++) {
34 auto& memory_type = memory_props.memoryTypes[i];
35 if ((memory_type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) &&
36 (memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
37 f(memory_type.heapIndex, memory_props.memoryHeaps[memory_type.heapIndex]);
38 }
39 }
40}
41
29/// Ownership handle of a memory commitment. 42/// Ownership handle of a memory commitment.
30/// Points to a subregion of a memory allocation. 43/// Points to a subregion of a memory allocation.
31class MemoryCommit { 44class MemoryCommit {
@@ -124,6 +137,7 @@ private:
124 std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations. 137 std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations.
125 VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers 138 VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers
126 // and optimal images 139 // and optimal images
140 u32 valid_memory_types{~0u};
127}; 141};
128 142
129} // namespace Vulkan 143} // namespace Vulkan
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index 1e3c0fa64..0487cd3b6 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -117,6 +117,9 @@ public:
117 virtual ~Exception() = default; 117 virtual ~Exception() = default;
118 118
119 const char* what() const noexcept override; 119 const char* what() const noexcept override;
120 VkResult GetResult() const noexcept {
121 return result;
122 }
120 123
121private: 124private:
122 VkResult result; 125 VkResult result;
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 8f86a1553..9ebece907 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -195,6 +195,8 @@ add_executable(yuzu
195 multiplayer/state.cpp 195 multiplayer/state.cpp
196 multiplayer/state.h 196 multiplayer/state.h
197 multiplayer/validation.h 197 multiplayer/validation.h
198 play_time_manager.cpp
199 play_time_manager.h
198 precompiled_headers.h 200 precompiled_headers.h
199 qt_common.cpp 201 qt_common.cpp
200 qt_common.h 202 qt_common.h
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp
index d15559518..ca0e14fad 100644
--- a/src/yuzu/applets/qt_controller.cpp
+++ b/src/yuzu/applets/qt_controller.cpp
@@ -23,6 +23,7 @@
23#include "yuzu/configuration/configure_vibration.h" 23#include "yuzu/configuration/configure_vibration.h"
24#include "yuzu/configuration/input_profiles.h" 24#include "yuzu/configuration/input_profiles.h"
25#include "yuzu/main.h" 25#include "yuzu/main.h"
26#include "yuzu/util/controller_navigation.h"
26 27
27namespace { 28namespace {
28 29
@@ -132,6 +133,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
132 ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected, 133 ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
133 }; 134 };
134 135
136 ui->labelError->setVisible(false);
137
135 // Setup/load everything prior to setting up connections. 138 // Setup/load everything prior to setting up connections.
136 // This avoids unintentionally changing the states of elements while loading them in. 139 // This avoids unintentionally changing the states of elements while loading them in.
137 SetSupportedControllers(); 140 SetSupportedControllers();
@@ -143,6 +146,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
143 146
144 LoadConfiguration(); 147 LoadConfiguration();
145 148
149 controller_navigation = new ControllerNavigation(system.HIDCore(), this);
150
146 for (std::size_t i = 0; i < NUM_PLAYERS; ++i) { 151 for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
147 SetExplainText(i); 152 SetExplainText(i);
148 UpdateControllerIcon(i); 153 UpdateControllerIcon(i);
@@ -151,6 +156,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
151 156
152 connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) { 157 connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
153 if (checked) { 158 if (checked) {
159 // Hide eventual error message about number of controllers
160 ui->labelError->setVisible(false);
154 for (std::size_t index = 0; index <= i; ++index) { 161 for (std::size_t index = 0; index <= i; ++index) {
155 connected_controller_checkboxes[index]->setChecked(checked); 162 connected_controller_checkboxes[index]->setChecked(checked);
156 } 163 }
@@ -199,6 +206,12 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
199 connect(ui->buttonBox, &QDialogButtonBox::accepted, this, 206 connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
200 &QtControllerSelectorDialog::ApplyConfiguration); 207 &QtControllerSelectorDialog::ApplyConfiguration);
201 208
209 connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
210 [this](Qt::Key key) {
211 QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
212 QCoreApplication::postEvent(this, event);
213 });
214
202 // Enhancement: Check if the parameters have already been met before disconnecting controllers. 215 // Enhancement: Check if the parameters have already been met before disconnecting controllers.
203 // If all the parameters are met AND only allows a single player, 216 // If all the parameters are met AND only allows a single player,
204 // stop the constructor here as we do not need to continue. 217 // stop the constructor here as we do not need to continue.
@@ -217,6 +230,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
217} 230}
218 231
219QtControllerSelectorDialog::~QtControllerSelectorDialog() { 232QtControllerSelectorDialog::~QtControllerSelectorDialog() {
233 controller_navigation->UnloadController();
220 system.HIDCore().DisableAllControllerConfiguration(); 234 system.HIDCore().DisableAllControllerConfiguration();
221} 235}
222 236
@@ -291,6 +305,31 @@ void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
291 dialog.exec(); 305 dialog.exec();
292} 306}
293 307
308void QtControllerSelectorDialog::keyPressEvent(QKeyEvent* evt) {
309 const auto num_connected_players = static_cast<int>(
310 std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
311 [](const QGroupBox* player) { return player->isChecked(); }));
312
313 const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
314 const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
315
316 if ((evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return) && !parameters_met) {
317 // Display error message when trying to validate using "Enter" and "OK" button is disabled
318 ui->labelError->setVisible(true);
319 return;
320 } else if (evt->key() == Qt::Key_Left && num_connected_players > min_supported_players) {
321 // Remove a player if possible
322 connected_controller_checkboxes[num_connected_players - 1]->setChecked(false);
323 return;
324 } else if (evt->key() == Qt::Key_Right && num_connected_players < max_supported_players) {
325 // Add a player, if possible
326 ui->labelError->setVisible(false);
327 connected_controller_checkboxes[num_connected_players]->setChecked(true);
328 return;
329 }
330 QDialog::keyPressEvent(evt);
331}
332
294bool QtControllerSelectorDialog::CheckIfParametersMet() { 333bool QtControllerSelectorDialog::CheckIfParametersMet() {
295 // Here, we check and validate the current configuration against all applicable parameters. 334 // Here, we check and validate the current configuration against all applicable parameters.
296 const auto num_connected_players = static_cast<int>( 335 const auto num_connected_players = static_cast<int>(
diff --git a/src/yuzu/applets/qt_controller.h b/src/yuzu/applets/qt_controller.h
index 2fdc35857..7f0673d06 100644
--- a/src/yuzu/applets/qt_controller.h
+++ b/src/yuzu/applets/qt_controller.h
@@ -34,6 +34,8 @@ class HIDCore;
34enum class NpadStyleIndex : u8; 34enum class NpadStyleIndex : u8;
35} // namespace Core::HID 35} // namespace Core::HID
36 36
37class ControllerNavigation;
38
37class QtControllerSelectorDialog final : public QDialog { 39class QtControllerSelectorDialog final : public QDialog {
38 Q_OBJECT 40 Q_OBJECT
39 41
@@ -46,6 +48,8 @@ public:
46 48
47 int exec() override; 49 int exec() override;
48 50
51 void keyPressEvent(QKeyEvent* evt) override;
52
49private: 53private:
50 // Applies the current configuration. 54 // Applies the current configuration.
51 void ApplyConfiguration(); 55 void ApplyConfiguration();
@@ -110,6 +114,8 @@ private:
110 114
111 Core::System& system; 115 Core::System& system;
112 116
117 ControllerNavigation* controller_navigation = nullptr;
118
113 // This is true if and only if all parameters are met. Otherwise, this is false. 119 // This is true if and only if all parameters are met. Otherwise, this is false.
114 // This determines whether the "OK" button can be clicked to exit the applet. 120 // This determines whether the "OK" button can be clicked to exit the applet.
115 bool parameters_met{false}; 121 bool parameters_met{false};
diff --git a/src/yuzu/applets/qt_controller.ui b/src/yuzu/applets/qt_controller.ui
index 729e921ee..6f7cb3c13 100644
--- a/src/yuzu/applets/qt_controller.ui
+++ b/src/yuzu/applets/qt_controller.ui
@@ -2624,13 +2624,53 @@
2624 </spacer> 2624 </spacer>
2625 </item> 2625 </item>
2626 <item alignment="Qt::AlignBottom"> 2626 <item alignment="Qt::AlignBottom">
2627 <widget class="QDialogButtonBox" name="buttonBox"> 2627 <widget class="QWidget" name="closeButtons" native="true">
2628 <property name="enabled"> 2628 <layout class="QVBoxLayout" name="verticalLayout_46">
2629 <bool>true</bool> 2629 <property name="spacing">
2630 </property> 2630 <number>7</number>
2631 <property name="standardButtons"> 2631 </property>
2632 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> 2632 <property name="leftMargin">
2633 </property> 2633 <number>0</number>
2634 </property>
2635 <property name="topMargin">
2636 <number>0</number>
2637 </property>
2638 <property name="rightMargin">
2639 <number>0</number>
2640 </property>
2641 <property name="bottomMargin">
2642 <number>0</number>
2643 </property>
2644 <item>
2645 <widget class="QLabel" name="labelError">
2646 <property name="enabled">
2647 <bool>true</bool>
2648 </property>
2649 <property name="styleSheet">
2650 <string notr="true">QLabel { color : red; }</string>
2651 </property>
2652 <property name="text">
2653 <string>Not enough controllers</string>
2654 </property>
2655 <property name="alignment">
2656 <set>Qt::AlignCenter</set>
2657 </property>
2658 <property name="margin">
2659 <number>0</number>
2660 </property>
2661 </widget>
2662 </item>
2663 <item>
2664 <widget class="QDialogButtonBox" name="buttonBox">
2665 <property name="enabled">
2666 <bool>true</bool>
2667 </property>
2668 <property name="standardButtons">
2669 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
2670 </property>
2671 </widget>
2672 </item>
2673 </layout>
2634 </widget> 2674 </widget>
2635 </item> 2675 </item>
2636 </layout> 2676 </layout>
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index 9ccfb2435..81dd51ad3 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -42,6 +42,9 @@ void ConfigureAudio::Setup(const ConfigurationShared::Builder& builder) {
42 for (auto* setting : Settings::values.linkage.by_category[category]) { 42 for (auto* setting : Settings::values.linkage.by_category[category]) {
43 settings.push_back(setting); 43 settings.push_back(setting);
44 } 44 }
45 for (auto* setting : UISettings::values.linkage.by_category[category]) {
46 settings.push_back(setting);
47 }
45 }; 48 };
46 49
47 push(Settings::Category::Audio); 50 push(Settings::Category::Audio);
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index a9fde9f4f..82f3b6e78 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -123,6 +123,8 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
123 connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); 123 connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
124 connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); 124 connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
125 connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); 125 connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
126 connect(ui->show_play_time, &QCheckBox::stateChanged, this,
127 &ConfigureUi::RequestGameListUpdate);
126 connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, 128 connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
127 &ConfigureUi::RequestGameListUpdate); 129 &ConfigureUi::RequestGameListUpdate);
128 connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), 130 connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
@@ -167,6 +169,7 @@ void ConfigureUi::ApplyConfiguration() {
167 UISettings::values.show_compat = ui->show_compat->isChecked(); 169 UISettings::values.show_compat = ui->show_compat->isChecked();
168 UISettings::values.show_size = ui->show_size->isChecked(); 170 UISettings::values.show_size = ui->show_size->isChecked();
169 UISettings::values.show_types = ui->show_types->isChecked(); 171 UISettings::values.show_types = ui->show_types->isChecked();
172 UISettings::values.show_play_time = ui->show_play_time->isChecked();
170 UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt(); 173 UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt();
171 UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt(); 174 UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt();
172 UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); 175 UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
@@ -179,6 +182,7 @@ void ConfigureUi::ApplyConfiguration() {
179 const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText()); 182 const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText());
180 UISettings::values.screenshot_height.SetValue(height); 183 UISettings::values.screenshot_height.SetValue(height);
181 184
185 RequestGameListUpdate();
182 system.ApplySettings(); 186 system.ApplySettings();
183} 187}
184 188
@@ -194,6 +198,7 @@ void ConfigureUi::SetConfiguration() {
194 ui->show_compat->setChecked(UISettings::values.show_compat.GetValue()); 198 ui->show_compat->setChecked(UISettings::values.show_compat.GetValue());
195 ui->show_size->setChecked(UISettings::values.show_size.GetValue()); 199 ui->show_size->setChecked(UISettings::values.show_size.GetValue());
196 ui->show_types->setChecked(UISettings::values.show_types.GetValue()); 200 ui->show_types->setChecked(UISettings::values.show_types.GetValue());
201 ui->show_play_time->setChecked(UISettings::values.show_play_time.GetValue());
197 ui->game_icon_size_combobox->setCurrentIndex( 202 ui->game_icon_size_combobox->setCurrentIndex(
198 ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue())); 203 ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue()));
199 ui->folder_icon_size_combobox->setCurrentIndex( 204 ui->folder_icon_size_combobox->setCurrentIndex(
diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui
index cb66ef104..b8e648381 100644
--- a/src/yuzu/configuration/configure_ui.ui
+++ b/src/yuzu/configuration/configure_ui.ui
@@ -105,6 +105,13 @@
105 </widget> 105 </widget>
106 </item> 106 </item>
107 <item> 107 <item>
108 <widget class="QCheckBox" name="show_play_time">
109 <property name="text">
110 <string>Show Play Time Column</string>
111 </property>
112 </widget>
113 </item>
114 <item>
108 <layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2"> 115 <layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2">
109 <item> 116 <item>
110 <widget class="QLabel" name="game_icon_size_label"> 117 <widget class="QLabel" name="game_icon_size_label">
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
index 276bdbaba..a4e8af1b4 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -29,9 +29,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
29 INSERT(Settings, sink_id, "Output Engine:", ""); 29 INSERT(Settings, sink_id, "Output Engine:", "");
30 INSERT(Settings, audio_output_device_id, "Output Device:", ""); 30 INSERT(Settings, audio_output_device_id, "Output Device:", "");
31 INSERT(Settings, audio_input_device_id, "Input Device:", ""); 31 INSERT(Settings, audio_input_device_id, "Input Device:", "");
32 INSERT(Settings, audio_muted, "Mute audio when in background", ""); 32 INSERT(Settings, audio_muted, "Mute audio", "");
33 INSERT(Settings, volume, "Volume:", ""); 33 INSERT(Settings, volume, "Volume:", "");
34 INSERT(Settings, dump_audio_commands, "", ""); 34 INSERT(Settings, dump_audio_commands, "", "");
35 INSERT(UISettings, mute_when_in_background, "Mute audio when in background", "");
35 36
36 // Core 37 // Core
37 INSERT(Settings, use_multi_core, "Multicore CPU Emulation", ""); 38 INSERT(Settings, use_multi_core, "Multicore CPU Emulation", "");
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index f254c1e1c..74f48031a 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -312,8 +312,10 @@ void GameList::OnFilterCloseClicked() {
312} 312}
313 313
314GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_, 314GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_,
315 Core::System& system_, GMainWindow* parent) 315 PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
316 : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, system{system_} { 316 GMainWindow* parent)
317 : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_},
318 play_time_manager{play_time_manager_}, system{system_} {
317 watcher = new QFileSystemWatcher(this); 319 watcher = new QFileSystemWatcher(this);
318 connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); 320 connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
319 321
@@ -340,6 +342,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid
340 342
341 tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons); 343 tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons);
342 tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); 344 tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
345 tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
343 item_model->setSortRole(GameListItemPath::SortRole); 346 item_model->setSortRole(GameListItemPath::SortRole);
344 347
345 connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons); 348 connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
@@ -548,6 +551,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
548 QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update")); 551 QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update"));
549 QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC")); 552 QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC"));
550 QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration")); 553 QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration"));
554 QAction* remove_play_time_data = remove_menu->addAction(tr("Remove Play Time Data"));
551 QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage")); 555 QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage"));
552 QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache")); 556 QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache"));
553 QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache")); 557 QAction* remove_vk_shader_cache = remove_menu->addAction(tr("Remove Vulkan Pipeline Cache"));
@@ -560,9 +564,9 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
560 QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity")); 564 QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
561 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); 565 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
562 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); 566 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
563#ifndef WIN32
564 QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut")); 567 QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
565 QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop")); 568 QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
569#ifndef WIN32
566 QAction* create_applications_menu_shortcut = 570 QAction* create_applications_menu_shortcut =
567 shortcut_menu->addAction(tr("Add to Applications Menu")); 571 shortcut_menu->addAction(tr("Add to Applications Menu"));
568#endif 572#endif
@@ -622,6 +626,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
622 connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() { 626 connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() {
623 emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path); 627 emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path);
624 }); 628 });
629 connect(remove_play_time_data, &QAction::triggered,
630 [this, program_id]() { emit RemovePlayTimeRequested(program_id); });
625 connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] { 631 connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] {
626 emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path); 632 emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path);
627 }); 633 });
@@ -638,10 +644,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
638 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { 644 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
639 emit NavigateToGamedbEntryRequested(program_id, compatibility_list); 645 emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
640 }); 646 });
641#ifndef WIN32
642 connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() { 647 connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
643 emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop); 648 emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
644 }); 649 });
650#ifndef WIN32
645 connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() { 651 connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
646 emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications); 652 emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
647 }); 653 });
@@ -790,6 +796,7 @@ void GameList::RetranslateUI() {
790 item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons")); 796 item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
791 item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type")); 797 item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
792 item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size")); 798 item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
799 item_model->setHeaderData(COLUMN_PLAY_TIME, Qt::Horizontal, tr("Play time"));
793} 800}
794 801
795void GameListSearchField::changeEvent(QEvent* event) { 802void GameListSearchField::changeEvent(QEvent* event) {
@@ -817,6 +824,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
817 tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat); 824 tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
818 tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types); 825 tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types);
819 tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size); 826 tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
827 tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
820 828
821 // Delete any rows that might already exist if we're repopulating 829 // Delete any rows that might already exist if we're repopulating
822 item_model->removeRows(0, item_model->rowCount()); 830 item_model->removeRows(0, item_model->rowCount());
@@ -825,7 +833,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
825 emit ShouldCancelWorker(); 833 emit ShouldCancelWorker();
826 834
827 GameListWorker* worker = 835 GameListWorker* worker =
828 new GameListWorker(vfs, provider, game_dirs, compatibility_list, system); 836 new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system);
829 837
830 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); 838 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
831 connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry, 839 connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry,
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 1fcbbf0ba..712570cea 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -18,6 +18,7 @@
18#include "core/core.h" 18#include "core/core.h"
19#include "uisettings.h" 19#include "uisettings.h"
20#include "yuzu/compatibility_list.h" 20#include "yuzu/compatibility_list.h"
21#include "yuzu/play_time_manager.h"
21 22
22namespace Core { 23namespace Core {
23class System; 24class System;
@@ -75,11 +76,13 @@ public:
75 COLUMN_ADD_ONS, 76 COLUMN_ADD_ONS,
76 COLUMN_FILE_TYPE, 77 COLUMN_FILE_TYPE,
77 COLUMN_SIZE, 78 COLUMN_SIZE,
79 COLUMN_PLAY_TIME,
78 COLUMN_COUNT, // Number of columns 80 COLUMN_COUNT, // Number of columns
79 }; 81 };
80 82
81 explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_, 83 explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
82 FileSys::ManualContentProvider* provider_, Core::System& system_, 84 FileSys::ManualContentProvider* provider_,
85 PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
83 GMainWindow* parent = nullptr); 86 GMainWindow* parent = nullptr);
84 ~GameList() override; 87 ~GameList() override;
85 88
@@ -113,6 +116,7 @@ signals:
113 void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type); 116 void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type);
114 void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, 117 void RemoveFileRequested(u64 program_id, GameListRemoveTarget target,
115 const std::string& game_path); 118 const std::string& game_path);
119 void RemovePlayTimeRequested(u64 program_id);
116 void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); 120 void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
117 void VerifyIntegrityRequested(const std::string& game_path); 121 void VerifyIntegrityRequested(const std::string& game_path);
118 void CopyTIDRequested(u64 program_id); 122 void CopyTIDRequested(u64 program_id);
@@ -168,6 +172,7 @@ private:
168 172
169 friend class GameListSearchField; 173 friend class GameListSearchField;
170 174
175 const PlayTime::PlayTimeManager& play_time_manager;
171 Core::System& system; 176 Core::System& system;
172}; 177};
173 178
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 1800f090f..86a0c41d9 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -18,6 +18,7 @@
18#include "common/common_types.h" 18#include "common/common_types.h"
19#include "common/logging/log.h" 19#include "common/logging/log.h"
20#include "common/string_util.h" 20#include "common/string_util.h"
21#include "yuzu/play_time_manager.h"
21#include "yuzu/uisettings.h" 22#include "yuzu/uisettings.h"
22#include "yuzu/util/util.h" 23#include "yuzu/util/util.h"
23 24
@@ -221,6 +222,31 @@ public:
221 } 222 }
222}; 223};
223 224
225/**
226 * GameListItem for Play Time values.
227 * This object stores the play time of a game in seconds, and its readable
228 * representation in minutes/hours
229 */
230class GameListItemPlayTime : public GameListItem {
231public:
232 static constexpr int PlayTimeRole = SortRole;
233
234 GameListItemPlayTime() = default;
235 explicit GameListItemPlayTime(const qulonglong time_seconds) {
236 setData(time_seconds, PlayTimeRole);
237 }
238
239 void setData(const QVariant& value, int role) override {
240 qulonglong time_seconds = value.toULongLong();
241 GameListItem::setData(PlayTime::ReadablePlayTime(time_seconds), Qt::DisplayRole);
242 GameListItem::setData(value, PlayTimeRole);
243 }
244
245 bool operator<(const QStandardItem& other) const override {
246 return data(PlayTimeRole).toULongLong() < other.data(PlayTimeRole).toULongLong();
247 }
248};
249
224class GameListDir : public GameListItem { 250class GameListDir : public GameListItem {
225public: 251public:
226 static constexpr int GameDirRole = Qt::UserRole + 2; 252 static constexpr int GameDirRole = Qt::UserRole + 2;
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index e7fb8a282..588f1dd6e 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -194,6 +194,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
194 const std::size_t size, const std::vector<u8>& icon, 194 const std::size_t size, const std::vector<u8>& icon,
195 Loader::AppLoader& loader, u64 program_id, 195 Loader::AppLoader& loader, u64 program_id,
196 const CompatibilityList& compatibility_list, 196 const CompatibilityList& compatibility_list,
197 const PlayTime::PlayTimeManager& play_time_manager,
197 const FileSys::PatchManager& patch) { 198 const FileSys::PatchManager& patch) {
198 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); 199 const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
199 200
@@ -212,6 +213,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
212 new GameListItemCompat(compatibility), 213 new GameListItemCompat(compatibility),
213 new GameListItem(file_type_string), 214 new GameListItem(file_type_string),
214 new GameListItemSize(size), 215 new GameListItemSize(size),
216 new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)),
215 }; 217 };
216 218
217 const auto patch_versions = GetGameListCachedObject( 219 const auto patch_versions = GetGameListCachedObject(
@@ -227,9 +229,12 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
227GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_, 229GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_,
228 FileSys::ManualContentProvider* provider_, 230 FileSys::ManualContentProvider* provider_,
229 QVector<UISettings::GameDir>& game_dirs_, 231 QVector<UISettings::GameDir>& game_dirs_,
230 const CompatibilityList& compatibility_list_, Core::System& system_) 232 const CompatibilityList& compatibility_list_,
233 const PlayTime::PlayTimeManager& play_time_manager_,
234 Core::System& system_)
231 : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_}, 235 : vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_},
232 compatibility_list{compatibility_list_}, system{system_} {} 236 compatibility_list{compatibility_list_},
237 play_time_manager{play_time_manager_}, system{system_} {}
233 238
234GameListWorker::~GameListWorker() = default; 239GameListWorker::~GameListWorker() = default;
235 240
@@ -280,7 +285,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
280 } 285 }
281 286
282 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader, 287 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
283 program_id, compatibility_list, patch), 288 program_id, compatibility_list, play_time_manager, patch),
284 parent_dir); 289 parent_dir);
285 } 290 }
286} 291}
@@ -357,7 +362,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
357 362
358 emit EntryReady(MakeGameListEntry(physical_name, name, 363 emit EntryReady(MakeGameListEntry(physical_name, name,
359 Common::FS::GetSize(physical_name), icon, 364 Common::FS::GetSize(physical_name), icon,
360 *loader, id, compatibility_list, patch), 365 *loader, id, compatibility_list,
366 play_time_manager, patch),
361 parent_dir); 367 parent_dir);
362 } 368 }
363 } else { 369 } else {
@@ -370,10 +376,11 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
370 const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), 376 const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
371 system.GetContentProvider()}; 377 system.GetContentProvider()};
372 378
373 emit EntryReady( 379 emit EntryReady(MakeGameListEntry(physical_name, name,
374 MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name), 380 Common::FS::GetSize(physical_name), icon,
375 icon, *loader, program_id, compatibility_list, patch), 381 *loader, program_id, compatibility_list,
376 parent_dir); 382 play_time_manager, patch),
383 parent_dir);
377 } 384 }
378 } 385 }
379 } else if (is_dir) { 386 } else if (is_dir) {
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 24a4e92c3..2bb0a0cb6 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -13,6 +13,7 @@
13#include <QString> 13#include <QString>
14 14
15#include "yuzu/compatibility_list.h" 15#include "yuzu/compatibility_list.h"
16#include "yuzu/play_time_manager.h"
16 17
17namespace Core { 18namespace Core {
18class System; 19class System;
@@ -36,7 +37,9 @@ public:
36 explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_, 37 explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
37 FileSys::ManualContentProvider* provider_, 38 FileSys::ManualContentProvider* provider_,
38 QVector<UISettings::GameDir>& game_dirs_, 39 QVector<UISettings::GameDir>& game_dirs_,
39 const CompatibilityList& compatibility_list_, Core::System& system_); 40 const CompatibilityList& compatibility_list_,
41 const PlayTime::PlayTimeManager& play_time_manager_,
42 Core::System& system_);
40 ~GameListWorker() override; 43 ~GameListWorker() override;
41 44
42 /// Starts the processing of directory tree information. 45 /// Starts the processing of directory tree information.
@@ -76,6 +79,7 @@ private:
76 FileSys::ManualContentProvider* provider; 79 FileSys::ManualContentProvider* provider;
77 QVector<UISettings::GameDir>& game_dirs; 80 QVector<UISettings::GameDir>& game_dirs;
78 const CompatibilityList& compatibility_list; 81 const CompatibilityList& compatibility_list;
82 const PlayTime::PlayTimeManager& play_time_manager;
79 83
80 QStringList watch_list; 84 QStringList watch_list;
81 std::atomic_bool stop_processing; 85 std::atomic_bool stop_processing;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index adb7b332f..5427758c1 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -18,6 +18,8 @@
18#include <sys/socket.h> 18#include <sys/socket.h>
19#endif 19#endif
20 20
21#include <boost/container/flat_set.hpp>
22
21// VFS includes must be before glad as they will conflict with Windows file api, which uses defines. 23// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
22#include "applets/qt_amiibo_settings.h" 24#include "applets/qt_amiibo_settings.h"
23#include "applets/qt_controller.h" 25#include "applets/qt_controller.h"
@@ -96,6 +98,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
96#include "common/scm_rev.h" 98#include "common/scm_rev.h"
97#include "common/scope_exit.h" 99#include "common/scope_exit.h"
98#ifdef _WIN32 100#ifdef _WIN32
101#include <shlobj.h>
99#include "common/windows/timer_resolution.h" 102#include "common/windows/timer_resolution.h"
100#endif 103#endif
101#ifdef ARCHITECTURE_x86_64 104#ifdef ARCHITECTURE_x86_64
@@ -148,6 +151,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
148#include "yuzu/install_dialog.h" 151#include "yuzu/install_dialog.h"
149#include "yuzu/loading_screen.h" 152#include "yuzu/loading_screen.h"
150#include "yuzu/main.h" 153#include "yuzu/main.h"
154#include "yuzu/play_time_manager.h"
151#include "yuzu/startup_checks.h" 155#include "yuzu/startup_checks.h"
152#include "yuzu/uisettings.h" 156#include "yuzu/uisettings.h"
153#include "yuzu/util/clickable_label.h" 157#include "yuzu/util/clickable_label.h"
@@ -336,6 +340,8 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
336 SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); 340 SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
337 discord_rpc->Update(); 341 discord_rpc->Update();
338 342
343 play_time_manager = std::make_unique<PlayTime::PlayTimeManager>();
344
339 system->GetRoomNetwork().Init(); 345 system->GetRoomNetwork().Init();
340 346
341 RegisterMetaTypes(); 347 RegisterMetaTypes();
@@ -984,7 +990,7 @@ void GMainWindow::InitializeWidgets() {
984 render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system); 990 render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system);
985 render_window->hide(); 991 render_window->hide();
986 992
987 game_list = new GameList(vfs, provider.get(), *system, this); 993 game_list = new GameList(vfs, provider.get(), *play_time_manager, *system, this);
988 ui->horizontalLayout->addWidget(game_list); 994 ui->horizontalLayout->addWidget(game_list);
989 995
990 game_list_placeholder = new GameListPlaceholder(this); 996 game_list_placeholder = new GameListPlaceholder(this);
@@ -1445,6 +1451,7 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
1445 Settings::values.audio_muted = false; 1451 Settings::values.audio_muted = false;
1446 auto_muted = false; 1452 auto_muted = false;
1447 } 1453 }
1454 UpdateVolumeUI();
1448 } 1455 }
1449} 1456}
1450 1457
@@ -1458,6 +1465,8 @@ void GMainWindow::ConnectWidgetEvents() {
1458 connect(game_list, &GameList::RemoveInstalledEntryRequested, this, 1465 connect(game_list, &GameList::RemoveInstalledEntryRequested, this,
1459 &GMainWindow::OnGameListRemoveInstalledEntry); 1466 &GMainWindow::OnGameListRemoveInstalledEntry);
1460 connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); 1467 connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile);
1468 connect(game_list, &GameList::RemovePlayTimeRequested, this,
1469 &GMainWindow::OnGameListRemovePlayTimeData);
1461 connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); 1470 connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
1462 connect(game_list, &GameList::VerifyIntegrityRequested, this, 1471 connect(game_list, &GameList::VerifyIntegrityRequested, this,
1463 &GMainWindow::OnGameListVerifyIntegrity); 1472 &GMainWindow::OnGameListVerifyIntegrity);
@@ -1551,6 +1560,15 @@ void GMainWindow::ConnectMenuEvents() {
1551 // Tools 1560 // Tools
1552 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, 1561 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
1553 ReinitializeKeyBehavior::Warning)); 1562 ReinitializeKeyBehavior::Warning));
1563 connect_menu(ui->action_Load_Album, &GMainWindow::OnAlbum);
1564 connect_menu(ui->action_Load_Cabinet_Nickname_Owner,
1565 [this]() { OnCabinet(Service::NFP::CabinetMode::StartNicknameAndOwnerSettings); });
1566 connect_menu(ui->action_Load_Cabinet_Eraser,
1567 [this]() { OnCabinet(Service::NFP::CabinetMode::StartGameDataEraser); });
1568 connect_menu(ui->action_Load_Cabinet_Restorer,
1569 [this]() { OnCabinet(Service::NFP::CabinetMode::StartRestorer); });
1570 connect_menu(ui->action_Load_Cabinet_Formatter,
1571 [this]() { OnCabinet(Service::NFP::CabinetMode::StartFormatter); });
1554 connect_menu(ui->action_Load_Mii_Edit, &GMainWindow::OnMiiEdit); 1572 connect_menu(ui->action_Load_Mii_Edit, &GMainWindow::OnMiiEdit);
1555 connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot); 1573 connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot);
1556 1574
@@ -1568,6 +1586,7 @@ void GMainWindow::ConnectMenuEvents() {
1568 1586
1569void GMainWindow::UpdateMenuState() { 1587void GMainWindow::UpdateMenuState() {
1570 const bool is_paused = emu_thread == nullptr || !emu_thread->IsRunning(); 1588 const bool is_paused = emu_thread == nullptr || !emu_thread->IsRunning();
1589 const bool is_firmware_available = CheckFirmwarePresence();
1571 1590
1572 const std::array running_actions{ 1591 const std::array running_actions{
1573 ui->action_Stop, 1592 ui->action_Stop,
@@ -1578,10 +1597,23 @@ void GMainWindow::UpdateMenuState() {
1578 ui->action_Pause, 1597 ui->action_Pause,
1579 }; 1598 };
1580 1599
1600 const std::array applet_actions{
1601 ui->action_Load_Album,
1602 ui->action_Load_Cabinet_Nickname_Owner,
1603 ui->action_Load_Cabinet_Eraser,
1604 ui->action_Load_Cabinet_Restorer,
1605 ui->action_Load_Cabinet_Formatter,
1606 ui->action_Load_Mii_Edit,
1607 };
1608
1581 for (QAction* action : running_actions) { 1609 for (QAction* action : running_actions) {
1582 action->setEnabled(emulation_running); 1610 action->setEnabled(emulation_running);
1583 } 1611 }
1584 1612
1613 for (QAction* action : applet_actions) {
1614 action->setEnabled(is_firmware_available && !emulation_running);
1615 }
1616
1585 ui->action_Capture_Screenshot->setEnabled(emulation_running && !is_paused); 1617 ui->action_Capture_Screenshot->setEnabled(emulation_running && !is_paused);
1586 1618
1587 if (emulation_running && is_paused) { 1619 if (emulation_running && is_paused) {
@@ -1591,8 +1623,6 @@ void GMainWindow::UpdateMenuState() {
1591 } 1623 }
1592 1624
1593 multiplayer_state->UpdateNotificationStatus(); 1625 multiplayer_state->UpdateNotificationStatus();
1594
1595 ui->action_Load_Mii_Edit->setEnabled(CheckFirmwarePresence());
1596} 1626}
1597 1627
1598void GMainWindow::OnDisplayTitleBars(bool show) { 1628void GMainWindow::OnDisplayTitleBars(bool show) {
@@ -2103,6 +2133,8 @@ void GMainWindow::OnEmulationStopped() {
2103 OnTasStateChanged(); 2133 OnTasStateChanged();
2104 render_window->FinalizeCamera(); 2134 render_window->FinalizeCamera();
2105 2135
2136 system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::None);
2137
2106 // Enable all controllers 2138 // Enable all controllers
2107 system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All}); 2139 system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
2108 2140
@@ -2511,6 +2543,17 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
2511 } 2543 }
2512} 2544}
2513 2545
2546void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) {
2547 if (QMessageBox::question(this, tr("Remove Play Time Data"), tr("Reset play time?"),
2548 QMessageBox::Yes | QMessageBox::No,
2549 QMessageBox::No) != QMessageBox::Yes) {
2550 return;
2551 }
2552
2553 play_time_manager->ResetProgramPlayTime(program_id);
2554 game_list->PopulateAsync(UISettings::values.game_dirs);
2555}
2556
2514void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) { 2557void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) {
2515 const auto target_file_name = [target] { 2558 const auto target_file_name = [target] {
2516 switch (target) { 2559 switch (target) {
@@ -2802,7 +2845,6 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2802 const QStringList args = QApplication::arguments(); 2845 const QStringList args = QApplication::arguments();
2803 std::filesystem::path yuzu_command = args[0].toStdString(); 2846 std::filesystem::path yuzu_command = args[0].toStdString();
2804 2847
2805#if defined(__linux__) || defined(__FreeBSD__)
2806 // If relative path, make it an absolute path 2848 // If relative path, make it an absolute path
2807 if (yuzu_command.c_str()[0] == '.') { 2849 if (yuzu_command.c_str()[0] == '.') {
2808 yuzu_command = Common::FS::GetCurrentDir() / yuzu_command; 2850 yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
@@ -2825,12 +2867,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2825 UISettings::values.shortcut_already_warned = true; 2867 UISettings::values.shortcut_already_warned = true;
2826 } 2868 }
2827#endif // __linux__ 2869#endif // __linux__
2828#endif // __linux__ || __FreeBSD__
2829 2870
2830 std::filesystem::path target_directory{}; 2871 std::filesystem::path target_directory{};
2831 // Determine target directory for shortcut 2872 // Determine target directory for shortcut
2832#if defined(__linux__) || defined(__FreeBSD__) 2873#if defined(WIN32)
2874 const char* home = std::getenv("USERPROFILE");
2875#else
2833 const char* home = std::getenv("HOME"); 2876 const char* home = std::getenv("HOME");
2877#endif
2834 const std::filesystem::path home_path = (home == nullptr ? "~" : home); 2878 const std::filesystem::path home_path = (home == nullptr ? "~" : home);
2835 const char* xdg_data_home = std::getenv("XDG_DATA_HOME"); 2879 const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
2836 2880
@@ -2840,7 +2884,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2840 QMessageBox::critical( 2884 QMessageBox::critical(
2841 this, tr("Create Shortcut"), 2885 this, tr("Create Shortcut"),
2842 tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.") 2886 tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
2843 .arg(QString::fromStdString(target_directory)), 2887 .arg(QString::fromStdString(target_directory.generic_string())),
2844 QMessageBox::StandardButton::Ok); 2888 QMessageBox::StandardButton::Ok);
2845 return; 2889 return;
2846 } 2890 }
@@ -2848,15 +2892,15 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2848 target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) / 2892 target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
2849 "applications"; 2893 "applications";
2850 if (!Common::FS::CreateDirs(target_directory)) { 2894 if (!Common::FS::CreateDirs(target_directory)) {
2851 QMessageBox::critical(this, tr("Create Shortcut"), 2895 QMessageBox::critical(
2852 tr("Cannot create shortcut in applications menu. Path \"%1\" " 2896 this, tr("Create Shortcut"),
2853 "does not exist and cannot be created.") 2897 tr("Cannot create shortcut in applications menu. Path \"%1\" "
2854 .arg(QString::fromStdString(target_directory)), 2898 "does not exist and cannot be created.")
2855 QMessageBox::StandardButton::Ok); 2899 .arg(QString::fromStdString(target_directory.generic_string())),
2900 QMessageBox::StandardButton::Ok);
2856 return; 2901 return;
2857 } 2902 }
2858 } 2903 }
2859#endif
2860 2904
2861 const std::string game_file_name = std::filesystem::path(game_path).filename().string(); 2905 const std::string game_file_name = std::filesystem::path(game_path).filename().string();
2862 // Determine full paths for icon and shortcut 2906 // Determine full paths for icon and shortcut
@@ -2878,9 +2922,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2878 const std::filesystem::path shortcut_path = 2922 const std::filesystem::path shortcut_path =
2879 target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name) 2923 target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
2880 : fmt::format("yuzu-{:016X}.desktop", program_id)); 2924 : fmt::format("yuzu-{:016X}.desktop", program_id));
2925#elif defined(WIN32)
2926 std::filesystem::path icons_path =
2927 Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir);
2928 std::filesystem::path icon_path =
2929 icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name)
2930 : fmt::format("yuzu-{:016X}.ico", program_id)));
2881#else 2931#else
2882 const std::filesystem::path icon_path{}; 2932 std::string icon_extension;
2883 const std::filesystem::path shortcut_path{};
2884#endif 2933#endif
2885 2934
2886 // Get title from game file 2935 // Get title from game file
@@ -2905,29 +2954,37 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
2905 LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path); 2954 LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
2906 } 2955 }
2907 2956
2908 QImage icon_jpeg = 2957 QImage icon_data =
2909 QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size())); 2958 QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
2910#if defined(__linux__) || defined(__FreeBSD__) 2959#if defined(__linux__) || defined(__FreeBSD__)
2911 // Convert and write the icon as a PNG 2960 // Convert and write the icon as a PNG
2912 if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) { 2961 if (!icon_data.save(QString::fromStdString(icon_path.string()))) {
2913 LOG_ERROR(Frontend, "Could not write icon as PNG to file"); 2962 LOG_ERROR(Frontend, "Could not write icon as PNG to file");
2914 } else { 2963 } else {
2915 LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string()); 2964 LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
2916 } 2965 }
2966#elif defined(WIN32)
2967 if (!SaveIconToFile(icon_path.string(), icon_data)) {
2968 LOG_ERROR(Frontend, "Could not write icon to file");
2969 return;
2970 }
2917#endif // __linux__ 2971#endif // __linux__
2918 2972
2919#if defined(__linux__) || defined(__FreeBSD__) 2973#ifdef _WIN32
2974 // Replace characters that are illegal in Windows filenames by a dash
2975 const std::string illegal_chars = "<>:\"/\\|?*";
2976 for (char c : illegal_chars) {
2977 std::replace(title.begin(), title.end(), c, '_');
2978 }
2979 const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str();
2980#endif
2981
2920 const std::string comment = 2982 const std::string comment =
2921 tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString(); 2983 tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
2922 const std::string arguments = fmt::format("-g \"{:s}\"", game_path); 2984 const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
2923 const std::string categories = "Game;Emulator;Qt;"; 2985 const std::string categories = "Game;Emulator;Qt;";
2924 const std::string keywords = "Switch;Nintendo;"; 2986 const std::string keywords = "Switch;Nintendo;";
2925#else 2987
2926 const std::string comment{};
2927 const std::string arguments{};
2928 const std::string categories{};
2929 const std::string keywords{};
2930#endif
2931 if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(), 2988 if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
2932 yuzu_command.string(), arguments, categories, keywords)) { 2989 yuzu_command.string(), arguments, categories, keywords)) {
2933 QMessageBox::critical(this, tr("Create Shortcut"), 2990 QMessageBox::critical(this, tr("Create Shortcut"),
@@ -3334,6 +3391,9 @@ void GMainWindow::OnStartGame() {
3334 UpdateMenuState(); 3391 UpdateMenuState();
3335 OnTasStateChanged(); 3392 OnTasStateChanged();
3336 3393
3394 play_time_manager->SetProgramId(system->GetApplicationProcessProgramID());
3395 play_time_manager->Start();
3396
3337 discord_rpc->Update(); 3397 discord_rpc->Update();
3338} 3398}
3339 3399
@@ -3349,6 +3409,7 @@ void GMainWindow::OnRestartGame() {
3349 3409
3350void GMainWindow::OnPauseGame() { 3410void GMainWindow::OnPauseGame() {
3351 emu_thread->SetRunning(false); 3411 emu_thread->SetRunning(false);
3412 play_time_manager->Stop();
3352 UpdateMenuState(); 3413 UpdateMenuState();
3353 AllowOSSleep(); 3414 AllowOSSleep();
3354} 3415}
@@ -3369,6 +3430,9 @@ void GMainWindow::OnStopGame() {
3369 return; 3430 return;
3370 } 3431 }
3371 3432
3433 play_time_manager->Stop();
3434 // Update game list to show new play time
3435 game_list->PopulateAsync(UISettings::values.game_dirs);
3372 if (OnShutdownBegin()) { 3436 if (OnShutdownBegin()) {
3373 OnShutdownBeginDialog(); 3437 OnShutdownBeginDialog();
3374 } else { 3438 } else {
@@ -3942,6 +4006,34 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
3942 shortcut_stream.close(); 4006 shortcut_stream.close();
3943 4007
3944 return true; 4008 return true;
4009#elif defined(WIN32)
4010 IShellLinkW* shell_link;
4011 auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
4012 (void**)&shell_link);
4013 if (FAILED(hres)) {
4014 return false;
4015 }
4016 shell_link->SetPath(
4017 Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to
4018 shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data());
4019 shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data());
4020 shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0);
4021
4022 IPersistFile* persist_file;
4023 hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file);
4024 if (FAILED(hres)) {
4025 return false;
4026 }
4027
4028 hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE);
4029 if (FAILED(hres)) {
4030 return false;
4031 }
4032
4033 persist_file->Release();
4034 shell_link->Release();
4035
4036 return true;
3945#endif 4037#endif
3946 return false; 4038 return false;
3947} 4039}
@@ -4134,6 +4226,53 @@ void GMainWindow::OnToggleStatusBar() {
4134 statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked()); 4226 statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
4135} 4227}
4136 4228
4229void GMainWindow::OnAlbum() {
4230 constexpr u64 AlbumId = 0x010000000000100Dull;
4231 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4232 if (!bis_system) {
4233 QMessageBox::warning(this, tr("No firmware available"),
4234 tr("Please install the firmware to use the Album applet."));
4235 return;
4236 }
4237
4238 auto album_nca = bis_system->GetEntry(AlbumId, FileSys::ContentRecordType::Program);
4239 if (!album_nca) {
4240 QMessageBox::warning(this, tr("Album Applet"),
4241 tr("Album applet is not available. Please reinstall firmware."));
4242 return;
4243 }
4244
4245 system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::PhotoViewer);
4246
4247 const auto filename = QString::fromStdString(album_nca->GetFullPath());
4248 UISettings::values.roms_path = QFileInfo(filename).path();
4249 BootGame(filename);
4250}
4251
4252void GMainWindow::OnCabinet(Service::NFP::CabinetMode mode) {
4253 constexpr u64 CabinetId = 0x0100000000001002ull;
4254 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
4255 if (!bis_system) {
4256 QMessageBox::warning(this, tr("No firmware available"),
4257 tr("Please install the firmware to use the Cabinet applet."));
4258 return;
4259 }
4260
4261 auto cabinet_nca = bis_system->GetEntry(CabinetId, FileSys::ContentRecordType::Program);
4262 if (!cabinet_nca) {
4263 QMessageBox::warning(this, tr("Cabinet Applet"),
4264 tr("Cabinet applet is not available. Please reinstall firmware."));
4265 return;
4266 }
4267
4268 system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::Cabinet);
4269 system->GetAppletManager().SetCabinetMode(mode);
4270
4271 const auto filename = QString::fromStdString(cabinet_nca->GetFullPath());
4272 UISettings::values.roms_path = QFileInfo(filename).path();
4273 BootGame(filename);
4274}
4275
4137void GMainWindow::OnMiiEdit() { 4276void GMainWindow::OnMiiEdit() {
4138 constexpr u64 MiiEditId = 0x0100000000001009ull; 4277 constexpr u64 MiiEditId = 0x0100000000001009ull;
4139 auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); 4278 auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
@@ -4150,6 +4289,8 @@ void GMainWindow::OnMiiEdit() {
4150 return; 4289 return;
4151 } 4290 }
4152 4291
4292 system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::MiiEdit);
4293
4153 const auto filename = QString::fromStdString((mii_applet_nca->GetFullPath())); 4294 const auto filename = QString::fromStdString((mii_applet_nca->GetFullPath()));
4154 UISettings::values.roms_path = QFileInfo(filename).path(); 4295 UISettings::values.roms_path = QFileInfo(filename).path();
4155 BootGame(filename); 4296 BootGame(filename);
@@ -4602,8 +4743,8 @@ bool GMainWindow::CheckFirmwarePresence() {
4602 4743
4603bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id, 4744bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
4604 u64* selected_title_id, u8* selected_content_record_type) { 4745 u64* selected_title_id, u8* selected_content_record_type) {
4605 using ContentInfo = std::pair<FileSys::TitleType, FileSys::ContentRecordType>; 4746 using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>;
4606 boost::container::flat_map<u64, ContentInfo> available_title_ids; 4747 boost::container::flat_set<ContentInfo> available_title_ids;
4607 4748
4608 const auto RetrieveEntries = [&](FileSys::TitleType title_type, 4749 const auto RetrieveEntries = [&](FileSys::TitleType title_type,
4609 FileSys::ContentRecordType record_type) { 4750 FileSys::ContentRecordType record_type) {
@@ -4611,12 +4752,14 @@ bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installe
4611 for (const auto& entry : entries) { 4752 for (const auto& entry : entries) {
4612 if (FileSys::GetBaseTitleID(entry.title_id) == program_id && 4753 if (FileSys::GetBaseTitleID(entry.title_id) == program_id &&
4613 installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success) { 4754 installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success) {
4614 available_title_ids[entry.title_id] = {title_type, record_type}; 4755 available_title_ids.insert({entry.title_id, title_type, record_type});
4615 } 4756 }
4616 } 4757 }
4617 }; 4758 };
4618 4759
4619 RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::Program); 4760 RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
4761 RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::HtmlDocument);
4762 RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::LegalInformation);
4620 RetrieveEntries(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); 4763 RetrieveEntries(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
4621 4764
4622 if (available_title_ids.empty()) { 4765 if (available_title_ids.empty()) {
@@ -4627,10 +4770,14 @@ bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installe
4627 4770
4628 if (available_title_ids.size() > 1) { 4771 if (available_title_ids.size() > 1) {
4629 QStringList list; 4772 QStringList list;
4630 for (auto& [title_id, content_info] : available_title_ids) { 4773 for (auto& [title_id, title_type, record_type] : available_title_ids) {
4631 const auto hex_title_id = QString::fromStdString(fmt::format("{:X}", title_id)); 4774 const auto hex_title_id = QString::fromStdString(fmt::format("{:X}", title_id));
4632 if (content_info.first == FileSys::TitleType::Application) { 4775 if (record_type == FileSys::ContentRecordType::Program) {
4633 list.push_back(QStringLiteral("Application [%1]").arg(hex_title_id)); 4776 list.push_back(QStringLiteral("Program [%1]").arg(hex_title_id));
4777 } else if (record_type == FileSys::ContentRecordType::HtmlDocument) {
4778 list.push_back(QStringLiteral("HTML document [%1]").arg(hex_title_id));
4779 } else if (record_type == FileSys::ContentRecordType::LegalInformation) {
4780 list.push_back(QStringLiteral("Legal information [%1]").arg(hex_title_id));
4634 } else { 4781 } else {
4635 list.push_back( 4782 list.push_back(
4636 QStringLiteral("DLC %1 [%2]").arg(title_id & 0x7FF).arg(hex_title_id)); 4783 QStringLiteral("DLC %1 [%2]").arg(title_id & 0x7FF).arg(hex_title_id));
@@ -4648,9 +4795,9 @@ bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installe
4648 title_index = list.indexOf(res); 4795 title_index = list.indexOf(res);
4649 } 4796 }
4650 4797
4651 const auto selected_info = available_title_ids.nth(title_index); 4798 const auto& [title_id, title_type, record_type] = *available_title_ids.nth(title_index);
4652 *selected_title_id = selected_info->first; 4799 *selected_title_id = title_id;
4653 *selected_content_record_type = static_cast<u8>(selected_info->second.second); 4800 *selected_content_record_type = static_cast<u8>(record_type);
4654 return true; 4801 return true;
4655} 4802}
4656 4803
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index ba318eb11..2346eb3bd 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -81,6 +81,10 @@ namespace DiscordRPC {
81class DiscordInterface; 81class DiscordInterface;
82} 82}
83 83
84namespace PlayTime {
85class PlayTimeManager;
86}
87
84namespace FileSys { 88namespace FileSys {
85class ContentProvider; 89class ContentProvider;
86class ManualContentProvider; 90class ManualContentProvider;
@@ -102,6 +106,10 @@ namespace Service::NFC {
102class NfcDevice; 106class NfcDevice;
103} // namespace Service::NFC 107} // namespace Service::NFC
104 108
109namespace Service::NFP {
110enum class CabinetMode : u8;
111} // namespace Service::NFP
112
105namespace Ui { 113namespace Ui {
106class MainWindow; 114class MainWindow;
107} 115}
@@ -319,6 +327,7 @@ private slots:
319 void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type); 327 void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type);
320 void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, 328 void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
321 const std::string& game_path); 329 const std::string& game_path);
330 void OnGameListRemovePlayTimeData(u64 program_id);
322 void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); 331 void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
323 void OnGameListVerifyIntegrity(const std::string& game_path); 332 void OnGameListVerifyIntegrity(const std::string& game_path);
324 void OnGameListCopyTID(u64 program_id); 333 void OnGameListCopyTID(u64 program_id);
@@ -365,6 +374,8 @@ private slots:
365 void ResetWindowSize720(); 374 void ResetWindowSize720();
366 void ResetWindowSize900(); 375 void ResetWindowSize900();
367 void ResetWindowSize1080(); 376 void ResetWindowSize1080();
377 void OnAlbum();
378 void OnCabinet(Service::NFP::CabinetMode mode);
368 void OnMiiEdit(); 379 void OnMiiEdit();
369 void OnCaptureScreenshot(); 380 void OnCaptureScreenshot();
370 void OnReinitializeKeys(ReinitializeKeyBehavior behavior); 381 void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
@@ -384,6 +395,7 @@ private:
384 void RemoveVulkanDriverPipelineCache(u64 program_id); 395 void RemoveVulkanDriverPipelineCache(u64 program_id);
385 void RemoveAllTransferableShaderCaches(u64 program_id); 396 void RemoveAllTransferableShaderCaches(u64 program_id);
386 void RemoveCustomConfiguration(u64 program_id, const std::string& game_path); 397 void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
398 void RemovePlayTimeData(u64 program_id);
387 void RemoveCacheStorage(u64 program_id); 399 void RemoveCacheStorage(u64 program_id);
388 bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id, 400 bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
389 u64* selected_title_id, u8* selected_content_record_type); 401 u64* selected_title_id, u8* selected_content_record_type);
@@ -423,6 +435,7 @@ private:
423 435
424 std::unique_ptr<Core::System> system; 436 std::unique_ptr<Core::System> system;
425 std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; 437 std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
438 std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager;
426 std::shared_ptr<InputCommon::InputSubsystem> input_subsystem; 439 std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
427 440
428 MultiplayerState* multiplayer_state = nullptr; 441 MultiplayerState* multiplayer_state = nullptr;
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 91d6c5ef3..88684ffb5 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -137,6 +137,15 @@
137 <property name="title"> 137 <property name="title">
138 <string>&amp;Tools</string> 138 <string>&amp;Tools</string>
139 </property> 139 </property>
140 <widget class="QMenu" name="menu_cabinet_applet">
141 <property name="title">
142 <string>&amp;Amiibo</string>
143 </property>
144 <addaction name="action_Load_Cabinet_Nickname_Owner"/>
145 <addaction name="action_Load_Cabinet_Eraser"/>
146 <addaction name="action_Load_Cabinet_Restorer"/>
147 <addaction name="action_Load_Cabinet_Formatter"/>
148 </widget>
140 <widget class="QMenu" name="menuTAS"> 149 <widget class="QMenu" name="menuTAS">
141 <property name="title"> 150 <property name="title">
142 <string>&amp;TAS</string> 151 <string>&amp;TAS</string>
@@ -150,6 +159,8 @@
150 <addaction name="action_Rederive"/> 159 <addaction name="action_Rederive"/>
151 <addaction name="action_Verify_installed_contents"/> 160 <addaction name="action_Verify_installed_contents"/>
152 <addaction name="separator"/> 161 <addaction name="separator"/>
162 <addaction name="menu_cabinet_applet"/>
163 <addaction name="action_Load_Album"/>
153 <addaction name="action_Load_Mii_Edit"/> 164 <addaction name="action_Load_Mii_Edit"/>
154 <addaction name="separator"/> 165 <addaction name="separator"/>
155 <addaction name="action_Capture_Screenshot"/> 166 <addaction name="action_Capture_Screenshot"/>
@@ -370,6 +381,31 @@
370 <string>&amp;Capture Screenshot</string> 381 <string>&amp;Capture Screenshot</string>
371 </property> 382 </property>
372 </action> 383 </action>
384 <action name="action_Load_Album">
385 <property name="text">
386 <string>Open &amp;Album</string>
387 </property>
388 </action>
389 <action name="action_Load_Cabinet_Nickname_Owner">
390 <property name="text">
391 <string>&amp;Set Nickname and Owner</string>
392 </property>
393 </action>
394 <action name="action_Load_Cabinet_Eraser">
395 <property name="text">
396 <string>&amp;Delete Game Data</string>
397 </property>
398 </action>
399 <action name="action_Load_Cabinet_Restorer">
400 <property name="text">
401 <string>&amp;Restore Amiibo</string>
402 </property>
403 </action>
404 <action name="action_Load_Cabinet_Formatter">
405 <property name="text">
406 <string>&amp;Format Amiibo</string>
407 </property>
408 </action>
373 <action name="action_Load_Mii_Edit"> 409 <action name="action_Load_Mii_Edit">
374 <property name="text"> 410 <property name="text">
375 <string>Open &amp;Mii Editor</string> 411 <string>Open &amp;Mii Editor</string>
diff --git a/src/yuzu/play_time_manager.cpp b/src/yuzu/play_time_manager.cpp
new file mode 100644
index 000000000..155c36b7d
--- /dev/null
+++ b/src/yuzu/play_time_manager.cpp
@@ -0,0 +1,179 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/alignment.h"
5#include "common/fs/file.h"
6#include "common/fs/fs.h"
7#include "common/fs/path_util.h"
8#include "common/logging/log.h"
9#include "common/settings.h"
10#include "common/thread.h"
11#include "core/hle/service/acc/profile_manager.h"
12#include "yuzu/play_time_manager.h"
13
14namespace PlayTime {
15
16namespace {
17
18struct PlayTimeElement {
19 ProgramId program_id;
20 PlayTime play_time;
21};
22
23std::optional<std::filesystem::path> GetCurrentUserPlayTimePath() {
24 const Service::Account::ProfileManager manager;
25 const auto uuid = manager.GetUser(static_cast<s32>(Settings::values.current_user));
26 if (!uuid.has_value()) {
27 return std::nullopt;
28 }
29 return Common::FS::GetYuzuPath(Common::FS::YuzuPath::PlayTimeDir) /
30 uuid->RawString().append(".bin");
31}
32
33[[nodiscard]] bool ReadPlayTimeFile(PlayTimeDatabase& out_play_time_db) {
34 const auto filename = GetCurrentUserPlayTimePath();
35
36 if (!filename.has_value()) {
37 LOG_ERROR(Frontend, "Failed to get current user path");
38 return false;
39 }
40
41 out_play_time_db.clear();
42
43 if (Common::FS::Exists(filename.value())) {
44 Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Read,
45 Common::FS::FileType::BinaryFile};
46 if (!file.IsOpen()) {
47 LOG_ERROR(Frontend, "Failed to open play time file: {}",
48 Common::FS::PathToUTF8String(filename.value()));
49 return false;
50 }
51
52 const size_t num_elements = file.GetSize() / sizeof(PlayTimeElement);
53 std::vector<PlayTimeElement> elements(num_elements);
54
55 if (file.ReadSpan<PlayTimeElement>(elements) != num_elements) {
56 return false;
57 }
58
59 for (const auto& [program_id, play_time] : elements) {
60 if (program_id != 0) {
61 out_play_time_db[program_id] = play_time;
62 }
63 }
64 }
65
66 return true;
67}
68
69[[nodiscard]] bool WritePlayTimeFile(const PlayTimeDatabase& play_time_db) {
70 const auto filename = GetCurrentUserPlayTimePath();
71
72 if (!filename.has_value()) {
73 LOG_ERROR(Frontend, "Failed to get current user path");
74 return false;
75 }
76
77 Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Write,
78 Common::FS::FileType::BinaryFile};
79 if (!file.IsOpen()) {
80 LOG_ERROR(Frontend, "Failed to open play time file: {}",
81 Common::FS::PathToUTF8String(filename.value()));
82 return false;
83 }
84
85 std::vector<PlayTimeElement> elements;
86 elements.reserve(play_time_db.size());
87
88 for (auto& [program_id, play_time] : play_time_db) {
89 if (program_id != 0) {
90 elements.push_back(PlayTimeElement{program_id, play_time});
91 }
92 }
93
94 return file.WriteSpan<PlayTimeElement>(elements) == elements.size();
95}
96
97} // namespace
98
99PlayTimeManager::PlayTimeManager() {
100 if (!ReadPlayTimeFile(database)) {
101 LOG_ERROR(Frontend, "Failed to read play time database! Resetting to default.");
102 }
103}
104
105PlayTimeManager::~PlayTimeManager() {
106 Save();
107}
108
109void PlayTimeManager::SetProgramId(u64 program_id) {
110 running_program_id = program_id;
111}
112
113void PlayTimeManager::Start() {
114 play_time_thread = std::jthread([&](std::stop_token stop_token) { AutoTimestamp(stop_token); });
115}
116
117void PlayTimeManager::Stop() {
118 play_time_thread = {};
119}
120
121void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) {
122 Common::SetCurrentThreadName("PlayTimeReport");
123
124 using namespace std::literals::chrono_literals;
125 using std::chrono::seconds;
126 using std::chrono::steady_clock;
127
128 auto timestamp = steady_clock::now();
129
130 const auto GetDuration = [&]() -> u64 {
131 const auto last_timestamp = std::exchange(timestamp, steady_clock::now());
132 const auto duration = std::chrono::duration_cast<seconds>(timestamp - last_timestamp);
133 return static_cast<u64>(duration.count());
134 };
135
136 while (!stop_token.stop_requested()) {
137 Common::StoppableTimedWait(stop_token, 30s);
138
139 database[running_program_id] += GetDuration();
140 Save();
141 }
142}
143
144void PlayTimeManager::Save() {
145 if (!WritePlayTimeFile(database)) {
146 LOG_ERROR(Frontend, "Failed to update play time database!");
147 }
148}
149
150u64 PlayTimeManager::GetPlayTime(u64 program_id) const {
151 auto it = database.find(program_id);
152 if (it != database.end()) {
153 return it->second;
154 } else {
155 return 0;
156 }
157}
158
159void PlayTimeManager::ResetProgramPlayTime(u64 program_id) {
160 database.erase(program_id);
161 Save();
162}
163
164QString ReadablePlayTime(qulonglong time_seconds) {
165 if (time_seconds == 0) {
166 return {};
167 }
168 const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0);
169 const auto time_hours = static_cast<double>(time_seconds) / 3600;
170 const bool is_minutes = time_minutes < 60;
171 const char* unit = is_minutes ? "m" : "h";
172 const auto value = is_minutes ? time_minutes : time_hours;
173
174 return QStringLiteral("%L1 %2")
175 .arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0)
176 .arg(QString::fromUtf8(unit));
177}
178
179} // namespace PlayTime
diff --git a/src/yuzu/play_time_manager.h b/src/yuzu/play_time_manager.h
new file mode 100644
index 000000000..5f96f3447
--- /dev/null
+++ b/src/yuzu/play_time_manager.h
@@ -0,0 +1,44 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <QString>
7
8#include <map>
9
10#include "common/common_funcs.h"
11#include "common/common_types.h"
12#include "common/polyfill_thread.h"
13
14namespace PlayTime {
15
16using ProgramId = u64;
17using PlayTime = u64;
18using PlayTimeDatabase = std::map<ProgramId, PlayTime>;
19
20class PlayTimeManager {
21public:
22 explicit PlayTimeManager();
23 ~PlayTimeManager();
24
25 YUZU_NON_COPYABLE(PlayTimeManager);
26 YUZU_NON_MOVEABLE(PlayTimeManager);
27
28 u64 GetPlayTime(u64 program_id) const;
29 void ResetProgramPlayTime(u64 program_id);
30 void SetProgramId(u64 program_id);
31 void Start();
32 void Stop();
33
34private:
35 PlayTimeDatabase database;
36 u64 running_program_id;
37 std::jthread play_time_thread;
38 void AutoTimestamp(std::stop_token stop_token);
39 void Save();
40};
41
42QString ReadablePlayTime(qulonglong time_seconds);
43
44} // namespace PlayTime
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 8efd63f31..975008159 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -103,7 +103,7 @@ struct Values {
103 true, 103 true,
104 true}; 104 true};
105 Setting<bool> mute_when_in_background{ 105 Setting<bool> mute_when_in_background{
106 linkage, false, "muteWhenInBackground", Category::Ui, Settings::Specialization::Default, 106 linkage, false, "muteWhenInBackground", Category::Audio, Settings::Specialization::Default,
107 true, true}; 107 true, true};
108 Setting<bool> hide_mouse{ 108 Setting<bool> hide_mouse{
109 linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default, 109 linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
@@ -183,6 +183,9 @@ struct Values {
183 Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList}; 183 Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList};
184 Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList}; 184 Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList};
185 185
186 // Play time
187 Setting<bool> show_play_time{linkage, true, "show_play_time", Category::UiGameList};
188
186 bool configuration_applied; 189 bool configuration_applied;
187 bool reset_to_defaults; 190 bool reset_to_defaults;
188 bool shortcut_already_warned{false}; 191 bool shortcut_already_warned{false};
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index 5c3e4589e..f2854c8ec 100644
--- a/src/yuzu/util/util.cpp
+++ b/src/yuzu/util/util.cpp
@@ -5,6 +5,10 @@
5#include <cmath> 5#include <cmath>
6#include <QPainter> 6#include <QPainter>
7#include "yuzu/util/util.h" 7#include "yuzu/util/util.h"
8#ifdef _WIN32
9#include <windows.h>
10#include "common/fs/file.h"
11#endif
8 12
9QFont GetMonospaceFont() { 13QFont GetMonospaceFont() {
10 QFont font(QStringLiteral("monospace")); 14 QFont font(QStringLiteral("monospace"));
@@ -37,3 +41,101 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) {
37 painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0); 41 painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0);
38 return circle_pixmap; 42 return circle_pixmap;
39} 43}
44
45bool SaveIconToFile(const std::string_view path, const QImage& image) {
46#if defined(WIN32)
47#pragma pack(push, 2)
48 struct IconDir {
49 WORD id_reserved;
50 WORD id_type;
51 WORD id_count;
52 };
53
54 struct IconDirEntry {
55 BYTE width;
56 BYTE height;
57 BYTE color_count;
58 BYTE reserved;
59 WORD planes;
60 WORD bit_count;
61 DWORD bytes_in_res;
62 DWORD image_offset;
63 };
64#pragma pack(pop)
65
66 const QImage source_image = image.convertToFormat(QImage::Format_RGB32);
67 constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16};
68 constexpr int bytes_per_pixel = 4;
69
70 const IconDir icon_dir{
71 .id_reserved = 0,
72 .id_type = 1,
73 .id_count = static_cast<WORD>(scale_sizes.size()),
74 };
75
76 Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write,
77 Common::FS::FileType::BinaryFile);
78 if (!icon_file.IsOpen()) {
79 return false;
80 }
81
82 if (!icon_file.Write(icon_dir)) {
83 return false;
84 }
85
86 std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size());
87 for (std::size_t i = 0; i < scale_sizes.size(); i++) {
88 const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel;
89 const IconDirEntry icon_entry{
90 .width = static_cast<BYTE>(scale_sizes[i]),
91 .height = static_cast<BYTE>(scale_sizes[i]),
92 .color_count = 0,
93 .reserved = 0,
94 .planes = 1,
95 .bit_count = bytes_per_pixel * 8,
96 .bytes_in_res = static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
97 .image_offset = static_cast<DWORD>(image_offset),
98 };
99 image_offset += icon_entry.bytes_in_res;
100 if (!icon_file.Write(icon_entry)) {
101 return false;
102 }
103 }
104
105 for (std::size_t i = 0; i < scale_sizes.size(); i++) {
106 const QImage scaled_image = source_image.scaled(
107 scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
108 const BITMAPINFOHEADER info_header{
109 .biSize = sizeof(BITMAPINFOHEADER),
110 .biWidth = scaled_image.width(),
111 .biHeight = scaled_image.height() * 2,
112 .biPlanes = 1,
113 .biBitCount = bytes_per_pixel * 8,
114 .biCompression = BI_RGB,
115 .biSizeImage{},
116 .biXPelsPerMeter{},
117 .biYPelsPerMeter{},
118 .biClrUsed{},
119 .biClrImportant{},
120 };
121
122 if (!icon_file.Write(info_header)) {
123 return false;
124 }
125
126 for (int y = 0; y < scaled_image.height(); y++) {
127 const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y);
128 std::vector<u8> line_data(scaled_image.width() * bytes_per_pixel);
129 std::memcpy(line_data.data(), line, line_data.size());
130 if (!icon_file.Write(line_data)) {
131 return false;
132 }
133 }
134 }
135 icon_file.Close();
136
137 return true;
138#else
139 return false;
140#endif
141}
diff --git a/src/yuzu/util/util.h b/src/yuzu/util/util.h
index 39dd2d895..09c14ce3f 100644
--- a/src/yuzu/util/util.h
+++ b/src/yuzu/util/util.h
@@ -7,14 +7,22 @@
7#include <QString> 7#include <QString>
8 8
9/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc. 9/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
10QFont GetMonospaceFont(); 10[[nodiscard]] QFont GetMonospaceFont();
11 11
12/// Convert a size in bytes into a readable format (KiB, MiB, etc.) 12/// Convert a size in bytes into a readable format (KiB, MiB, etc.)
13QString ReadableByteSize(qulonglong size); 13[[nodiscard]] QString ReadableByteSize(qulonglong size);
14 14
15/** 15/**
16 * Creates a circle pixmap from a specified color 16 * Creates a circle pixmap from a specified color
17 * @param color The color the pixmap shall have 17 * @param color The color the pixmap shall have
18 * @return QPixmap circle pixmap 18 * @return QPixmap circle pixmap
19 */ 19 */
20QPixmap CreateCirclePixmapFromColor(const QColor& color); 20[[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color);
21
22/**
23 * Saves a windows icon to a file
24 * @param path The icons path
25 * @param image The image to save
26 * @return bool If the operation succeeded
27 */
28[[nodiscard]] bool SaveIconToFile(const std::string_view path, const QImage& image);