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/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/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/core_timing.cpp79
-rw-r--r--src/core/core_timing.h12
-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/hid/emulated_controller.cpp11
-rw-r--r--src/core/hid/emulated_controller.h2
-rw-r--r--src/core/hle/kernel/k_hardware_timer.cpp4
-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/svc/svc_transfer_memory.cpp54
-rw-r--r--src/core/hle/service/am/am.cpp275
-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.cpp342
-rw-r--r--src/core/hle/service/caps/caps_manager.h72
-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.cpp104
-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/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/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/query_cache/bank_base.h1
-rw-r--r--src/video_core/renderer_base.h3
-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_query_cache.cpp7
-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.cpp4
-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/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.cpp21
-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.cpp77
-rw-r--r--src/yuzu/util/util.h14
128 files changed, 3437 insertions, 552 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:
return;
}
m_window->OnSurfaceChanged(m_native_window);
- m_system.Renderer().NotifySurfaceChanged();
}
void ConfigureFilesystemProvider(const std::string& filepath) {
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 {
/**
* AudioRenderer command for preparing depop.
- * Adds the previusly output last samples to the depop buffer.
+ * Adds the previously output last samples to the depop buffer.
*/
struct DepopPrepareCommand : ICommand {
/**
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)
target_link_libraries(common PRIVATE xbyak::xbyak)
endif()
+if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX))
+ target_sources(common
+ PRIVATE
+ arm64/native_clock.cpp
+ arm64/native_clock.h
+ )
+endif()
+
if (MSVC)
target_compile_definitions(common PRIVATE
# 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/arm64/native_clock.h"
+
+namespace Common::Arm64 {
+
+namespace {
+
+NativeClock::FactorType GetFixedPointFactor(u64 num, u64 den) {
+ return (static_cast<NativeClock::FactorType>(num) << 64) / den;
+}
+
+u64 MultiplyHigh(u64 m, NativeClock::FactorType factor) {
+ return static_cast<u64>((m * factor) >> 64);
+}
+
+} // namespace
+
+NativeClock::NativeClock() {
+ const u64 host_cntfrq = GetHostCNTFRQ();
+ ns_cntfrq_factor = GetFixedPointFactor(NsRatio::den, host_cntfrq);
+ us_cntfrq_factor = GetFixedPointFactor(UsRatio::den, host_cntfrq);
+ ms_cntfrq_factor = GetFixedPointFactor(MsRatio::den, host_cntfrq);
+ guest_cntfrq_factor = GetFixedPointFactor(CNTFRQ, host_cntfrq);
+ gputick_cntfrq_factor = GetFixedPointFactor(GPUTickFreq, host_cntfrq);
+}
+
+std::chrono::nanoseconds NativeClock::GetTimeNS() const {
+ return std::chrono::nanoseconds{MultiplyHigh(GetHostTicksElapsed(), ns_cntfrq_factor)};
+}
+
+std::chrono::microseconds NativeClock::GetTimeUS() const {
+ return std::chrono::microseconds{MultiplyHigh(GetHostTicksElapsed(), us_cntfrq_factor)};
+}
+
+std::chrono::milliseconds NativeClock::GetTimeMS() const {
+ return std::chrono::milliseconds{MultiplyHigh(GetHostTicksElapsed(), ms_cntfrq_factor)};
+}
+
+u64 NativeClock::GetCNTPCT() const {
+ return MultiplyHigh(GetHostTicksElapsed(), guest_cntfrq_factor);
+}
+
+u64 NativeClock::GetGPUTick() const {
+ return MultiplyHigh(GetHostTicksElapsed(), gputick_cntfrq_factor);
+}
+
+u64 NativeClock::GetHostTicksNow() const {
+ u64 cntvct_el0 = 0;
+ asm volatile("dsb ish\n\t"
+ "mrs %[cntvct_el0], cntvct_el0\n\t"
+ "dsb ish\n\t"
+ : [cntvct_el0] "=r"(cntvct_el0));
+ return cntvct_el0;
+}
+
+u64 NativeClock::GetHostTicksElapsed() const {
+ return GetHostTicksNow();
+}
+
+bool NativeClock::IsNative() const {
+ return true;
+}
+
+u64 NativeClock::GetHostCNTFRQ() {
+ u64 cntfrq_el0 = 0;
+ asm("mrs %[cntfrq_el0], cntfrq_el0" : [cntfrq_el0] "=r"(cntfrq_el0));
+ return cntfrq_el0;
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/wall_clock.h"
+
+namespace Common::Arm64 {
+
+class NativeClock final : public WallClock {
+public:
+ explicit NativeClock();
+
+ std::chrono::nanoseconds GetTimeNS() const override;
+
+ std::chrono::microseconds GetTimeUS() const override;
+
+ std::chrono::milliseconds GetTimeMS() const override;
+
+ u64 GetCNTPCT() const override;
+
+ u64 GetGPUTick() const override;
+
+ u64 GetHostTicksNow() const override;
+
+ u64 GetHostTicksElapsed() const override;
+
+ bool IsNative() const override;
+
+ static u64 GetHostCNTFRQ();
+
+public:
+ using FactorType = unsigned __int128;
+
+ FactorType GetGuestCNTFRQFactor() const {
+ return guest_cntfrq_factor;
+ }
+
+private:
+ FactorType ns_cntfrq_factor;
+ FactorType us_cntfrq_factor;
+ FactorType ms_cntfrq_factor;
+ FactorType guest_cntfrq_factor;
+ FactorType gputick_cntfrq_factor;
+};
+
+} // namespace Common::Arm64
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 @@
#define LOAD_DIR "load"
#define LOG_DIR "log"
#define NAND_DIR "nand"
+#define PLAY_TIME_DIR "play_time"
#define SCREENSHOTS_DIR "screenshots"
#define SDMC_DIR "sdmc"
#define SHADER_DIR "shader"
#define TAS_DIR "tas"
+#define ICONS_DIR "icons"
// yuzu-specific files
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:
GenerateYuzuPath(YuzuPath::LoadDir, yuzu_path / LOAD_DIR);
GenerateYuzuPath(YuzuPath::LogDir, yuzu_path / LOG_DIR);
GenerateYuzuPath(YuzuPath::NANDDir, yuzu_path / NAND_DIR);
+ GenerateYuzuPath(YuzuPath::PlayTimeDir, yuzu_path / PLAY_TIME_DIR);
GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR);
GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
+ GenerateYuzuPath(YuzuPath::IconsDir, yuzu_path / ICONS_DIR);
}
private:
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 {
LoadDir, // Where cheat/mod files are stored.
LogDir, // Where log files are stored.
NANDDir, // Where the emulated NAND is stored.
+ PlayTimeDir, // Where play time data is stored.
ScreenshotsDir, // Where yuzu screenshots are stored.
SDMCDir, // Where the emulated SDMC is stored.
ShaderDir, // Where shaders are stored.
TASDir, // Where TAS scripts are stored.
+ IconsDir, // Where Icons for Windows shortcuts are stored.
};
/**
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) {
return convert.from_bytes(input.data(), input.data() + input.size());
}
+std::u32string UTF8ToUTF32(std::string_view input) {
+ std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
+ return convert.from_bytes(input.data(), input.data() + input.size());
+}
+
#ifdef _WIN32
static std::wstring CPToUTF16(u32 code_page, std::string_view input) {
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* _
[[nodiscard]] std::string UTF16ToUTF8(std::u16string_view input);
[[nodiscard]] std::u16string UTF8ToUTF16(std::string_view input);
+[[nodiscard]] std::u32string UTF8ToUTF32(std::string_view input);
#ifdef _WIN32
[[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 @@
#include "common/x64/rdtsc.h"
#endif
+#if defined(ARCHITECTURE_arm64) && defined(__linux__)
+#include "common/arm64/native_clock.h"
+#endif
+
namespace Common {
class StandardWallClock final : public WallClock {
@@ -53,7 +57,7 @@ private:
};
std::unique_ptr<WallClock> CreateOptimalClock() {
-#ifdef ARCHITECTURE_x86_64
+#if defined(ARCHITECTURE_x86_64)
const auto& caps = GetCPUCaps();
if (caps.invariant_tsc && caps.tsc_frequency >= std::nano::den) {
@@ -64,6 +68,8 @@ std::unique_ptr<WallClock> CreateOptimalClock() {
// - Is not more precise than 1 GHz (1ns resolution)
return std::make_unique<StandardWallClock>();
}
+#elif defined(ARCHITECTURE_arm64) && defined(__linux__)
+ return std::make_unique<Arm64::NativeClock>();
#else
return std::make_unique<StandardWallClock>();
#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
hle/service/caps/caps_a.h
hle/service/caps/caps_c.cpp
hle/service/caps/caps_c.h
- hle/service/caps/caps_u.cpp
- hle/service/caps/caps_u.h
+ hle/service/caps/caps_manager.cpp
+ hle/service/caps/caps_manager.h
+ hle/service/caps/caps_result.h
hle/service/caps/caps_sc.cpp
hle/service/caps/caps_sc.h
hle/service/caps/caps_ss.cpp
hle/service/caps/caps_ss.h
hle/service/caps/caps_su.cpp
hle/service/caps/caps_su.h
+ hle/service/caps/caps_types.h
+ hle/service/caps/caps_u.cpp
+ hle/service/caps/caps_u.h
hle/service/erpt/erpt.cpp
hle/service/erpt/erpt.h
hle/service/es/es.cpp
@@ -698,6 +702,8 @@ add_library(core STATIC
hle/service/nvnflinger/consumer_base.cpp
hle/service/nvnflinger/consumer_base.h
hle/service/nvnflinger/consumer_listener.h
+ hle/service/nvnflinger/fb_share_buffer_manager.cpp
+ hle/service/nvnflinger/fb_share_buffer_manager.h
hle/service/nvnflinger/graphic_buffer_producer.cpp
hle/service/nvnflinger/graphic_buffer_producer.h
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() {
impl->RefreshTime();
if (IsPoweredOn()) {
+ if (Settings::values.custom_rtc_enabled) {
+ const s64 posix_time{Settings::values.custom_rtc.GetValue()};
+ GetTimeManager().UpdateLocalSystemClockTime(posix_time);
+ }
Renderer().RefreshBaseSettings();
}
}
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index b98a0cb33..e671b270f 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -32,6 +32,7 @@ struct CoreTiming::Event {
std::uintptr_t user_data;
std::weak_ptr<EventType> type;
s64 reschedule_time;
+ heap_t::handle_type handle{};
// Sort by time, unless the times are the same, in which case sort by
// the order added to the queue
@@ -122,9 +123,9 @@ void CoreTiming::ScheduleEvent(std::chrono::nanoseconds ns_into_future,
std::scoped_lock scope{basic_lock};
const auto next_time{absolute_time ? ns_into_future : GetGlobalTimeNs() + ns_into_future};
- event_queue.emplace_back(
- Event{next_time.count(), event_fifo_id++, user_data, event_type, 0});
- std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
+ auto h{event_queue.emplace(
+ Event{next_time.count(), event_fifo_id++, user_data, event_type, 0})};
+ (*h).handle = h;
}
event.Set();
@@ -138,10 +139,9 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
std::scoped_lock scope{basic_lock};
const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
- event_queue.emplace_back(
- Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()});
-
- std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
+ auto h{event_queue.emplace(Event{next_time.count(), event_fifo_id++, user_data, event_type,
+ resched_time.count()})};
+ (*h).handle = h;
}
event.Set();
@@ -151,15 +151,17 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
std::uintptr_t user_data, bool wait) {
{
std::scoped_lock lk{basic_lock};
- const auto itr =
- std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
- return e.type.lock().get() == event_type.get() && e.user_data == user_data;
- });
-
- // Removing random items breaks the invariant so we have to re-establish it.
- if (itr != event_queue.end()) {
- event_queue.erase(itr, event_queue.end());
- std::make_heap(event_queue.begin(), event_queue.end(), std::greater<>());
+
+ std::vector<heap_t::handle_type> to_remove;
+ for (auto itr = event_queue.begin(); itr != event_queue.end(); itr++) {
+ const Event& e = *itr;
+ if (e.type.lock().get() == event_type.get() && e.user_data == user_data) {
+ to_remove.push_back(itr->handle);
+ }
+ }
+
+ for (auto h : to_remove) {
+ event_queue.erase(h);
}
}
@@ -200,35 +202,45 @@ std::optional<s64> CoreTiming::Advance() {
std::scoped_lock lock{advance_lock, basic_lock};
global_timer = GetGlobalTimeNs().count();
- while (!event_queue.empty() && event_queue.front().time <= global_timer) {
- Event evt = std::move(event_queue.front());
- std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
- event_queue.pop_back();
+ while (!event_queue.empty() && event_queue.top().time <= global_timer) {
+ const Event& evt = event_queue.top();
if (const auto event_type{evt.type.lock()}) {
- basic_lock.unlock();
+ if (evt.reschedule_time == 0) {
+ const auto evt_user_data = evt.user_data;
+ const auto evt_time = evt.time;
+
+ event_queue.pop();
+
+ basic_lock.unlock();
+
+ event_type->callback(
+ evt_user_data, evt_time,
+ std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt_time});
+
+ basic_lock.lock();
+ } else {
+ basic_lock.unlock();
- const auto new_schedule_time{event_type->callback(
- evt.user_data, evt.time,
- std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt.time})};
+ const auto new_schedule_time{event_type->callback(
+ evt.user_data, evt.time,
+ std::chrono::nanoseconds{GetGlobalTimeNs().count() - evt.time})};
- basic_lock.lock();
+ basic_lock.lock();
- if (evt.reschedule_time != 0) {
const auto next_schedule_time{new_schedule_time.has_value()
? new_schedule_time.value().count()
: evt.reschedule_time};
- // If this event was scheduled into a pause, its time now is going to be way behind.
- // Re-set this event to continue from the end of the pause.
+ // If this event was scheduled into a pause, its time now is going to be way
+ // behind. Re-set this event to continue from the end of the pause.
auto next_time{evt.time + next_schedule_time};
if (evt.time < pause_end_time) {
next_time = pause_end_time + next_schedule_time;
}
- event_queue.emplace_back(
- Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time});
- std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
+ event_queue.update(evt.handle, Event{next_time, event_fifo_id++, evt.user_data,
+ evt.type, next_schedule_time, evt.handle});
}
}
@@ -236,7 +248,7 @@ std::optional<s64> CoreTiming::Advance() {
}
if (!event_queue.empty()) {
- return event_queue.front().time;
+ return event_queue.top().time;
} else {
return std::nullopt;
}
@@ -274,7 +286,8 @@ void CoreTiming::ThreadLoop() {
#endif
}
} else {
- // Queue is empty, wait until another event is scheduled and signals us to continue.
+ // Queue is empty, wait until another event is scheduled and signals us to
+ // continue.
wait_set = true;
event.Wait();
}
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index c20e906fb..26a8b93a7 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -11,7 +11,8 @@
#include <optional>
#include <string>
#include <thread>
-#include <vector>
+
+#include <boost/heap/fibonacci_heap.hpp>
#include "common/common_types.h"
#include "common/thread.h"
@@ -151,11 +152,10 @@ private:
s64 timer_resolution_ns;
#endif
- // The queue is a min-heap using std::make_heap/push_heap/pop_heap.
- // We don't use std::priority_queue because we need to be able to serialize, unserialize and
- // erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't
- // accommodated by the standard adaptor class.
- std::vector<Event> event_queue;
+ using heap_t =
+ boost::heap::fibonacci_heap<CoreTiming::Event, boost::heap::compare<std::greater<>>>;
+
+ heap_t event_queue;
u64 event_fifo_id = 0;
std::shared_ptr<EventType> ev_lost;
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 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <atomic>
+#include <codecvt>
+#include <locale>
#include <numeric>
#include <optional>
#include <thread>
@@ -12,6 +14,7 @@
#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/settings.h"
+#include "common/string_util.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/debugger/gdbstub.h"
@@ -68,10 +71,16 @@ static std::string EscapeGDB(std::string_view data) {
}
static std::string EscapeXML(std::string_view data) {
+ std::u32string converted = U"[Encoding error]";
+ try {
+ converted = Common::UTF8ToUTF32(data);
+ } catch (std::range_error&) {
+ }
+
std::string escaped;
escaped.reserve(data.size());
- for (char c : data) {
+ for (char32_t c : converted) {
switch (c) {
case '&':
escaped += "&amp;";
@@ -86,7 +95,11 @@ static std::string EscapeXML(std::string_view data) {
escaped += "&gt;";
break;
default:
- escaped += c;
+ if (c > 0x7f) {
+ escaped += fmt::format("&#{};", static_cast<u32>(c));
+ } else {
+ escaped += static_cast<char>(c);
+ }
break;
}
}
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 @@
#include <vector>
#include "common/logging/log.h"
+#include "common/scope_exit.h"
#include "core/file_sys/program_metadata.h"
#include "core/file_sys/vfs.h"
#include "core/loader/loader.h"
@@ -95,6 +96,13 @@ Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) {
return Loader::ResultStatus::Success;
}
+Loader::ResultStatus ProgramMetadata::Reload(VirtualFile file) {
+ const u64 original_program_id = aci_header.title_id;
+ SCOPE_EXIT({ aci_header.title_id = original_program_id; });
+
+ return this->Load(file);
+}
+
/*static*/ ProgramMetadata ProgramMetadata::GetDefault() {
// Allow use of cores 0~3 and thread priorities 1~63.
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:
static ProgramMetadata GetDefault();
Loader::ResultStatus Load(VirtualFile file);
+ Loader::ResultStatus Reload(VirtualFile file);
/// Load from parameters instead of NPDM file, used for KIP
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:
return Write(reinterpret_cast<const u8*>(&data), sizeof(T), offset);
}
- // Renames the file to name. Returns whether or not the operation was successsful.
+ // Renames the file to name. Returns whether or not the operation was successful.
virtual bool Rename(std::string_view name) = 0;
// Returns the full path of this file as a string, recursively
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 94bd656fe..2af3f06fc 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -542,6 +542,7 @@ void EmulatedController::UnloadInput() {
}
void EmulatedController::EnableConfiguration() {
+ std::scoped_lock lock{connect_mutex, npad_mutex};
is_configuring = true;
tmp_is_connected = is_connected;
tmp_npad_type = npad_type;
@@ -1556,7 +1557,7 @@ void EmulatedController::Connect(bool use_temporary_value) {
auto trigger_guard =
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Connected, !is_configuring); });
- std::scoped_lock lock{mutex};
+ std::scoped_lock lock{connect_mutex, mutex};
if (is_configuring) {
tmp_is_connected = true;
return;
@@ -1572,7 +1573,7 @@ void EmulatedController::Connect(bool use_temporary_value) {
void EmulatedController::Disconnect() {
auto trigger_guard =
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Disconnected, !is_configuring); });
- std::scoped_lock lock{mutex};
+ std::scoped_lock lock{connect_mutex, mutex};
if (is_configuring) {
tmp_is_connected = false;
return;
@@ -1586,7 +1587,7 @@ void EmulatedController::Disconnect() {
}
bool EmulatedController::IsConnected(bool get_temporary_value) const {
- std::scoped_lock lock{mutex};
+ std::scoped_lock lock{connect_mutex};
if (get_temporary_value && is_configuring) {
return tmp_is_connected;
}
@@ -1599,7 +1600,7 @@ NpadIdType EmulatedController::GetNpadIdType() const {
}
NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) const {
- std::scoped_lock lock{mutex};
+ std::scoped_lock lock{npad_mutex};
if (get_temporary_value && is_configuring) {
return tmp_npad_type;
}
@@ -1609,7 +1610,7 @@ NpadStyleIndex EmulatedController::GetNpadStyleIndex(bool get_temporary_value) c
void EmulatedController::SetNpadStyleIndex(NpadStyleIndex npad_type_) {
auto trigger_guard =
SCOPE_GUARD({ TriggerOnChange(ControllerTriggerType::Type, !is_configuring); });
- std::scoped_lock lock{mutex};
+ std::scoped_lock lock{mutex, npad_mutex};
if (is_configuring) {
if (tmp_npad_type == npad_type_) {
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index 88d77db8d..d4500583e 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -603,6 +603,8 @@ private:
mutable std::mutex mutex;
mutable std::mutex callback_mutex;
+ mutable std::mutex npad_mutex;
+ mutable std::mutex connect_mutex;
std::unordered_map<int, ControllerUpdateCallback> callback_list;
int last_callback_key = 0;
diff --git a/src/core/hle/kernel/k_hardware_timer.cpp b/src/core/hle/kernel/k_hardware_timer.cpp
index 4dcd53821..8e2e40307 100644
--- a/src/core/hle/kernel/k_hardware_timer.cpp
+++ b/src/core/hle/kernel/k_hardware_timer.cpp
@@ -35,7 +35,9 @@ void KHardwareTimer::DoTask() {
}
// Disable the timer interrupt while we handle this.
- this->DisableInterrupt();
+ // Not necessary due to core timing already having popped this event to call it.
+ // this->DisableInterrupt();
+ m_wakeup_time = std::numeric_limits<s64>::max();
if (const s64 next_time = this->DoInterruptTaskImpl(GetTick());
0 < next_time && next_time <= m_wakeup_time) {
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
found->Reset(address, inserted_region_last, old_pair, new_attr, type_id);
this->insert(*found);
} else {
- // If we can't re-use, adjust the old region.
+ // If we can't reuse, adjust the old region.
found->Reset(old_address, address - 1, old_pair, old_attr, old_type);
this->insert(*found);
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 @@
#include "common/assert.h"
#include "common/literals.h"
#include "common/scope_exit.h"
+#include "common/settings.h"
#include "core/core.h"
#include "core/hle/kernel/k_address_space_info.h"
#include "core/hle/kernel/k_memory_block.h"
@@ -337,11 +338,14 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type
}
void KPageTable::Finalize() {
+ auto HostUnmapCallback = [&](KProcessAddress addr, u64 size) {
+ if (Settings::IsFastmemEnabled()) {
+ m_system.DeviceMemory().buffer.Unmap(GetInteger(addr), size);
+ }
+ };
+
// Finalize memory blocks.
- m_memory_block_manager.Finalize(m_memory_block_slab_manager,
- [&](KProcessAddress addr, u64 size) {
- m_memory->UnmapRegion(*m_page_table_impl, addr, size);
- });
+ m_memory_block_manager.Finalize(m_memory_block_slab_manager, std::move(HostUnmapCallback));
// Release any insecure mapped memory.
if (m_mapped_insecure_memory) {
@@ -2945,6 +2949,23 @@ Result KPageTable::UnlockForIpcUserBuffer(KProcessAddress address, size_t size)
KMemoryAttribute::Locked, nullptr));
}
+Result KPageTable::LockForTransferMemory(KPageGroup* out, KProcessAddress address, size_t size,
+ KMemoryPermission perm) {
+ R_RETURN(this->LockMemoryAndOpen(out, nullptr, address, size, KMemoryState::FlagCanTransfer,
+ KMemoryState::FlagCanTransfer, KMemoryPermission::All,
+ KMemoryPermission::UserReadWrite, KMemoryAttribute::All,
+ KMemoryAttribute::None, perm, KMemoryAttribute::Locked));
+}
+
+Result KPageTable::UnlockForTransferMemory(KProcessAddress address, size_t size,
+ const KPageGroup& pg) {
+ R_RETURN(this->UnlockMemory(address, size, KMemoryState::FlagCanTransfer,
+ KMemoryState::FlagCanTransfer, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::All,
+ KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite,
+ KMemoryAttribute::Locked, std::addressof(pg)));
+}
+
Result KPageTable::LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size) {
R_RETURN(this->LockMemoryAndOpen(
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:
Result CleanupForIpcServer(KProcessAddress address, size_t size, KMemoryState dst_state);
Result CleanupForIpcClient(KProcessAddress address, size_t size, KMemoryState dst_state);
+ Result LockForTransferMemory(KPageGroup* out, KProcessAddress address, size_t size,
+ KMemoryPermission perm);
+ Result UnlockForTransferMemory(KProcessAddress address, size_t size, const KPageGroup& pg);
Result LockForCodeMemory(KPageGroup* out, KProcessAddress addr, size_t size);
Result UnlockForCodeMemory(KProcessAddress addr, size_t size, const KPageGroup& pg);
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 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/scope_exit.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/k_transfer_memory.h"
@@ -9,28 +10,50 @@
namespace Kernel {
KTransferMemory::KTransferMemory(KernelCore& kernel)
- : KAutoObjectWithSlabHeapAndContainer{kernel} {}
+ : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{kernel} {}
KTransferMemory::~KTransferMemory() = default;
-Result KTransferMemory::Initialize(KProcessAddress address, std::size_t size,
- Svc::MemoryPermission owner_perm) {
+Result KTransferMemory::Initialize(KProcessAddress addr, std::size_t size,
+ Svc::MemoryPermission own_perm) {
// Set members.
m_owner = GetCurrentProcessPointer(m_kernel);
- // TODO(bunnei): Lock for transfer memory
+ // Get the owner page table.
+ auto& page_table = m_owner->GetPageTable();
+
+ // Construct the page group, guarding to make sure our state is valid on exit.
+ m_page_group.emplace(m_kernel, page_table.GetBlockInfoManager());
+ auto pg_guard = SCOPE_GUARD({ m_page_group.reset(); });
+
+ // Lock the memory.
+ R_TRY(page_table.LockForTransferMemory(std::addressof(*m_page_group), addr, size,
+ ConvertToKMemoryPermission(own_perm)));
// Set remaining tracking members.
m_owner->Open();
- m_owner_perm = owner_perm;
- m_address = address;
- m_size = size;
+ m_owner_perm = own_perm;
+ m_address = addr;
m_is_initialized = true;
+ m_is_mapped = false;
+ // We succeeded.
+ pg_guard.Cancel();
R_SUCCEED();
}
-void KTransferMemory::Finalize() {}
+void KTransferMemory::Finalize() {
+ // Unlock.
+ if (!m_is_mapped) {
+ const size_t size = m_page_group->GetNumPages() * PageSize;
+ ASSERT(R_SUCCEEDED(
+ m_owner->GetPageTable().UnlockForTransferMemory(m_address, size, *m_page_group)));
+ }
+
+ // Close the page group.
+ m_page_group->Close();
+ m_page_group->Finalize();
+}
void KTransferMemory::PostDestroy(uintptr_t arg) {
KProcess* owner = reinterpret_cast<KProcess*>(arg);
@@ -38,4 +61,54 @@ void KTransferMemory::PostDestroy(uintptr_t arg) {
owner->Close();
}
+Result KTransferMemory::Map(KProcessAddress address, size_t size, Svc::MemoryPermission map_perm) {
+ // Validate the size.
+ R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
+
+ // Validate the permission.
+ R_UNLESS(m_owner_perm == map_perm, ResultInvalidState);
+
+ // Lock ourselves.
+ KScopedLightLock lk(m_lock);
+
+ // Ensure we're not already mapped.
+ R_UNLESS(!m_is_mapped, ResultInvalidState);
+
+ // Map the memory.
+ const KMemoryState state = (m_owner_perm == Svc::MemoryPermission::None)
+ ? KMemoryState::Transfered
+ : KMemoryState::SharedTransfered;
+ R_TRY(GetCurrentProcess(m_kernel).GetPageTable().MapPageGroup(
+ address, *m_page_group, state, KMemoryPermission::UserReadWrite));
+
+ // Mark ourselves as mapped.
+ m_is_mapped = true;
+
+ R_SUCCEED();
+}
+
+Result KTransferMemory::Unmap(KProcessAddress address, size_t size) {
+ // Validate the size.
+ R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
+
+ // Lock ourselves.
+ KScopedLightLock lk(m_lock);
+
+ // Unmap the memory.
+ const KMemoryState state = (m_owner_perm == Svc::MemoryPermission::None)
+ ? KMemoryState::Transfered
+ : KMemoryState::SharedTransfered;
+ R_TRY(GetCurrentProcess(m_kernel).GetPageTable().UnmapPageGroup(address, *m_page_group, state));
+
+ // Mark ourselves as unmapped.
+ ASSERT(m_is_mapped);
+ m_is_mapped = false;
+
+ R_SUCCEED();
+}
+
+size_t KTransferMemory::GetSize() const {
+ return m_is_initialized ? m_page_group->GetNumPages() * PageSize : 0;
+}
+
} // 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 @@
#pragma once
+#include <optional>
+
+#include "core/hle/kernel/k_page_group.h"
#include "core/hle/kernel/slab_helpers.h"
#include "core/hle/kernel/svc_types.h"
#include "core/hle/result.h"
@@ -48,16 +51,19 @@ public:
return m_address;
}
- size_t GetSize() const {
- return m_is_initialized ? m_size : 0;
- }
+ size_t GetSize() const;
+
+ Result Map(KProcessAddress address, size_t size, Svc::MemoryPermission map_perm);
+ Result Unmap(KProcessAddress address, size_t size);
private:
+ std::optional<KPageGroup> m_page_group{};
KProcess* m_owner{};
KProcessAddress m_address{};
+ KLightLock m_lock;
Svc::MemoryPermission m_owner_perm{};
- size_t m_size{};
bool m_is_initialized{};
+ bool m_is_mapped{};
};
} // namespace Kernel
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
}
Result MapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address, uint64_t size,
- MemoryPermission owner_perm) {
- UNIMPLEMENTED();
- R_THROW(ResultNotImplemented);
+ MemoryPermission map_perm) {
+ // Validate the address/size.
+ R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress);
+ R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize);
+ R_UNLESS(size > 0, ResultInvalidSize);
+ R_UNLESS((address < address + size), ResultInvalidCurrentMemory);
+
+ // Validate the permission.
+ R_UNLESS(IsValidTransferMemoryPermission(map_perm), ResultInvalidState);
+
+ // Get the transfer memory.
+ KScopedAutoObject trmem = GetCurrentProcess(system.Kernel())
+ .GetHandleTable()
+ .GetObject<KTransferMemory>(trmem_handle);
+ R_UNLESS(trmem.IsNotNull(), ResultInvalidHandle);
+
+ // Verify that the mapping is in range.
+ R_UNLESS(GetCurrentProcess(system.Kernel())
+ .GetPageTable()
+ .CanContain(address, size, KMemoryState::Transfered),
+ ResultInvalidMemoryRegion);
+
+ // Map the transfer memory.
+ R_TRY(trmem->Map(address, size, map_perm));
+
+ // We succeeded.
+ R_SUCCEED();
}
Result UnmapTransferMemory(Core::System& system, Handle trmem_handle, uint64_t address,
uint64_t size) {
- UNIMPLEMENTED();
- R_THROW(ResultNotImplemented);
+ // Validate the address/size.
+ R_UNLESS(Common::IsAligned(address, PageSize), ResultInvalidAddress);
+ R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize);
+ R_UNLESS(size > 0, ResultInvalidSize);
+ R_UNLESS((address < address + size), ResultInvalidCurrentMemory);
+
+ // Get the transfer memory.
+ KScopedAutoObject trmem = GetCurrentProcess(system.Kernel())
+ .GetHandleTable()
+ .GetObject<KTransferMemory>(trmem_handle);
+ R_UNLESS(trmem.IsNotNull(), ResultInvalidHandle);
+
+ // Verify that the mapping is in range.
+ R_UNLESS(GetCurrentProcess(system.Kernel())
+ .GetPageTable()
+ .CanContain(address, size, KMemoryState::Transfered),
+ ResultInvalidMemoryRegion);
+
+ // Unmap the transfer memory.
+ R_TRY(trmem->Unmap(address, size));
+
+ R_SUCCEED();
}
Result 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 e9bd04842..ac376b55a 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -8,6 +8,7 @@
#include "common/settings.h"
#include "common/settings_enums.h"
#include "core/core.h"
+#include "core/core_timing.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
@@ -19,6 +20,7 @@
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
+#include "core/hle/service/am/applets/applet_cabinet.h"
#include "core/hle/service/am/applets/applet_mii_edit_types.h"
#include "core/hle/service/am/applets/applet_profile_select.h"
#include "core/hle/service/am/applets/applet_web_browser.h"
@@ -29,15 +31,17 @@
#include "core/hle/service/apm/apm_controller.h"
#include "core/hle/service/apm/apm_interface.h"
#include "core/hle/service/bcat/backend/backend.h"
-#include "core/hle/service/caps/caps.h"
+#include "core/hle/service/caps/caps_types.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ns/ns.h"
+#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
#include "core/hle/service/nvnflinger/nvnflinger.h"
#include "core/hle/service/pm/pm.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/sm/sm.h"
#include "core/hle/service/vi/vi.h"
+#include "core/hle/service/vi/vi_results.h"
#include "core/memory.h"
namespace Service::AM {
@@ -190,7 +194,7 @@ IDisplayController::IDisplayController(Core::System& system_)
{4, nullptr, "UpdateCallerAppletCaptureImage"},
{5, nullptr, "GetLastForegroundCaptureImageEx"},
{6, nullptr, "GetLastApplicationCaptureImageEx"},
- {7, nullptr, "GetCallerAppletCaptureImageEx"},
+ {7, &IDisplayController::GetCallerAppletCaptureImageEx, "GetCallerAppletCaptureImageEx"},
{8, &IDisplayController::TakeScreenShotOfOwnLayer, "TakeScreenShotOfOwnLayer"},
{9, nullptr, "CopyBetweenCaptureBuffers"},
{10, nullptr, "AcquireLastApplicationCaptureBuffer"},
@@ -208,8 +212,8 @@ IDisplayController::IDisplayController(Core::System& system_)
{23, nullptr, "ReleaseLastApplicationCaptureSharedBuffer"},
{24, nullptr, "AcquireLastForegroundCaptureSharedBuffer"},
{25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"},
- {26, nullptr, "AcquireCallerAppletCaptureSharedBuffer"},
- {27, nullptr, "ReleaseCallerAppletCaptureSharedBuffer"},
+ {26, &IDisplayController::AcquireCallerAppletCaptureSharedBuffer, "AcquireCallerAppletCaptureSharedBuffer"},
+ {27, &IDisplayController::ReleaseCallerAppletCaptureSharedBuffer, "ReleaseCallerAppletCaptureSharedBuffer"},
{28, nullptr, "TakeScreenShotOfOwnLayerEx"},
};
// clang-format on
@@ -219,6 +223,15 @@ IDisplayController::IDisplayController(Core::System& system_)
IDisplayController::~IDisplayController() = default;
+void IDisplayController::GetCallerAppletCaptureImageEx(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push(1u);
+ rb.Push(0);
+}
+
void IDisplayController::TakeScreenShotOfOwnLayer(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
@@ -226,6 +239,22 @@ void IDisplayController::TakeScreenShotOfOwnLayer(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
+void IDisplayController::AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push(1U);
+ rb.Push(0);
+}
+
+void IDisplayController::ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
IDebugFunctions::IDebugFunctions(Core::System& system_)
: ServiceFramework{system_, "IDebugFunctions"} {
// clang-format off
@@ -285,14 +314,14 @@ ISelfController::ISelfController(Core::System& system_, Nvnflinger::Nvnflinger&
{20, nullptr, "SetDesirableKeyboardLayout"},
{21, nullptr, "GetScreenShotProgramId"},
{40, &ISelfController::CreateManagedDisplayLayer, "CreateManagedDisplayLayer"},
- {41, nullptr, "IsSystemBufferSharingEnabled"},
- {42, nullptr, "GetSystemSharedLayerHandle"},
- {43, nullptr, "GetSystemSharedBufferHandle"},
+ {41, &ISelfController::IsSystemBufferSharingEnabled, "IsSystemBufferSharingEnabled"},
+ {42, &ISelfController::GetSystemSharedLayerHandle, "GetSystemSharedLayerHandle"},
+ {43, &ISelfController::GetSystemSharedBufferHandle, "GetSystemSharedBufferHandle"},
{44, &ISelfController::CreateManagedDisplaySeparableLayer, "CreateManagedDisplaySeparableLayer"},
{45, nullptr, "SetManagedDisplayLayerSeparationMode"},
{46, nullptr, "SetRecordingLayerCompositionEnabled"},
{50, &ISelfController::SetHandlesRequestToDisplay, "SetHandlesRequestToDisplay"},
- {51, nullptr, "ApproveToDisplay"},
+ {51, &ISelfController::ApproveToDisplay, "ApproveToDisplay"},
{60, nullptr, "OverrideAutoSleepTimeAndDimmingTime"},
{61, nullptr, "SetMediaPlaybackState"},
{62, &ISelfController::SetIdleTimeDetectionExtension, "SetIdleTimeDetectionExtension"},
@@ -491,6 +520,50 @@ void ISelfController::CreateManagedDisplayLayer(HLERequestContext& ctx) {
rb.Push(*layer_id);
}
+void ISelfController::IsSystemBufferSharingEnabled(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(this->EnsureBufferSharingEnabled());
+}
+
+void ISelfController::GetSystemSharedLayerHandle(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(this->EnsureBufferSharingEnabled());
+ rb.Push<s64>(system_shared_buffer_id);
+ rb.Push<s64>(system_shared_layer_id);
+}
+
+void ISelfController::GetSystemSharedBufferHandle(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(this->EnsureBufferSharingEnabled());
+ rb.Push<s64>(system_shared_buffer_id);
+}
+
+Result ISelfController::EnsureBufferSharingEnabled() {
+ if (buffer_sharing_enabled) {
+ return ResultSuccess;
+ }
+
+ if (system.GetAppletManager().GetCurrentAppletId() <= Applets::AppletId::Application) {
+ return VI::ResultOperationFailed;
+ }
+
+ const auto display_id = nvnflinger.OpenDisplay("Default");
+ const auto result = nvnflinger.GetSystemBufferManager().Initialize(
+ &system_shared_buffer_id, &system_shared_layer_id, *display_id);
+
+ if (result.IsSuccess()) {
+ buffer_sharing_enabled = true;
+ }
+
+ return result;
+}
+
void ISelfController::CreateManagedDisplaySeparableLayer(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
@@ -516,6 +589,13 @@ void ISelfController::SetHandlesRequestToDisplay(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
+void ISelfController::ApproveToDisplay(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void ISelfController::SetIdleTimeDetectionExtension(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
idle_time_detection_extension = rp.Pop<u32>();
@@ -684,9 +764,70 @@ void AppletMessageQueue::OperationModeChanged() {
on_operation_mode_changed->Signal();
}
+ILockAccessor::ILockAccessor(Core::System& system_)
+ : ServiceFramework{system_, "ILockAccessor"}, service_context{system_, "ILockAccessor"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {1, &ILockAccessor::TryLock, "TryLock"},
+ {2, &ILockAccessor::Unlock, "Unlock"},
+ {3, &ILockAccessor::GetEvent, "GetEvent"},
+ {4,&ILockAccessor::IsLocked, "IsLocked"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+
+ lock_event = service_context.CreateEvent("ILockAccessor::LockEvent");
+}
+
+ILockAccessor::~ILockAccessor() = default;
+
+void ILockAccessor::TryLock(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto return_handle = rp.Pop<bool>();
+
+ LOG_WARNING(Service_AM, "(STUBBED) called, return_handle={}", return_handle);
+
+ // TODO: When return_handle is true this function should return the lock handle
+
+ is_locked = true;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_locked);
+}
+
+void ILockAccessor::Unlock(HLERequestContext& ctx) {
+ LOG_INFO(Service_AM, "called");
+
+ is_locked = false;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void ILockAccessor::GetEvent(HLERequestContext& ctx) {
+ LOG_INFO(Service_AM, "called");
+
+ lock_event->Signal();
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(lock_event->GetReadableEvent());
+}
+
+void ILockAccessor::IsLocked(HLERequestContext& ctx) {
+ LOG_INFO(Service_AM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_locked);
+}
+
ICommonStateGetter::ICommonStateGetter(Core::System& system_,
std::shared_ptr<AppletMessageQueue> msg_queue_)
- : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)} {
+ : ServiceFramework{system_, "ICommonStateGetter"}, msg_queue{std::move(msg_queue_)},
+ service_context{system_, "ICommonStateGetter"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"},
@@ -699,14 +840,14 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
{7, nullptr, "GetCradleStatus"},
{8, &ICommonStateGetter::GetBootMode, "GetBootMode"},
{9, &ICommonStateGetter::GetCurrentFocusState, "GetCurrentFocusState"},
- {10, nullptr, "RequestToAcquireSleepLock"},
+ {10, &ICommonStateGetter::RequestToAcquireSleepLock, "RequestToAcquireSleepLock"},
{11, nullptr, "ReleaseSleepLock"},
{12, nullptr, "ReleaseSleepLockTransiently"},
- {13, nullptr, "GetAcquiredSleepLockEvent"},
+ {13, &ICommonStateGetter::GetAcquiredSleepLockEvent, "GetAcquiredSleepLockEvent"},
{14, nullptr, "GetWakeupCount"},
{20, nullptr, "PushToGeneralChannel"},
{30, nullptr, "GetHomeButtonReaderLockAccessor"},
- {31, nullptr, "GetReaderLockAccessorEx"},
+ {31, &ICommonStateGetter::GetReaderLockAccessorEx, "GetReaderLockAccessorEx"},
{32, nullptr, "GetWriterLockAccessorEx"},
{40, nullptr, "GetCradleFwVersion"},
{50, &ICommonStateGetter::IsVrModeEnabled, "IsVrModeEnabled"},
@@ -724,7 +865,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
{65, nullptr, "GetApplicationIdByContentActionName"},
{66, &ICommonStateGetter::SetCpuBoostMode, "SetCpuBoostMode"},
{67, nullptr, "CancelCpuBoostMode"},
- {68, nullptr, "GetBuiltInDisplayType"},
+ {68, &ICommonStateGetter::GetBuiltInDisplayType, "GetBuiltInDisplayType"},
{80, &ICommonStateGetter::PerformSystemButtonPressingIfInFocus, "PerformSystemButtonPressingIfInFocus"},
{90, nullptr, "SetPerformanceConfigurationChangedNotification"},
{91, nullptr, "GetCurrentPerformanceConfiguration"},
@@ -745,6 +886,8 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system_,
RegisterHandlers(functions);
+ sleep_lock_event = service_context.CreateEvent("ICommonStateGetter::SleepLockEvent");
+
// Configure applets to be in foreground state
msg_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
msg_queue->PushMessage(AppletMessageQueue::AppletMessage::ChangeIntoForeground);
@@ -793,6 +936,36 @@ void ICommonStateGetter::GetCurrentFocusState(HLERequestContext& ctx) {
rb.Push(static_cast<u8>(FocusState::InFocus));
}
+void ICommonStateGetter::RequestToAcquireSleepLock(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ // Sleep lock is acquired immediately.
+ sleep_lock_event->Signal();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void ICommonStateGetter::GetReaderLockAccessorEx(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto unknown = rp.Pop<u32>();
+
+ LOG_INFO(Service_AM, "called, unknown={}", unknown);
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<ILockAccessor>(system);
+}
+
+void ICommonStateGetter::GetAcquiredSleepLockEvent(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(sleep_lock_event->GetReadableEvent());
+}
+
void ICommonStateGetter::IsVrModeEnabled(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
@@ -869,6 +1042,14 @@ void ICommonStateGetter::SetCpuBoostMode(HLERequestContext& ctx) {
apm_sys->SetCpuBoostMode(ctx);
}
+void ICommonStateGetter::GetBuiltInDisplayType(HLERequestContext& ctx) {
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(0);
+}
+
void ICommonStateGetter::PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto system_button{rp.PopEnum<SystemButtonType>()};
@@ -1385,7 +1566,19 @@ ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_)
// clang-format on
RegisterHandlers(functions);
- PushInShowMiiEditData();
+ switch (system.GetAppletManager().GetCurrentAppletId()) {
+ case Applets::AppletId::Cabinet:
+ PushInShowCabinetData();
+ break;
+ case Applets::AppletId::MiiEdit:
+ PushInShowMiiEditData();
+ break;
+ case Applets::AppletId::PhotoViewer:
+ PushInShowAlbum();
+ break;
+ default:
+ break;
+ }
}
ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default;
@@ -1431,7 +1624,7 @@ void ILibraryAppletSelfAccessor::GetLibraryAppletInfo(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
const LibraryAppletInfo applet_info{
- .applet_id = Applets::AppletId::MiiEdit,
+ .applet_id = system.GetAppletManager().GetCurrentAppletId(),
.library_applet_mode = Applets::LibraryAppletMode::AllForeground,
};
@@ -1459,6 +1652,52 @@ void ILibraryAppletSelfAccessor::GetCallerAppletIdentityInfo(HLERequestContext&
rb.PushRaw(applet_info);
}
+void ILibraryAppletSelfAccessor::PushInShowAlbum() {
+ const Applets::CommonArguments arguments{
+ .arguments_version = Applets::CommonArgumentVersion::Version3,
+ .size = Applets::CommonArgumentSize::Version3,
+ .library_version = 1,
+ .theme_color = Applets::ThemeColor::BasicBlack,
+ .play_startup_sound = true,
+ .system_tick = system.CoreTiming().GetClockTicks(),
+ };
+
+ std::vector<u8> argument_data(sizeof(arguments));
+ std::vector<u8> settings_data{2};
+ std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
+ queue_data.emplace_back(std::move(argument_data));
+ queue_data.emplace_back(std::move(settings_data));
+}
+
+void ILibraryAppletSelfAccessor::PushInShowCabinetData() {
+ const Applets::CommonArguments arguments{
+ .arguments_version = Applets::CommonArgumentVersion::Version3,
+ .size = Applets::CommonArgumentSize::Version3,
+ .library_version = static_cast<u32>(Applets::CabinetAppletVersion::Version1),
+ .theme_color = Applets::ThemeColor::BasicBlack,
+ .play_startup_sound = true,
+ .system_tick = system.CoreTiming().GetClockTicks(),
+ };
+
+ const Applets::StartParamForAmiiboSettings amiibo_settings{
+ .param_1 = 0,
+ .applet_mode = system.GetAppletManager().GetCabinetMode(),
+ .flags = Applets::CabinetFlags::None,
+ .amiibo_settings_1 = 0,
+ .device_handle = 0,
+ .tag_info{},
+ .register_info{},
+ .amiibo_settings_3{},
+ };
+
+ std::vector<u8> argument_data(sizeof(arguments));
+ std::vector<u8> settings_data(sizeof(amiibo_settings));
+ std::memcpy(argument_data.data(), &arguments, sizeof(arguments));
+ std::memcpy(settings_data.data(), &amiibo_settings, sizeof(amiibo_settings));
+ queue_data.emplace_back(std::move(argument_data));
+ queue_data.emplace_back(std::move(settings_data));
+}
+
void ILibraryAppletSelfAccessor::PushInShowMiiEditData() {
struct MiiEditV3 {
Applets::MiiEditAppletInputCommon common;
@@ -2094,9 +2333,6 @@ void IApplicationFunctions::PrepareForJit(HLERequestContext& ctx) {
void LoopProcess(Nvnflinger::Nvnflinger& nvnflinger, Core::System& system) {
auto message_queue = std::make_shared<AppletMessageQueue>(system);
- // Needed on game boot
- message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
-
auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService(
@@ -2238,7 +2474,7 @@ void IProcessWindingController::GetLaunchReason(HLERequestContext& ctx) {
}
void IProcessWindingController::OpenCallingLibraryApplet(HLERequestContext& ctx) {
- const auto applet_id = Applets::AppletId::MiiEdit;
+ const auto applet_id = system.GetAppletManager().GetCurrentAppletId();
const auto applet_mode = Applets::LibraryAppletMode::AllForeground;
LOG_WARNING(Service_AM, "(STUBBED) called with applet_id={:08X}, applet_mode={:08X}", applet_id,
@@ -2259,4 +2495,5 @@ void IProcessWindingController::OpenCallingLibraryApplet(HLERequestContext& ctx)
rb.Push(ResultSuccess);
rb.PushIpcInterface<ILibraryAppletAccessor>(system, applet);
}
+
} // 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:
~IDisplayController() override;
private:
+ void GetCallerAppletCaptureImageEx(HLERequestContext& ctx);
void TakeScreenShotOfOwnLayer(HLERequestContext& ctx);
+ void AcquireCallerAppletCaptureSharedBuffer(HLERequestContext& ctx);
+ void ReleaseCallerAppletCaptureSharedBuffer(HLERequestContext& ctx);
};
class IDebugFunctions final : public ServiceFramework<IDebugFunctions> {
@@ -150,9 +153,13 @@ private:
void SetRestartMessageEnabled(HLERequestContext& ctx);
void SetOutOfFocusSuspendingEnabled(HLERequestContext& ctx);
void SetAlbumImageOrientation(HLERequestContext& ctx);
+ void IsSystemBufferSharingEnabled(HLERequestContext& ctx);
+ void GetSystemSharedBufferHandle(HLERequestContext& ctx);
+ void GetSystemSharedLayerHandle(HLERequestContext& ctx);
void CreateManagedDisplayLayer(HLERequestContext& ctx);
void CreateManagedDisplaySeparableLayer(HLERequestContext& ctx);
void SetHandlesRequestToDisplay(HLERequestContext& ctx);
+ void ApproveToDisplay(HLERequestContext& ctx);
void SetIdleTimeDetectionExtension(HLERequestContext& ctx);
void GetIdleTimeDetectionExtension(HLERequestContext& ctx);
void ReportUserIsActive(HLERequestContext& ctx);
@@ -164,6 +171,8 @@ private:
void SaveCurrentScreenshot(HLERequestContext& ctx);
void SetRecordVolumeMuted(HLERequestContext& ctx);
+ Result EnsureBufferSharingEnabled();
+
enum class ScreenshotPermission : u32 {
Inherit = 0,
Enable = 1,
@@ -179,10 +188,30 @@ private:
u32 idle_time_detection_extension = 0;
u64 num_fatal_sections_entered = 0;
+ u64 system_shared_buffer_id = 0;
+ u64 system_shared_layer_id = 0;
bool is_auto_sleep_disabled = false;
+ bool buffer_sharing_enabled = false;
ScreenshotPermission screenshot_permission = ScreenshotPermission::Inherit;
};
+class ILockAccessor final : public ServiceFramework<ILockAccessor> {
+public:
+ explicit ILockAccessor(Core::System& system_);
+ ~ILockAccessor() override;
+
+private:
+ void TryLock(HLERequestContext& ctx);
+ void Unlock(HLERequestContext& ctx);
+ void GetEvent(HLERequestContext& ctx);
+ void IsLocked(HLERequestContext& ctx);
+
+ bool is_locked{};
+
+ Kernel::KEvent* lock_event;
+ KernelHelpers::ServiceContext service_context;
+};
+
class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
public:
explicit ICommonStateGetter(Core::System& system_,
@@ -223,6 +252,9 @@ private:
void GetEventHandle(HLERequestContext& ctx);
void ReceiveMessage(HLERequestContext& ctx);
void GetCurrentFocusState(HLERequestContext& ctx);
+ void RequestToAcquireSleepLock(HLERequestContext& ctx);
+ void GetAcquiredSleepLockEvent(HLERequestContext& ctx);
+ void GetReaderLockAccessorEx(HLERequestContext& ctx);
void GetDefaultDisplayResolutionChangeEvent(HLERequestContext& ctx);
void GetOperationMode(HLERequestContext& ctx);
void GetPerformanceMode(HLERequestContext& ctx);
@@ -234,12 +266,15 @@ private:
void EndVrModeEx(HLERequestContext& ctx);
void GetDefaultDisplayResolution(HLERequestContext& ctx);
void SetCpuBoostMode(HLERequestContext& ctx);
+ void GetBuiltInDisplayType(HLERequestContext& ctx);
void PerformSystemButtonPressingIfInFocus(HLERequestContext& ctx);
void GetSettingsPlatformRegion(HLERequestContext& ctx);
void SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(HLERequestContext& ctx);
std::shared_ptr<AppletMessageQueue> msg_queue;
bool vr_mode_state{};
+ Kernel::KEvent* sleep_lock_event;
+ KernelHelpers::ServiceContext service_context;
};
class IStorageImpl {
@@ -311,6 +346,8 @@ private:
void ExitProcessAndReturn(HLERequestContext& ctx);
void GetCallerAppletIdentityInfo(HLERequestContext& ctx);
+ void PushInShowAlbum();
+ void PushInShowCabinetData();
void PushInShowMiiEditData();
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:
{11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"},
{20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"},
{21, &ILibraryAppletProxy::GetAppletCommonFunctions, "GetAppletCommonFunctions"},
- {22, nullptr, "GetHomeMenuFunctions"},
- {23, nullptr, "GetGlobalStateController"},
+ {22, &ILibraryAppletProxy::GetHomeMenuFunctions, "GetHomeMenuFunctions"},
+ {23, &ILibraryAppletProxy::GetGlobalStateController, "GetGlobalStateController"},
{1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"},
};
// clang-format on
@@ -110,6 +110,22 @@ private:
rb.PushIpcInterface<IAppletCommonFunctions>(system);
}
+ void GetHomeMenuFunctions(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IHomeMenuFunctions>(system);
+ }
+
+ void GetGlobalStateController(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_AM, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IGlobalStateController>(system);
+ }
+
void GetDebugFunctions(HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
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 {
Version1 = 0x1,
};
+enum class CabinetFlags : u8 {
+ None = 0,
+ DeviceHandle = 1 << 0,
+ TagInfo = 1 << 1,
+ RegisterInfo = 1 << 2,
+ All = DeviceHandle | TagInfo | RegisterInfo,
+};
+DECLARE_ENUM_FLAG_OPERATORS(CabinetFlags)
+
enum class CabinetResult : u8 {
Cancel = 0,
TagInfo = 1 << 1,
@@ -51,7 +60,7 @@ static_assert(sizeof(AmiiboSettingsStartParam) == 0x30,
struct StartParamForAmiiboSettings {
u8 param_1;
Service::NFP::CabinetMode applet_mode;
- u8 flags;
+ CabinetFlags flags;
u8 amiibo_settings_1;
u64 device_handle;
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() {
CopyArgumentData(data, args->application_error);
error_code = Result(args->application_error.error_code);
break;
+ case ErrorAppletMode::ShowErrorPctl:
+ CopyArgumentData(data, args->error_record);
+ error_code = Decode64BitError(args->error_record.error_code_64);
+ break;
case ErrorAppletMode::ShowErrorRecord:
CopyArgumentData(data, args->error_record);
error_code = Decode64BitError(args->error_record.error_code_64);
@@ -191,6 +195,7 @@ void Error::Execute() {
frontend.ShowCustomErrorText(error_code, main_text_string, detail_text_string, callback);
break;
}
+ case ErrorAppletMode::ShowErrorPctl:
case ErrorAppletMode::ShowErrorRecord:
reporter.SaveErrorReport(title_id, error_code,
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() {
const auto data = broker.PeekDataToAppletForDebug();
system.GetReporter().SaveUnimplementedAppletReport(
- static_cast<u32>(id), common_args.arguments_version, common_args.library_version,
- common_args.theme_color, common_args.play_startup_sound, common_args.system_tick,
- data.normal, data.interactive);
+ static_cast<u32>(id), static_cast<u32>(common_args.arguments_version),
+ common_args.library_version, static_cast<u32>(common_args.theme_color),
+ common_args.play_startup_sound, common_args.system_tick, data.normal, data.interactive);
LogCurrentStorage(broker, "Initialize");
}
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 {
return frontend;
}
+NFP::CabinetMode AppletManager::GetCabinetMode() const {
+ return cabinet_mode;
+}
+
+AppletId AppletManager::GetCurrentAppletId() const {
+ return current_applet_id;
+}
+
void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
if (set.cabinet != nullptr) {
frontend.cabinet = std::move(set.cabinet);
@@ -237,6 +245,14 @@ void AppletManager::SetAppletFrontendSet(AppletFrontendSet set) {
}
}
+void AppletManager::SetCabinetMode(NFP::CabinetMode mode) {
+ cabinet_mode = mode;
+}
+
+void AppletManager::SetCurrentAppletId(AppletId applet_id) {
+ current_applet_id = applet_id;
+}
+
void AppletManager::SetDefaultAppletFrontendSet() {
ClearAll();
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;
class KReadableEvent;
} // namespace Kernel
+namespace Service::NFP {
+enum class CabinetMode : u8;
+} // namespace Service::NFP
+
namespace Service::AM {
class IStorage;
@@ -41,6 +45,8 @@ class IStorage;
namespace Applets {
enum class AppletId : u32 {
+ None = 0x00,
+ Application = 0x01,
OverlayDisplay = 0x02,
QLaunch = 0x03,
Starter = 0x04,
@@ -71,6 +77,32 @@ enum class LibraryAppletMode : u32 {
AllForegroundInitiallyHidden = 4,
};
+enum class CommonArgumentVersion : u32 {
+ Version0,
+ Version1,
+ Version2,
+ Version3,
+};
+
+enum class CommonArgumentSize : u32 {
+ Version3 = 0x20,
+};
+
+enum class ThemeColor : u32 {
+ BasicWhite = 0,
+ BasicBlack = 3,
+};
+
+struct CommonArguments {
+ CommonArgumentVersion arguments_version;
+ CommonArgumentSize size;
+ u32 library_version;
+ ThemeColor theme_color;
+ bool play_startup_sound;
+ u64_le system_tick;
+};
+static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size.");
+
class AppletDataBroker final {
public:
explicit AppletDataBroker(Core::System& system_, LibraryAppletMode applet_mode_);
@@ -161,16 +193,6 @@ public:
}
protected:
- struct CommonArguments {
- u32_le arguments_version;
- u32_le size;
- u32_le library_version;
- u32_le theme_color;
- bool play_startup_sound;
- u64_le system_tick;
- };
- static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size.");
-
CommonArguments common_args{};
AppletDataBroker broker;
LibraryAppletMode applet_mode;
@@ -219,8 +241,12 @@ public:
~AppletManager();
const AppletFrontendSet& GetAppletFrontendSet() const;
+ NFP::CabinetMode GetCabinetMode() const;
+ AppletId GetCurrentAppletId() const;
void SetAppletFrontendSet(AppletFrontendSet set);
+ void SetCabinetMode(NFP::CabinetMode mode);
+ void SetCurrentAppletId(AppletId applet_id);
void SetDefaultAppletFrontendSet();
void SetDefaultAppletsIfMissing();
void ClearAll();
@@ -228,6 +254,9 @@ public:
std::shared_ptr<Applet> GetApplet(AppletId id, LibraryAppletMode mode) const;
private:
+ AppletId current_applet_id{};
+ NFP::CabinetMode cabinet_mode{};
+
AppletFrontendSet frontend;
Core::System& system;
};
diff --git a/src/core/hle/service/caps/caps.cpp b/src/core/hle/service/caps/caps.cpp
index 610fe9940..286f9fd10 100644
--- a/src/core/hle/service/caps/caps.cpp
+++ b/src/core/hle/service/caps/caps.cpp
@@ -4,6 +4,7 @@
#include "core/hle/service/caps/caps.h"
#include "core/hle/service/caps/caps_a.h"
#include "core/hle/service/caps/caps_c.h"
+#include "core/hle/service/caps/caps_manager.h"
#include "core/hle/service/caps/caps_sc.h"
#include "core/hle/service/caps/caps_ss.h"
#include "core/hle/service/caps/caps_su.h"
@@ -15,13 +16,21 @@ namespace Service::Capture {
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
+ auto album_manager = std::make_shared<AlbumManager>();
+
+ server_manager->RegisterNamedService(
+ "caps:a", std::make_shared<IAlbumAccessorService>(system, album_manager));
+ server_manager->RegisterNamedService(
+ "caps:c", std::make_shared<IAlbumControlService>(system, album_manager));
+ server_manager->RegisterNamedService(
+ "caps:u", std::make_shared<IAlbumApplicationService>(system, album_manager));
+
+ server_manager->RegisterNamedService("caps:ss", std::make_shared<IScreenShotService>(system));
+ server_manager->RegisterNamedService("caps:sc",
+ std::make_shared<IScreenShotControlService>(system));
+ server_manager->RegisterNamedService("caps:su",
+ std::make_shared<IScreenShotApplicationService>(system));
- server_manager->RegisterNamedService("caps:a", std::make_shared<CAPS_A>(system));
- server_manager->RegisterNamedService("caps:c", std::make_shared<CAPS_C>(system));
- server_manager->RegisterNamedService("caps:u", std::make_shared<CAPS_U>(system));
- server_manager->RegisterNamedService("caps:sc", std::make_shared<CAPS_SC>(system));
- server_manager->RegisterNamedService("caps:ss", std::make_shared<CAPS_SS>(system));
- server_manager->RegisterNamedService("caps:su", std::make_shared<CAPS_SU>(system));
ServerManager::RunServer(std::move(server_manager));
}
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 @@
#pragma once
-#include "common/common_funcs.h"
-#include "common/common_types.h"
-
namespace Core {
class System;
}
-namespace Service::SM {
-class ServiceManager;
-}
-
namespace Service::Capture {
-enum class AlbumImageOrientation {
- Orientation0 = 0,
- Orientation1 = 1,
- Orientation2 = 2,
- Orientation3 = 3,
-};
-
-enum class AlbumReportOption : s32 {
- Disable = 0,
- Enable = 1,
-};
-
-enum class ContentType : u8 {
- Screenshot = 0,
- Movie = 1,
- ExtraMovie = 3,
-};
-
-enum class AlbumStorage : u8 {
- NAND = 0,
- SD = 1,
-};
-
-struct AlbumFileDateTime {
- s16 year{};
- s8 month{};
- s8 day{};
- s8 hour{};
- s8 minute{};
- s8 second{};
- s8 uid{};
-};
-static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime has incorrect size.");
-
-struct AlbumEntry {
- u64 size{};
- u64 application_id{};
- AlbumFileDateTime datetime{};
- AlbumStorage storage{};
- ContentType content{};
- INSERT_PADDING_BYTES(6);
-};
-static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry has incorrect size.");
-
-struct AlbumFileEntry {
- u64 size{}; // Size of the entry
- u64 hash{}; // AES256 with hardcoded key over AlbumEntry
- AlbumFileDateTime datetime{};
- AlbumStorage storage{};
- ContentType content{};
- INSERT_PADDING_BYTES(5);
- u8 unknown{1}; // Set to 1 on official SW
-};
-static_assert(sizeof(AlbumFileEntry) == 0x20, "AlbumFileEntry has incorrect size.");
-
-struct ApplicationAlbumEntry {
- u64 size{}; // Size of the entry
- u64 hash{}; // AES256 with hardcoded key over AlbumEntry
- AlbumFileDateTime datetime{};
- AlbumStorage storage{};
- ContentType content{};
- INSERT_PADDING_BYTES(5);
- u8 unknown{1}; // Set to 1 on official SW
-};
-static_assert(sizeof(ApplicationAlbumEntry) == 0x20, "ApplicationAlbumEntry has incorrect size.");
-
-struct ApplicationAlbumFileEntry {
- ApplicationAlbumEntry entry{};
- AlbumFileDateTime datetime{};
- u64 unknown{};
-};
-static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30,
- "ApplicationAlbumFileEntry has incorrect size.");
-
void LoopProcess(Core::System& system);
} // 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..e22f72bf6 100644
--- a/src/core/hle/service/caps/caps_a.cpp
+++ b/src/core/hle/service/caps/caps_a.cpp
@@ -1,40 +1,26 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/logging/log.h"
#include "core/hle/service/caps/caps_a.h"
+#include "core/hle/service/caps/caps_manager.h"
+#include "core/hle/service/caps/caps_result.h"
+#include "core/hle/service/caps/caps_types.h"
+#include "core/hle/service/ipc_helpers.h"
namespace Service::Capture {
-class IAlbumAccessorSession final : public ServiceFramework<IAlbumAccessorSession> {
-public:
- explicit IAlbumAccessorSession(Core::System& system_)
- : ServiceFramework{system_, "IAlbumAccessorSession"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {2001, nullptr, "OpenAlbumMovieReadStream"},
- {2002, nullptr, "CloseAlbumMovieReadStream"},
- {2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
- {2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
- {2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
- {2006, nullptr, "GetAlbumMovieReadStreamImageDataSize"},
- {2007, nullptr, "ReadImageDataFromAlbumMovieReadStream"},
- {2008, nullptr, "ReadFileAttributeFromAlbumMovieReadStream"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-};
-
-CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
+IAlbumAccessorService::IAlbumAccessorService(Core::System& system_,
+ std::shared_ptr<AlbumManager> album_manager)
+ : ServiceFramework{system_, "caps:a"}, manager{album_manager} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetAlbumFileCount"},
{1, nullptr, "GetAlbumFileList"},
{2, nullptr, "LoadAlbumFile"},
- {3, nullptr, "DeleteAlbumFile"},
+ {3, &IAlbumAccessorService::DeleteAlbumFile, "DeleteAlbumFile"},
{4, nullptr, "StorageCopyAlbumFile"},
- {5, nullptr, "IsAlbumMounted"},
+ {5, &IAlbumAccessorService::IsAlbumMounted, "IsAlbumMounted"},
{6, nullptr, "GetAlbumUsage"},
{7, nullptr, "GetAlbumFileSize"},
{8, nullptr, "LoadAlbumFileThumbnail"},
@@ -47,18 +33,18 @@ CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
{15, nullptr, "GetAlbumUsage3"},
{16, nullptr, "GetAlbumMountResult"},
{17, nullptr, "GetAlbumUsage16"},
- {18, nullptr, "Unknown18"},
+ {18, &IAlbumAccessorService::Unknown18, "Unknown18"},
{19, nullptr, "Unknown19"},
{100, nullptr, "GetAlbumFileCountEx0"},
- {101, nullptr, "GetAlbumFileListEx0"},
+ {101, &IAlbumAccessorService::GetAlbumFileListEx0, "GetAlbumFileListEx0"},
{202, nullptr, "SaveEditedScreenShot"},
{301, nullptr, "GetLastThumbnail"},
{302, nullptr, "GetLastOverlayMovieThumbnail"},
- {401, nullptr, "GetAutoSavingStorage"},
+ {401, &IAlbumAccessorService::GetAutoSavingStorage, "GetAutoSavingStorage"},
{501, nullptr, "GetRequiredStorageSpaceSizeToCopyAll"},
{1001, nullptr, "LoadAlbumScreenShotThumbnailImageEx0"},
- {1002, nullptr, "LoadAlbumScreenShotImageEx1"},
- {1003, nullptr, "LoadAlbumScreenShotThumbnailImageEx1"},
+ {1002, &IAlbumAccessorService::LoadAlbumScreenShotImageEx1, "LoadAlbumScreenShotImageEx1"},
+ {1003, &IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1, "LoadAlbumScreenShotThumbnailImageEx1"},
{8001, nullptr, "ForceAlbumUnmounted"},
{8002, nullptr, "ResetAlbumMountStatus"},
{8011, nullptr, "RefreshAlbumCache"},
@@ -74,6 +60,199 @@ CAPS_A::CAPS_A(Core::System& system_) : ServiceFramework{system_, "caps:a"} {
RegisterHandlers(functions);
}
-CAPS_A::~CAPS_A() = default;
+IAlbumAccessorService::~IAlbumAccessorService() = default;
+
+void IAlbumAccessorService::DeleteAlbumFile(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto file_id{rp.PopRaw<AlbumFileId>()};
+
+ LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}",
+ file_id.application_id, file_id.storage, file_id.type);
+
+ Result result = manager->DeleteAlbumFile(file_id);
+ result = TranslateResult(result);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IAlbumAccessorService::IsAlbumMounted(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto storage{rp.PopEnum<AlbumStorage>()};
+
+ LOG_INFO(Service_Capture, "called, storage={}", storage);
+
+ Result result = manager->IsAlbumMounted(storage);
+ const bool is_mounted = result.IsSuccess();
+ result = TranslateResult(result);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push<u8>(is_mounted);
+}
+
+void IAlbumAccessorService::Unknown18(HLERequestContext& ctx) {
+ struct UnknownBuffer {
+ INSERT_PADDING_BYTES(0x10);
+ };
+ static_assert(sizeof(UnknownBuffer) == 0x10, "UnknownBuffer is an invalid size");
+
+ LOG_WARNING(Service_Capture, "(STUBBED) called");
+
+ std::vector<UnknownBuffer> buffer{};
+
+ if (!buffer.empty()) {
+ ctx.WriteBuffer(buffer);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<u32>(buffer.size()));
+}
+
+void IAlbumAccessorService::GetAlbumFileListEx0(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto storage{rp.PopEnum<AlbumStorage>()};
+ const auto flags{rp.Pop<u8>()};
+ const auto album_entry_size{ctx.GetWriteBufferNumElements<AlbumEntry>()};
+
+ LOG_INFO(Service_Capture, "called, storage={}, flags={}", storage, flags);
+
+ std::vector<AlbumEntry> entries;
+ Result result = manager->GetAlbumFileList(entries, storage, flags);
+ result = TranslateResult(result);
+
+ entries.resize(std::min(album_entry_size, entries.size()));
+
+ if (!entries.empty()) {
+ ctx.WriteBuffer(entries);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push(entries.size());
+}
+
+void IAlbumAccessorService::GetAutoSavingStorage(HLERequestContext& ctx) {
+ LOG_WARNING(Service_Capture, "(STUBBED) called");
+
+ bool is_autosaving{};
+ Result result = manager->GetAutoSavingStorage(is_autosaving);
+ result = TranslateResult(result);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push<u8>(is_autosaving);
+}
+
+void IAlbumAccessorService::LoadAlbumScreenShotImageEx1(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto file_id{rp.PopRaw<AlbumFileId>()};
+ const auto decoder_options{rp.PopRaw<ScreenShotDecodeOption>()};
+ const auto image_buffer_size{ctx.GetWriteBufferSize(1)};
+
+ LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}",
+ file_id.application_id, file_id.storage, file_id.type, decoder_options.flags);
+
+ std::vector<u8> image;
+ LoadAlbumScreenShotImageOutput image_output;
+ Result result =
+ manager->LoadAlbumScreenShotImage(image_output, image, file_id, decoder_options);
+ result = TranslateResult(result);
+
+ if (image.size() > image_buffer_size) {
+ result = ResultWorkMemoryError;
+ }
+
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(image_output, 0);
+ ctx.WriteBuffer(image, 1);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto file_id{rp.PopRaw<AlbumFileId>()};
+ const auto decoder_options{rp.PopRaw<ScreenShotDecodeOption>()};
+
+ LOG_INFO(Service_Capture, "called, application_id=0x{:0x}, storage={}, type={}, flags={}",
+ file_id.application_id, file_id.storage, file_id.type, decoder_options.flags);
+
+ std::vector<u8> image(ctx.GetWriteBufferSize(1));
+ LoadAlbumScreenShotImageOutput image_output;
+ Result result =
+ manager->LoadAlbumScreenShotThumbnail(image_output, image, file_id, decoder_options);
+ result = TranslateResult(result);
+
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(image_output, 0);
+ ctx.WriteBuffer(image, 1);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+Result IAlbumAccessorService::TranslateResult(Result in_result) {
+ if (in_result.IsSuccess()) {
+ return in_result;
+ }
+
+ if ((in_result.raw & 0x3801ff) == ResultUnknown1024.raw) {
+ if (in_result.description - 0x514 < 100) {
+ return ResultInvalidFileData;
+ }
+ if (in_result.description - 0x5dc < 100) {
+ return ResultInvalidFileData;
+ }
+
+ if (in_result.description - 0x578 < 100) {
+ if (in_result == ResultFileCountLimit) {
+ return ResultUnknown22;
+ }
+ return ResultUnknown25;
+ }
+
+ if (in_result.raw < ResultUnknown1801.raw) {
+ if (in_result == ResultUnknown1202) {
+ return ResultUnknown810;
+ }
+ if (in_result == ResultUnknown1203) {
+ return ResultUnknown810;
+ }
+ if (in_result == ResultUnknown1701) {
+ return ResultUnknown5;
+ }
+ } else if (in_result.raw < ResultUnknown1803.raw) {
+ if (in_result == ResultUnknown1801) {
+ return ResultUnknown5;
+ }
+ if (in_result == ResultUnknown1802) {
+ return ResultUnknown6;
+ }
+ } else {
+ if (in_result == ResultUnknown1803) {
+ return ResultUnknown7;
+ }
+ if (in_result == ResultUnknown1804) {
+ return ResultOutOfRange;
+ }
+ }
+ return ResultUnknown1024;
+ }
+
+ if (in_result.module == ErrorModule::FS) {
+ if ((in_result.description >> 0xc < 0x7d) || (in_result.description - 1000 < 2000) ||
+ (((in_result.description - 3000) >> 3) < 0x271)) {
+ // TODO: Translate FS error
+ return in_result;
+ }
+ }
+
+ return in_result;
+}
} // 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;
}
namespace Service::Capture {
+class AlbumManager;
-class CAPS_A final : public ServiceFramework<CAPS_A> {
+class IAlbumAccessorService final : public ServiceFramework<IAlbumAccessorService> {
public:
- explicit CAPS_A(Core::System& system_);
- ~CAPS_A() override;
+ explicit IAlbumAccessorService(Core::System& system_,
+ std::shared_ptr<AlbumManager> album_manager);
+ ~IAlbumAccessorService() override;
+
+private:
+ void DeleteAlbumFile(HLERequestContext& ctx);
+ void IsAlbumMounted(HLERequestContext& ctx);
+ void Unknown18(HLERequestContext& ctx);
+ void GetAlbumFileListEx0(HLERequestContext& ctx);
+ void GetAutoSavingStorage(HLERequestContext& ctx);
+ void LoadAlbumScreenShotImageEx1(HLERequestContext& ctx);
+ void LoadAlbumScreenShotThumbnailImageEx1(HLERequestContext& ctx);
+
+ Result TranslateResult(Result in_result);
+
+ std::shared_ptr<AlbumManager> manager = nullptr;
};
} // 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 @@
#include "common/logging/log.h"
#include "core/hle/service/caps/caps_c.h"
+#include "core/hle/service/caps/caps_manager.h"
+#include "core/hle/service/caps/caps_result.h"
+#include "core/hle/service/caps/caps_types.h"
#include "core/hle/service/ipc_helpers.h"
namespace Service::Capture {
-class IAlbumControlSession final : public ServiceFramework<IAlbumControlSession> {
-public:
- explicit IAlbumControlSession(Core::System& system_)
- : ServiceFramework{system_, "IAlbumControlSession"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {2001, nullptr, "OpenAlbumMovieReadStream"},
- {2002, nullptr, "CloseAlbumMovieReadStream"},
- {2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
- {2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
- {2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
- {2006, nullptr, "GetAlbumMovieReadStreamImageDataSize"},
- {2007, nullptr, "ReadImageDataFromAlbumMovieReadStream"},
- {2008, nullptr, "ReadFileAttributeFromAlbumMovieReadStream"},
- {2401, nullptr, "OpenAlbumMovieWriteStream"},
- {2402, nullptr, "FinishAlbumMovieWriteStream"},
- {2403, nullptr, "CommitAlbumMovieWriteStream"},
- {2404, nullptr, "DiscardAlbumMovieWriteStream"},
- {2405, nullptr, "DiscardAlbumMovieWriteStreamNoDelete"},
- {2406, nullptr, "CommitAlbumMovieWriteStreamEx"},
- {2411, nullptr, "StartAlbumMovieWriteStreamDataSection"},
- {2412, nullptr, "EndAlbumMovieWriteStreamDataSection"},
- {2413, nullptr, "StartAlbumMovieWriteStreamMetaSection"},
- {2414, nullptr, "EndAlbumMovieWriteStreamMetaSection"},
- {2421, nullptr, "ReadDataFromAlbumMovieWriteStream"},
- {2422, nullptr, "WriteDataToAlbumMovieWriteStream"},
- {2424, nullptr, "WriteMetaToAlbumMovieWriteStream"},
- {2431, nullptr, "GetAlbumMovieWriteStreamBrokenReason"},
- {2433, nullptr, "GetAlbumMovieWriteStreamDataSize"},
- {2434, nullptr, "SetAlbumMovieWriteStreamDataSize"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-};
-
-CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} {
+IAlbumControlService::IAlbumControlService(Core::System& system_,
+ std::shared_ptr<AlbumManager> album_manager)
+ : ServiceFramework{system_, "caps:c"}, manager{album_manager} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "CaptureRawImage"},
{2, nullptr, "CaptureRawImageWithTimeout"},
- {33, &CAPS_C::SetShimLibraryVersion, "SetShimLibraryVersion"},
+ {33, &IAlbumControlService::SetShimLibraryVersion, "SetShimLibraryVersion"},
{1001, nullptr, "RequestTakingScreenShot"},
{1002, nullptr, "RequestTakingScreenShotWithTimeout"},
{1011, nullptr, "NotifyTakingScreenShotRefused"},
@@ -72,9 +40,9 @@ CAPS_C::CAPS_C(Core::System& system_) : ServiceFramework{system_, "caps:c"} {
RegisterHandlers(functions);
}
-CAPS_C::~CAPS_C() = default;
+IAlbumControlService::~IAlbumControlService() = default;
-void CAPS_C::SetShimLibraryVersion(HLERequestContext& ctx) {
+void IAlbumControlService::SetShimLibraryVersion(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto library_version{rp.Pop<u64>()};
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;
}
namespace Service::Capture {
+class AlbumManager;
-class CAPS_C final : public ServiceFramework<CAPS_C> {
+class IAlbumControlService final : public ServiceFramework<IAlbumControlService> {
public:
- explicit CAPS_C(Core::System& system_);
- ~CAPS_C() override;
+ explicit IAlbumControlService(Core::System& system_,
+ std::shared_ptr<AlbumManager> album_manager);
+ ~IAlbumControlService() override;
private:
void SetShimLibraryVersion(HLERequestContext& ctx);
+
+ std::shared_ptr<AlbumManager> manager = nullptr;
};
} // 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..2df6a930a
--- /dev/null
+++ b/src/core/hle/service/caps/caps_manager.cpp
@@ -0,0 +1,342 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <sstream>
+#include <stb_image.h>
+#include <stb_image_resize.h>
+
+#include "common/fs/file.h"
+#include "common/fs/path_util.h"
+#include "common/logging/log.h"
+#include "core/hle/service/caps/caps_manager.h"
+#include "core/hle/service/caps/caps_result.h"
+
+namespace Service::Capture {
+
+AlbumManager::AlbumManager() {}
+
+AlbumManager::~AlbumManager() = default;
+
+Result AlbumManager::DeleteAlbumFile(const AlbumFileId& file_id) {
+ if (file_id.storage > AlbumStorage::Sd) {
+ return ResultInvalidStorage;
+ }
+
+ if (!is_mounted) {
+ return ResultIsNotMounted;
+ }
+
+ std::filesystem::path path;
+ const auto result = GetFile(path, file_id);
+
+ if (result.IsError()) {
+ return result;
+ }
+
+ if (!Common::FS::RemoveFile(path)) {
+ return ResultFileNotFound;
+ }
+
+ return ResultSuccess;
+}
+
+Result AlbumManager::IsAlbumMounted(AlbumStorage storage) {
+ if (storage > AlbumStorage::Sd) {
+ return ResultInvalidStorage;
+ }
+
+ is_mounted = true;
+
+ if (storage == AlbumStorage::Sd) {
+ FindScreenshots();
+ }
+
+ return is_mounted ? ResultSuccess : ResultIsNotMounted;
+}
+
+Result AlbumManager::GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
+ u8 flags) const {
+ if (storage > AlbumStorage::Sd) {
+ return ResultInvalidStorage;
+ }
+
+ if (!is_mounted) {
+ return ResultIsNotMounted;
+ }
+
+ for (auto& [file_id, path] : album_files) {
+ if (file_id.storage != storage) {
+ continue;
+ }
+ if (out_entries.size() >= SdAlbumFileLimit) {
+ break;
+ }
+
+ const auto entry_size = Common::FS::GetSize(path);
+ out_entries.push_back({
+ .entry_size = entry_size,
+ .file_id = file_id,
+ });
+ }
+
+ return ResultSuccess;
+}
+
+Result AlbumManager::GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
+ ContentType contex_type, AlbumFileDateTime start_date,
+ AlbumFileDateTime end_date, u64 aruid) const {
+ if (!is_mounted) {
+ return ResultIsNotMounted;
+ }
+
+ for (auto& [file_id, path] : album_files) {
+ if (file_id.type != contex_type) {
+ continue;
+ }
+
+ if (file_id.date > start_date) {
+ continue;
+ }
+
+ if (file_id.date < end_date) {
+ continue;
+ }
+
+ if (out_entries.size() >= SdAlbumFileLimit) {
+ break;
+ }
+
+ const auto entry_size = Common::FS::GetSize(path);
+ ApplicationAlbumFileEntry entry{.entry =
+ {
+ .size = entry_size,
+ .hash{},
+ .datetime = file_id.date,
+ .storage = file_id.storage,
+ .content = contex_type,
+ .unknown = 1,
+ },
+ .datetime = file_id.date,
+ .unknown = {}};
+ out_entries.push_back(entry);
+ }
+
+ return ResultSuccess;
+}
+
+Result AlbumManager::GetAutoSavingStorage(bool& out_is_autosaving) const {
+ out_is_autosaving = false;
+ return ResultSuccess;
+}
+
+Result AlbumManager::LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& out_image_output,
+ std::vector<u8>& out_image,
+ const AlbumFileId& file_id,
+ const ScreenShotDecodeOption& decoder_options) const {
+ if (file_id.storage > AlbumStorage::Sd) {
+ return ResultInvalidStorage;
+ }
+
+ if (!is_mounted) {
+ return ResultIsNotMounted;
+ }
+
+ out_image_output = {
+ .width = 1280,
+ .height = 720,
+ .attribute =
+ {
+ .unknown_0{},
+ .orientation = AlbumImageOrientation::None,
+ .unknown_1{},
+ .unknown_2{},
+ },
+ };
+
+ std::filesystem::path path;
+ const auto result = GetFile(path, file_id);
+
+ if (result.IsError()) {
+ return result;
+ }
+
+ out_image.resize(out_image_output.height * out_image_output.width * STBI_rgb_alpha);
+
+ return LoadImage(out_image, path, static_cast<int>(out_image_output.width),
+ +static_cast<int>(out_image_output.height), decoder_options.flags);
+}
+
+Result AlbumManager::LoadAlbumScreenShotThumbnail(
+ LoadAlbumScreenShotImageOutput& out_image_output, std::vector<u8>& out_image,
+ const AlbumFileId& file_id, const ScreenShotDecodeOption& decoder_options) const {
+ if (file_id.storage > AlbumStorage::Sd) {
+ return ResultInvalidStorage;
+ }
+
+ if (!is_mounted) {
+ return ResultIsNotMounted;
+ }
+
+ out_image_output = {
+ .width = 320,
+ .height = 180,
+ .attribute =
+ {
+ .unknown_0{},
+ .orientation = AlbumImageOrientation::None,
+ .unknown_1{},
+ .unknown_2{},
+ },
+ };
+
+ std::filesystem::path path;
+ const auto result = GetFile(path, file_id);
+
+ if (result.IsError()) {
+ return result;
+ }
+
+ out_image.resize(out_image_output.height * out_image_output.width * STBI_rgb_alpha);
+
+ return LoadImage(out_image, path, static_cast<int>(out_image_output.width),
+ +static_cast<int>(out_image_output.height), decoder_options.flags);
+}
+
+Result AlbumManager::GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const {
+ const auto file = album_files.find(file_id);
+
+ if (file == album_files.end()) {
+ return ResultFileNotFound;
+ }
+
+ out_path = file->second;
+ return ResultSuccess;
+}
+
+void AlbumManager::FindScreenshots() {
+ is_mounted = false;
+ album_files.clear();
+
+ // TODO: Swap this with a blocking operation.
+ const auto screenshots_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ScreenshotsDir);
+ Common::FS::IterateDirEntries(
+ screenshots_dir,
+ [this](const std::filesystem::path& full_path) {
+ AlbumEntry entry;
+ if (GetAlbumEntry(entry, full_path).IsError()) {
+ return true;
+ }
+ while (album_files.contains(entry.file_id)) {
+ if (++entry.file_id.date.unique_id == 0) {
+ break;
+ }
+ }
+ album_files[entry.file_id] = full_path;
+ return true;
+ },
+ Common::FS::DirEntryFilter::File);
+
+ is_mounted = true;
+}
+
+Result AlbumManager::GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const {
+ std::istringstream line_stream(path.filename().string());
+ std::string date;
+ std::string application;
+ std::string time;
+
+ // Parse filename to obtain entry properties
+ std::getline(line_stream, application, '_');
+ std::getline(line_stream, date, '_');
+ std::getline(line_stream, time, '_');
+
+ std::istringstream date_stream(date);
+ std::istringstream time_stream(time);
+ std::string year;
+ std::string month;
+ std::string day;
+ std::string hour;
+ std::string minute;
+ std::string second;
+
+ std::getline(date_stream, year, '-');
+ std::getline(date_stream, month, '-');
+ std::getline(date_stream, day, '-');
+
+ std::getline(time_stream, hour, '-');
+ std::getline(time_stream, minute, '-');
+ std::getline(time_stream, second, '-');
+
+ try {
+ out_entry = {
+ .entry_size = 1,
+ .file_id{
+ .application_id = static_cast<u64>(std::stoll(application, 0, 16)),
+ .date =
+ {
+ .year = static_cast<u16>(std::stoi(year)),
+ .month = static_cast<u8>(std::stoi(month)),
+ .day = static_cast<u8>(std::stoi(day)),
+ .hour = static_cast<u8>(std::stoi(hour)),
+ .minute = static_cast<u8>(std::stoi(minute)),
+ .second = static_cast<u8>(std::stoi(second)),
+ .unique_id = 0,
+ },
+ .storage = AlbumStorage::Sd,
+ .type = ContentType::Screenshot,
+ .unknown = 1,
+ },
+ };
+ } catch (const std::invalid_argument&) {
+ return ResultUnknown;
+ } catch (const std::out_of_range&) {
+ return ResultUnknown;
+ } catch (const std::exception&) {
+ return ResultUnknown;
+ }
+
+ return ResultSuccess;
+}
+
+Result AlbumManager::LoadImage(std::span<u8> out_image, const std::filesystem::path& path,
+ int width, int height, ScreenShotDecoderFlag flag) const {
+ if (out_image.size() != static_cast<std::size_t>(width * height * STBI_rgb_alpha)) {
+ return ResultUnknown;
+ }
+
+ const Common::FS::IOFile db_file{path, Common::FS::FileAccessMode::Read,
+ Common::FS::FileType::BinaryFile};
+
+ std::vector<u8> raw_file(db_file.GetSize());
+ if (db_file.Read(raw_file) != raw_file.size()) {
+ return ResultUnknown;
+ }
+
+ int filter_flag = STBIR_FILTER_DEFAULT;
+ int original_width, original_height, color_channels;
+ const auto dbi_image =
+ stbi_load_from_memory(raw_file.data(), static_cast<int>(raw_file.size()), &original_width,
+ &original_height, &color_channels, STBI_rgb_alpha);
+
+ if (dbi_image == nullptr) {
+ return ResultUnknown;
+ }
+
+ switch (flag) {
+ case ScreenShotDecoderFlag::EnableFancyUpsampling:
+ filter_flag = STBIR_FILTER_TRIANGLE;
+ break;
+ case ScreenShotDecoderFlag::EnableBlockSmoothing:
+ filter_flag = STBIR_FILTER_BOX;
+ break;
+ default:
+ filter_flag = STBIR_FILTER_DEFAULT;
+ break;
+ }
+
+ stbir_resize_uint8_srgb(dbi_image, original_width, original_height, 0, out_image.data(), width,
+ height, 0, STBI_rgb_alpha, 3, filter_flag);
+
+ return ResultSuccess;
+}
+} // 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..8337c655c
--- /dev/null
+++ b/src/core/hle/service/caps/caps_manager.h
@@ -0,0 +1,72 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <unordered_map>
+
+#include "common/fs/fs.h"
+#include "core/hle/result.h"
+#include "core/hle/service/caps/caps_types.h"
+
+namespace Core {
+class System;
+}
+
+namespace std {
+// Hash used to create lists from AlbumFileId data
+template <>
+struct hash<Service::Capture::AlbumFileId> {
+ size_t operator()(const Service::Capture::AlbumFileId& pad_id) const noexcept {
+ u64 hash_value = (static_cast<u64>(pad_id.date.year) << 8);
+ hash_value ^= (static_cast<u64>(pad_id.date.month) << 7);
+ hash_value ^= (static_cast<u64>(pad_id.date.day) << 6);
+ hash_value ^= (static_cast<u64>(pad_id.date.hour) << 5);
+ hash_value ^= (static_cast<u64>(pad_id.date.minute) << 4);
+ hash_value ^= (static_cast<u64>(pad_id.date.second) << 3);
+ hash_value ^= (static_cast<u64>(pad_id.date.unique_id) << 2);
+ hash_value ^= (static_cast<u64>(pad_id.storage) << 1);
+ hash_value ^= static_cast<u64>(pad_id.type);
+ return static_cast<size_t>(hash_value);
+ }
+};
+
+} // namespace std
+
+namespace Service::Capture {
+
+class AlbumManager {
+public:
+ explicit AlbumManager();
+ ~AlbumManager();
+
+ Result DeleteAlbumFile(const AlbumFileId& file_id);
+ Result IsAlbumMounted(AlbumStorage storage);
+ Result GetAlbumFileList(std::vector<AlbumEntry>& out_entries, AlbumStorage storage,
+ u8 flags) const;
+ Result GetAlbumFileList(std::vector<ApplicationAlbumFileEntry>& out_entries,
+ ContentType contex_type, AlbumFileDateTime start_date,
+ AlbumFileDateTime end_date, u64 aruid) const;
+ Result GetAutoSavingStorage(bool& out_is_autosaving) const;
+ Result LoadAlbumScreenShotImage(LoadAlbumScreenShotImageOutput& out_image_output,
+ std::vector<u8>& out_image, const AlbumFileId& file_id,
+ const ScreenShotDecodeOption& decoder_options) const;
+ Result LoadAlbumScreenShotThumbnail(LoadAlbumScreenShotImageOutput& out_image_output,
+ std::vector<u8>& out_image, const AlbumFileId& file_id,
+ const ScreenShotDecodeOption& decoder_options) const;
+
+private:
+ static constexpr std::size_t NandAlbumFileLimit = 1000;
+ static constexpr std::size_t SdAlbumFileLimit = 10000;
+
+ void FindScreenshots();
+ Result GetFile(std::filesystem::path& out_path, const AlbumFileId& file_id) const;
+ Result GetAlbumEntry(AlbumEntry& out_entry, const std::filesystem::path& path) const;
+ Result LoadImage(std::span<u8> out_image, const std::filesystem::path& path, int width,
+ int height, ScreenShotDecoderFlag flag) const;
+
+ bool is_mounted{};
+ std::unordered_map<AlbumFileId, std::filesystem::path> album_files;
+};
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::Capture {
+
+constexpr Result ResultWorkMemoryError(ErrorModule::Capture, 3);
+constexpr Result ResultUnknown5(ErrorModule::Capture, 5);
+constexpr Result ResultUnknown6(ErrorModule::Capture, 6);
+constexpr Result ResultUnknown7(ErrorModule::Capture, 7);
+constexpr Result ResultOutOfRange(ErrorModule::Capture, 8);
+constexpr Result ResulInvalidTimestamp(ErrorModule::Capture, 12);
+constexpr Result ResultInvalidStorage(ErrorModule::Capture, 13);
+constexpr Result ResultInvalidFileContents(ErrorModule::Capture, 14);
+constexpr Result ResultIsNotMounted(ErrorModule::Capture, 21);
+constexpr Result ResultUnknown22(ErrorModule::Capture, 22);
+constexpr Result ResultFileNotFound(ErrorModule::Capture, 23);
+constexpr Result ResultInvalidFileData(ErrorModule::Capture, 24);
+constexpr Result ResultUnknown25(ErrorModule::Capture, 25);
+constexpr Result ResultReadBufferShortage(ErrorModule::Capture, 30);
+constexpr Result ResultUnknown810(ErrorModule::Capture, 810);
+constexpr Result ResultUnknown1024(ErrorModule::Capture, 1024);
+constexpr Result ResultUnknown1202(ErrorModule::Capture, 1202);
+constexpr Result ResultUnknown1203(ErrorModule::Capture, 1203);
+constexpr Result ResultFileCountLimit(ErrorModule::Capture, 1401);
+constexpr Result ResultUnknown1701(ErrorModule::Capture, 1701);
+constexpr Result ResultUnknown1801(ErrorModule::Capture, 1801);
+constexpr Result ResultUnknown1802(ErrorModule::Capture, 1802);
+constexpr Result ResultUnknown1803(ErrorModule::Capture, 1803);
+constexpr Result ResultUnknown1804(ErrorModule::Capture, 1804);
+
+} // 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 @@
namespace Service::Capture {
-CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} {
+IScreenShotControlService::IScreenShotControlService(Core::System& system_)
+ : ServiceFramework{system_, "caps:sc"} {
// clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "CaptureRawImage"},
@@ -34,6 +35,6 @@ CAPS_SC::CAPS_SC(Core::System& system_) : ServiceFramework{system_, "caps:sc"} {
RegisterHandlers(functions);
}
-CAPS_SC::~CAPS_SC() = default;
+IScreenShotControlService::~IScreenShotControlService() = default;
} // 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;
namespace Service::Capture {
-class CAPS_SC final : public ServiceFramework<CAPS_SC> {
+class IScreenShotControlService final : public ServiceFramework<IScreenShotControlService> {
public:
- explicit CAPS_SC(Core::System& system_);
- ~CAPS_SC() override;
+ explicit IScreenShotControlService(Core::System& system_);
+ ~IScreenShotControlService() override;
};
} // 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 @@
namespace Service::Capture {
-CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} {
+IScreenShotService::IScreenShotService(Core::System& system_)
+ : ServiceFramework{system_, "caps:ss"} {
// clang-format off
static const FunctionInfo functions[] = {
{201, nullptr, "SaveScreenShot"},
@@ -21,6 +22,6 @@ CAPS_SS::CAPS_SS(Core::System& system_) : ServiceFramework{system_, "caps:ss"} {
RegisterHandlers(functions);
}
-CAPS_SS::~CAPS_SS() = default;
+IScreenShotService::~IScreenShotService() = default;
} // 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;
namespace Service::Capture {
-class CAPS_SS final : public ServiceFramework<CAPS_SS> {
+class IScreenShotService final : public ServiceFramework<IScreenShotService> {
public:
- explicit CAPS_SS(Core::System& system_);
- ~CAPS_SS() override;
+ explicit IScreenShotService(Core::System& system_);
+ ~IScreenShotService() override;
};
} // 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 @@
namespace Service::Capture {
-CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} {
+IScreenShotApplicationService::IScreenShotApplicationService(Core::System& system_)
+ : ServiceFramework{system_, "caps:su"} {
// clang-format off
static const FunctionInfo functions[] = {
- {32, &CAPS_SU::SetShimLibraryVersion, "SetShimLibraryVersion"},
+ {32, &IScreenShotApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"},
{201, nullptr, "SaveScreenShot"},
{203, nullptr, "SaveScreenShotEx0"},
{205, nullptr, "SaveScreenShotEx1"},
@@ -21,9 +22,9 @@ CAPS_SU::CAPS_SU(Core::System& system_) : ServiceFramework{system_, "caps:su"} {
RegisterHandlers(functions);
}
-CAPS_SU::~CAPS_SU() = default;
+IScreenShotApplicationService::~IScreenShotApplicationService() = default;
-void CAPS_SU::SetShimLibraryVersion(HLERequestContext& ctx) {
+void IScreenShotApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto library_version{rp.Pop<u64>()};
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;
namespace Service::Capture {
-class CAPS_SU final : public ServiceFramework<CAPS_SU> {
+class IScreenShotApplicationService final : public ServiceFramework<IScreenShotApplicationService> {
public:
- explicit CAPS_SU(Core::System& system_);
- ~CAPS_SU() override;
+ explicit IScreenShotApplicationService(Core::System& system_);
+ ~IScreenShotApplicationService() override;
private:
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..bf6061273
--- /dev/null
+++ b/src/core/hle/service/caps/caps_types.h
@@ -0,0 +1,184 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Service::Capture {
+
+// This is nn::album::ImageOrientation
+enum class AlbumImageOrientation {
+ None,
+ Rotate90,
+ Rotate180,
+ Rotate270,
+};
+
+// This is nn::album::AlbumReportOption
+enum class AlbumReportOption : s32 {
+ Disable,
+ Enable,
+};
+
+enum class ContentType : u8 {
+ Screenshot = 0,
+ Movie = 1,
+ ExtraMovie = 3,
+};
+
+enum class AlbumStorage : u8 {
+ Nand,
+ Sd,
+};
+
+enum class ScreenShotDecoderFlag : u64 {
+ None = 0,
+ EnableFancyUpsampling = 1 << 0,
+ EnableBlockSmoothing = 1 << 1,
+};
+
+// This is nn::capsrv::AlbumFileDateTime
+struct AlbumFileDateTime {
+ u16 year{};
+ u8 month{};
+ u8 day{};
+ u8 hour{};
+ u8 minute{};
+ u8 second{};
+ u8 unique_id{};
+
+ friend constexpr bool operator==(const AlbumFileDateTime&, const AlbumFileDateTime&) = default;
+ friend constexpr bool operator>(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
+ if (a.year > b.year) {
+ return true;
+ }
+ if (a.month > b.month) {
+ return true;
+ }
+ if (a.day > b.day) {
+ return true;
+ }
+ if (a.hour > b.hour) {
+ return true;
+ }
+ if (a.minute > b.minute) {
+ return true;
+ }
+ return a.second > b.second;
+ };
+ friend constexpr bool operator<(const AlbumFileDateTime& a, const AlbumFileDateTime& b) {
+ if (a.year < b.year) {
+ return true;
+ }
+ if (a.month < b.month) {
+ return true;
+ }
+ if (a.day < b.day) {
+ return true;
+ }
+ if (a.hour < b.hour) {
+ return true;
+ }
+ if (a.minute < b.minute) {
+ return true;
+ }
+ return a.second < b.second;
+ };
+};
+static_assert(sizeof(AlbumFileDateTime) == 0x8, "AlbumFileDateTime has incorrect size.");
+
+// This is nn::album::AlbumEntry
+struct AlbumFileEntry {
+ u64 size{}; // Size of the entry
+ u64 hash{}; // AES256 with hardcoded key over AlbumEntry
+ AlbumFileDateTime datetime{};
+ AlbumStorage storage{};
+ ContentType content{};
+ INSERT_PADDING_BYTES(5);
+ u8 unknown{}; // Set to 1 on official SW
+};
+static_assert(sizeof(AlbumFileEntry) == 0x20, "AlbumFileEntry has incorrect size.");
+
+struct AlbumFileId {
+ u64 application_id{};
+ AlbumFileDateTime date{};
+ AlbumStorage storage{};
+ ContentType type{};
+ INSERT_PADDING_BYTES(0x5);
+ u8 unknown{};
+
+ friend constexpr bool operator==(const AlbumFileId&, const AlbumFileId&) = default;
+};
+static_assert(sizeof(AlbumFileId) == 0x18, "AlbumFileId is an invalid size");
+
+// This is nn::capsrv::AlbumEntry
+struct AlbumEntry {
+ u64 entry_size{};
+ AlbumFileId file_id{};
+};
+static_assert(sizeof(AlbumEntry) == 0x20, "AlbumEntry has incorrect size.");
+
+// This is nn::capsrv::ApplicationAlbumEntry
+struct ApplicationAlbumEntry {
+ u64 size{}; // Size of the entry
+ u64 hash{}; // AES256 with hardcoded key over AlbumEntry
+ AlbumFileDateTime datetime{};
+ AlbumStorage storage{};
+ ContentType content{};
+ INSERT_PADDING_BYTES(5);
+ u8 unknown{1}; // Set to 1 on official SW
+};
+static_assert(sizeof(ApplicationAlbumEntry) == 0x20, "ApplicationAlbumEntry has incorrect size.");
+
+// This is nn::capsrv::ApplicationAlbumFileEntry
+struct ApplicationAlbumFileEntry {
+ ApplicationAlbumEntry entry{};
+ AlbumFileDateTime datetime{};
+ u64 unknown{};
+};
+static_assert(sizeof(ApplicationAlbumFileEntry) == 0x30,
+ "ApplicationAlbumFileEntry has incorrect size.");
+
+struct ApplicationData {
+ std::array<u8, 0x400> data{};
+ u32 data_size{};
+};
+static_assert(sizeof(ApplicationData) == 0x404, "ApplicationData is an invalid size");
+
+struct ScreenShotAttribute {
+ u32 unknown_0{};
+ AlbumImageOrientation orientation{};
+ u32 unknown_1{};
+ u32 unknown_2{};
+ INSERT_PADDING_BYTES(0x30);
+};
+static_assert(sizeof(ScreenShotAttribute) == 0x40, "ScreenShotAttribute is an invalid size");
+
+struct ScreenShotDecodeOption {
+ ScreenShotDecoderFlag flags{};
+ INSERT_PADDING_BYTES(0x18);
+};
+static_assert(sizeof(ScreenShotDecodeOption) == 0x20, "ScreenShotDecodeOption is an invalid size");
+
+struct LoadAlbumScreenShotImageOutput {
+ s64 width{};
+ s64 height{};
+ ScreenShotAttribute attribute{};
+ INSERT_PADDING_BYTES(0x400);
+};
+static_assert(sizeof(LoadAlbumScreenShotImageOutput) == 0x450,
+ "LoadAlbumScreenShotImageOutput is an invalid size");
+
+struct LoadAlbumScreenShotImageOutputForApplication {
+ s64 width{};
+ s64 height{};
+ ScreenShotAttribute attribute{};
+ ApplicationData data{};
+ INSERT_PADDING_BYTES(0xAC);
+};
+static_assert(sizeof(LoadAlbumScreenShotImageOutputForApplication) == 0x500,
+ "LoadAlbumScreenShotImageOutput is an invalid size");
+
+} // 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..260f25490 100644
--- a/src/core/hle/service/caps/caps_u.cpp
+++ b/src/core/hle/service/caps/caps_u.cpp
@@ -2,45 +2,29 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
-#include "core/hle/service/caps/caps.h"
+#include "core/hle/service/caps/caps_manager.h"
+#include "core/hle/service/caps/caps_types.h"
#include "core/hle/service/caps/caps_u.h"
#include "core/hle/service/ipc_helpers.h"
namespace Service::Capture {
-class IAlbumAccessorApplicationSession final
- : public ServiceFramework<IAlbumAccessorApplicationSession> {
-public:
- explicit IAlbumAccessorApplicationSession(Core::System& system_)
- : ServiceFramework{system_, "IAlbumAccessorApplicationSession"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {2001, nullptr, "OpenAlbumMovieReadStream"},
- {2002, nullptr, "CloseAlbumMovieReadStream"},
- {2003, nullptr, "GetAlbumMovieReadStreamMovieDataSize"},
- {2004, nullptr, "ReadMovieDataFromAlbumMovieReadStream"},
- {2005, nullptr, "GetAlbumMovieReadStreamBrokenReason"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-};
-
-CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} {
+IAlbumApplicationService::IAlbumApplicationService(Core::System& system_,
+ std::shared_ptr<AlbumManager> album_manager)
+ : ServiceFramework{system_, "caps:u"}, manager{album_manager} {
// clang-format off
static const FunctionInfo functions[] = {
- {32, &CAPS_U::SetShimLibraryVersion, "SetShimLibraryVersion"},
- {102, &CAPS_U::GetAlbumContentsFileListForApplication, "GetAlbumContentsFileListForApplication"},
- {103, nullptr, "DeleteAlbumContentsFileForApplication"},
- {104, nullptr, "GetAlbumContentsFileSizeForApplication"},
+ {32, &IAlbumApplicationService::SetShimLibraryVersion, "SetShimLibraryVersion"},
+ {102, &IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated, "GetAlbumFileList0AafeAruidDeprecated"},
+ {103, nullptr, "DeleteAlbumFileByAruid"},
+ {104, nullptr, "GetAlbumFileSizeByAruid"},
{105, nullptr, "DeleteAlbumFileByAruidForDebug"},
- {110, nullptr, "LoadAlbumContentsFileScreenShotImageForApplication"},
- {120, nullptr, "LoadAlbumContentsFileThumbnailImageForApplication"},
- {130, nullptr, "PrecheckToCreateContentsForApplication"},
+ {110, nullptr, "LoadAlbumScreenShotImageByAruid"},
+ {120, nullptr, "LoadAlbumScreenShotThumbnailImageByAruid"},
+ {130, nullptr, "PrecheckToCreateContentsByAruid"},
{140, nullptr, "GetAlbumFileList1AafeAruidDeprecated"},
{141, nullptr, "GetAlbumFileList2AafeUidAruidDeprecated"},
- {142, &CAPS_U::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"},
+ {142, &IAlbumApplicationService::GetAlbumFileList3AaeAruid, "GetAlbumFileList3AaeAruid"},
{143, nullptr, "GetAlbumFileList4AaeUidAruid"},
{144, nullptr, "GetAllAlbumFileList3AaeAruid"},
{60002, nullptr, "OpenAccessorSessionForApplication"},
@@ -50,9 +34,9 @@ CAPS_U::CAPS_U(Core::System& system_) : ServiceFramework{system_, "caps:u"} {
RegisterHandlers(functions);
}
-CAPS_U::~CAPS_U() = default;
+IAlbumApplicationService::~IAlbumApplicationService() = default;
-void CAPS_U::SetShimLibraryVersion(HLERequestContext& ctx) {
+void IAlbumApplicationService::SetShimLibraryVersion(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto library_version{rp.Pop<u64>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
@@ -64,10 +48,7 @@ void CAPS_U::SetShimLibraryVersion(HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
-void CAPS_U::GetAlbumContentsFileListForApplication(HLERequestContext& ctx) {
- // Takes a type-0x6 output buffer containing an array of ApplicationAlbumFileEntry, a PID, an
- // u8 ContentType, two s64s, and an u64 AppletResourceUserId. Returns an output u64 for total
- // output entries (which is copied to a s32 by official SW).
+void IAlbumApplicationService::GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto pid{rp.Pop<s32>()};
const auto content_type{rp.PopEnum<ContentType>()};
@@ -75,26 +56,49 @@ void CAPS_U::GetAlbumContentsFileListForApplication(HLERequestContext& ctx) {
const auto end_posix_time{rp.Pop<s64>()};
const auto applet_resource_user_id{rp.Pop<u64>()};
- // TODO: Update this when we implement the album.
- // Currently we do not have a method of accessing album entries, set this to 0 for now.
- constexpr u32 total_entries_1{};
- constexpr u32 total_entries_2{};
+ LOG_WARNING(Service_Capture,
+ "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, "
+ "end_posix_time={}, applet_resource_user_id={}",
+ pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id);
+
+ // TODO: Translate posix to DateTime
+
+ std::vector<ApplicationAlbumFileEntry> entries;
+ const Result result =
+ manager->GetAlbumFileList(entries, content_type, {}, {}, applet_resource_user_id);
- LOG_WARNING(
- Service_Capture,
- "(STUBBED) called. pid={}, content_type={}, start_posix_time={}, "
- "end_posix_time={}, applet_resource_user_id={}, total_entries_1={}, total_entries_2={}",
- pid, content_type, start_posix_time, end_posix_time, applet_resource_user_id,
- total_entries_1, total_entries_2);
+ if (!entries.empty()) {
+ ctx.WriteBuffer(entries);
+ }
IPC::ResponseBuilder rb{ctx, 4};
- rb.Push(ResultSuccess);
- rb.Push(total_entries_1);
- rb.Push(total_entries_2);
+ rb.Push(result);
+ rb.Push<u64>(entries.size());
}
-void CAPS_U::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
- GetAlbumContentsFileListForApplication(ctx);
+void IAlbumApplicationService::GetAlbumFileList3AaeAruid(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto pid{rp.Pop<s32>()};
+ const auto content_type{rp.PopEnum<ContentType>()};
+ const auto start_date_time{rp.PopRaw<AlbumFileDateTime>()};
+ const auto end_date_time{rp.PopRaw<AlbumFileDateTime>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_Capture,
+ "(STUBBED) called. pid={}, content_type={}, applet_resource_user_id={}", pid,
+ content_type, applet_resource_user_id);
+
+ std::vector<ApplicationAlbumFileEntry> entries;
+ const Result result = manager->GetAlbumFileList(entries, content_type, start_date_time,
+ end_date_time, applet_resource_user_id);
+
+ if (!entries.empty()) {
+ ctx.WriteBuffer(entries);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push<u64>(entries.size());
}
} // 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;
}
namespace Service::Capture {
+class AlbumManager;
-class CAPS_U final : public ServiceFramework<CAPS_U> {
+class IAlbumApplicationService final : public ServiceFramework<IAlbumApplicationService> {
public:
- explicit CAPS_U(Core::System& system_);
- ~CAPS_U() override;
+ explicit IAlbumApplicationService(Core::System& system_,
+ std::shared_ptr<AlbumManager> album_manager);
+ ~IAlbumApplicationService() override;
private:
void SetShimLibraryVersion(HLERequestContext& ctx);
- void GetAlbumContentsFileListForApplication(HLERequestContext& ctx);
+ void GetAlbumFileList0AafeAruidDeprecated(HLERequestContext& ctx);
void GetAlbumFileList3AaeAruid(HLERequestContext& ctx);
+
+ std::shared_ptr<AlbumManager> manager = nullptr;
};
} // 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_)
if (Settings::values.enable_fs_access_log) {
access_log_mode = AccessLogMode::SdCard;
}
+
+ // This should be true on creation
+ fsc.SetAutoSaveDataCreation(true);
}
FSP_SRV::~FSP_SRV() = default;
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:
explicit IMonitorService(Core::System& system_) : ServiceFramework{system_, "IMonitorService"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "GetStateForMonitor"},
+ {0, &IMonitorService::GetStateForMonitor, "GetStateForMonitor"},
{1, nullptr, "GetNetworkInfoForMonitor"},
{2, nullptr, "GetIpv4AddressForMonitor"},
{3, nullptr, "GetDisconnectReasonForMonitor"},
{4, nullptr, "GetSecurityParameterForMonitor"},
{5, nullptr, "GetNetworkConfigForMonitor"},
- {100, nullptr, "InitializeMonitor"},
+ {100, &IMonitorService::InitializeMonitor, "InitializeMonitor"},
{101, nullptr, "FinalizeMonitor"},
};
// clang-format on
RegisterHandlers(functions);
}
+
+private:
+ void GetStateForMonitor(HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(state);
+ }
+
+ void InitializeMonitor(HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
+
+ state = State::Initialized;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ State state{State::None};
};
class LDNM final : public ServiceFramework<LDNM> {
@@ -731,14 +751,81 @@ public:
}
};
+class ISfMonitorService final : public ServiceFramework<ISfMonitorService> {
+public:
+ explicit ISfMonitorService(Core::System& system_)
+ : ServiceFramework{system_, "ISfMonitorService"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &ISfMonitorService::Initialize, "Initialize"},
+ {288, &ISfMonitorService::GetGroupInfo, "GetGroupInfo"},
+ {320, nullptr, "GetLinkLevel"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void Initialize(HLERequestContext& ctx) {
+ LOG_WARNING(Service_LDN, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(0);
+ }
+
+ void GetGroupInfo(HLERequestContext& ctx) {
+ LOG_WARNING(Service_LDN, "(STUBBED) called");
+
+ struct GroupInfo {
+ std::array<u8, 0x200> info;
+ };
+
+ GroupInfo group_info{};
+
+ ctx.WriteBuffer(group_info);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+};
+
+class LP2PM final : public ServiceFramework<LP2PM> {
+public:
+ explicit LP2PM(Core::System& system_) : ServiceFramework{system_, "lp2p:m"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &LP2PM::CreateMonitorService, "CreateMonitorService"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+private:
+ void CreateMonitorService(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 reserved_input = rp.Pop<u64>();
+
+ LOG_INFO(Service_LDN, "called, reserved_input={}", reserved_input);
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<ISfMonitorService>(system);
+ }
+};
+
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
server_manager->RegisterNamedService("ldn:m", std::make_shared<LDNM>(system));
server_manager->RegisterNamedService("ldn:s", std::make_shared<LDNS>(system));
server_manager->RegisterNamedService("ldn:u", std::make_shared<LDNU>(system));
+
server_manager->RegisterNamedService("lp2p:app", std::make_shared<LP2PAPP>(system));
server_manager->RegisterNamedService("lp2p:sys", std::make_shared<LP2PSYS>(system));
+ server_manager->RegisterNamedService("lp2p:m", std::make_shared<LP2PM>(system));
+
ServerManager::RunServer(std::move(server_manager));
}
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
return ResultWrongDeviceState;
}
- Service::Mii::StoreData store_data{};
- Service::Mii::NfpStoreDataExtension extension{};
- store_data.BuildBase(Mii::Gender::Male);
- extension.SetFromStoreData(store_data);
-
auto& settings = tag_data.settings;
if (tag_data.settings.settings.amiibo_initialized == 0) {
@@ -843,8 +838,8 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
}
SetAmiiboName(settings, register_info.amiibo_name);
- tag_data.owner_mii.BuildFromStoreData(store_data);
- tag_data.mii_extension = extension;
+ tag_data.owner_mii.BuildFromStoreData(register_info.mii_store_data);
+ tag_data.mii_extension.SetFromStoreData(register_info.mii_store_data);
tag_data.unknown = 0;
tag_data.unknown2 = {};
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) {
}
}
+void IGeneralService::IsAnyForegroundRequestAccepted(HLERequestContext& ctx) {
+ const bool is_accepted{};
+
+ LOG_WARNING(Service_NIFM, "(STUBBED) called, is_accepted={}", is_accepted);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u8>(is_accepted);
+}
+
IGeneralService::IGeneralService(Core::System& system_)
: ServiceFramework{system_, "IGeneralService"}, network{system_.GetRoomNetwork()} {
// clang-format off
@@ -569,7 +579,7 @@ IGeneralService::IGeneralService(Core::System& system_)
{19, nullptr, "SetEthernetCommunicationEnabled"},
{20, &IGeneralService::IsEthernetCommunicationEnabled, "IsEthernetCommunicationEnabled"},
{21, &IGeneralService::IsAnyInternetRequestAccepted, "IsAnyInternetRequestAccepted"},
- {22, nullptr, "IsAnyForegroundRequestAccepted"},
+ {22, &IGeneralService::IsAnyForegroundRequestAccepted, "IsAnyForegroundRequestAccepted"},
{23, nullptr, "PutToSleep"},
{24, nullptr, "WakeUp"},
{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:
void GetInternetConnectionStatus(HLERequestContext& ctx);
void IsEthernetCommunicationEnabled(HLERequestContext& ctx);
void IsAnyInternetRequestAccepted(HLERequestContext& ctx);
+ void IsAnyForegroundRequestAccepted(HLERequestContext& ctx);
Network::RoomNetwork& network;
};
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 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/vfs.h"
+#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/glue/glue_manager.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/ns/errors.h"
@@ -502,8 +503,8 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_)
static const FunctionInfo functions[] = {
{11, nullptr, "CalculateApplicationOccupiedSize"},
{43, nullptr, "CheckSdCardMountStatus"},
- {47, nullptr, "GetTotalSpaceSize"},
- {48, nullptr, "GetFreeSpaceSize"},
+ {47, &IContentManagementInterface::GetTotalSpaceSize, "GetTotalSpaceSize"},
+ {48, &IContentManagementInterface::GetFreeSpaceSize, "GetFreeSpaceSize"},
{600, nullptr, "CountApplicationContentMeta"},
{601, nullptr, "ListApplicationContentMetaStatus"},
{605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
@@ -516,6 +517,28 @@ IContentManagementInterface::IContentManagementInterface(Core::System& system_)
IContentManagementInterface::~IContentManagementInterface() = default;
+void IContentManagementInterface::GetTotalSpaceSize(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto storage{rp.PopEnum<FileSys::StorageId>()};
+
+ LOG_INFO(Service_Capture, "called, storage={}", storage);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push<u64>(system.GetFileSystemController().GetTotalSpaceSize(storage));
+}
+
+void IContentManagementInterface::GetFreeSpaceSize(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto storage{rp.PopEnum<FileSys::StorageId>()};
+
+ LOG_INFO(Service_Capture, "called, storage={}", storage);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push<u64>(system.GetFileSystemController().GetFreeSpaceSize(storage));
+}
+
IDocumentInterface::IDocumentInterface(Core::System& system_)
: ServiceFramework{system_, "IDocumentInterface"} {
// 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
public:
explicit IContentManagementInterface(Core::System& system_);
~IContentManagementInterface() override;
+
+private:
+ void GetTotalSpaceSize(HLERequestContext& ctx);
+ void GetFreeSpaceSize(HLERequestContext& ctx);
};
class 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:
IsSharedMemMapped = 6
};
-private:
- /// Id to use for the next handle that is created.
- u32 next_handle = 0;
-
- /// Id to use for the next object that is created.
- u32 next_id = 0;
-
struct IocCreateParams {
// Input
u32_le size{};
@@ -113,6 +106,13 @@ private:
NvResult IocParam(std::span<const u8> input, std::span<u8> output);
NvResult IocFree(std::span<const u8> input, std::span<u8> output);
+private:
+ /// Id to use for the next handle that is created.
+ u32 next_handle = 0;
+
+ /// Id to use for the next object that is created.
+ u32 next_id = 0;
+
NvCore::Container& container;
NvCore::NvMap& file;
};
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 @@
namespace Service::android {
-class GraphicBuffer;
+struct GraphicBuffer;
class BufferItem final {
public:
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 @@
namespace Service::android {
-class GraphicBuffer;
+struct GraphicBuffer;
enum class BufferState : u32 {
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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <random>
+
+#include "core/core.h"
+#include "core/hle/kernel/k_process.h"
+#include "core/hle/kernel/k_system_resource.h"
+#include "core/hle/service/nvdrv/devices/nvmap.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
+#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
+#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
+#include "core/hle/service/nvnflinger/pixel_format.h"
+#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
+#include "core/hle/service/vi/layer/vi_layer.h"
+#include "core/hle/service/vi/vi_results.h"
+
+namespace Service::Nvnflinger {
+
+namespace {
+
+Result AllocateIoForProcessAddressSpace(Common::ProcessAddress* out_map_address,
+ std::unique_ptr<Kernel::KPageGroup>* out_page_group,
+ Core::System& system, u32 size) {
+ using Core::Memory::YUZU_PAGESIZE;
+
+ // Allocate memory for the system shared buffer.
+ // FIXME: Because the gmmu can only point to cpu addresses, we need
+ // to map this in the application space to allow it to be used.
+ // FIXME: Add proper smmu emulation.
+ // FIXME: This memory belongs to vi's .data section.
+ auto& kernel = system.Kernel();
+ auto* process = system.ApplicationProcess();
+ auto& page_table = process->GetPageTable();
+
+ // Hold a temporary page group reference while we try to map it.
+ auto pg = std::make_unique<Kernel::KPageGroup>(
+ kernel, std::addressof(kernel.GetSystemSystemResource().GetBlockInfoManager()));
+
+ // Allocate memory from secure pool.
+ R_TRY(kernel.MemoryManager().AllocateAndOpen(
+ pg.get(), size / YUZU_PAGESIZE,
+ Kernel::KMemoryManager::EncodeOption(Kernel::KMemoryManager::Pool::Secure,
+ Kernel::KMemoryManager::Direction::FromBack)));
+
+ // Get bounds of where mapping is possible.
+ const VAddr alias_code_begin = GetInteger(page_table.GetAliasCodeRegionStart());
+ const VAddr alias_code_size = page_table.GetAliasCodeRegionSize() / YUZU_PAGESIZE;
+ const auto state = Kernel::KMemoryState::Io;
+ const auto perm = Kernel::KMemoryPermission::UserReadWrite;
+ std::mt19937_64 rng{process->GetRandomEntropy(0)};
+
+ // Retry up to 64 times to map into alias code range.
+ Result res = ResultSuccess;
+ int i;
+ for (i = 0; i < 64; i++) {
+ *out_map_address = alias_code_begin + ((rng() % alias_code_size) * YUZU_PAGESIZE);
+ res = page_table.MapPageGroup(*out_map_address, *pg, state, perm);
+ if (R_SUCCEEDED(res)) {
+ break;
+ }
+ }
+
+ // Return failure, if necessary
+ R_UNLESS(i < 64, res);
+
+ // Return the mapped page group.
+ *out_page_group = std::move(pg);
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+template <typename T>
+std::span<u8> SerializeIoc(T& params) {
+ return std::span(reinterpret_cast<u8*>(std::addressof(params)), sizeof(T));
+}
+
+Result CreateNvMapHandle(u32* out_nv_map_handle, Nvidia::Devices::nvmap& nvmap, u32 size) {
+ // Create a handle.
+ Nvidia::Devices::nvmap::IocCreateParams create_in_params{
+ .size = size,
+ .handle = 0,
+ };
+ Nvidia::Devices::nvmap::IocCreateParams create_out_params{};
+ R_UNLESS(nvmap.IocCreate(SerializeIoc(create_in_params), SerializeIoc(create_out_params)) ==
+ Nvidia::NvResult::Success,
+ VI::ResultOperationFailed);
+
+ // Assign the output handle.
+ *out_nv_map_handle = create_out_params.handle;
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+Result FreeNvMapHandle(Nvidia::Devices::nvmap& nvmap, u32 handle) {
+ // Free the handle.
+ Nvidia::Devices::nvmap::IocFreeParams free_in_params{
+ .handle = handle,
+ };
+ Nvidia::Devices::nvmap::IocFreeParams free_out_params{};
+ R_UNLESS(nvmap.IocFree(SerializeIoc(free_in_params), SerializeIoc(free_out_params)) ==
+ Nvidia::NvResult::Success,
+ VI::ResultOperationFailed);
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+Result AllocNvMapHandle(Nvidia::Devices::nvmap& nvmap, u32 handle, Common::ProcessAddress buffer,
+ u32 size) {
+ // Assign the allocated memory to the handle.
+ Nvidia::Devices::nvmap::IocAllocParams alloc_in_params{
+ .handle = handle,
+ .heap_mask = 0,
+ .flags = {},
+ .align = 0,
+ .kind = 0,
+ .address = GetInteger(buffer),
+ };
+ Nvidia::Devices::nvmap::IocAllocParams alloc_out_params{};
+ R_UNLESS(nvmap.IocAlloc(SerializeIoc(alloc_in_params), SerializeIoc(alloc_out_params)) ==
+ Nvidia::NvResult::Success,
+ VI::ResultOperationFailed);
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+Result AllocateHandleForBuffer(u32* out_handle, Nvidia::Module& nvdrv,
+ Common::ProcessAddress buffer, u32 size) {
+ // Get the nvmap device.
+ auto nvmap_fd = nvdrv.Open("/dev/nvmap");
+ auto nvmap = nvdrv.GetDevice<Nvidia::Devices::nvmap>(nvmap_fd);
+ ASSERT(nvmap != nullptr);
+
+ // Create a handle.
+ R_TRY(CreateNvMapHandle(out_handle, *nvmap, size));
+
+ // Ensure we maintain a clean state on failure.
+ ON_RESULT_FAILURE {
+ ASSERT(R_SUCCEEDED(FreeNvMapHandle(*nvmap, *out_handle)));
+ };
+
+ // Assign the allocated memory to the handle.
+ R_RETURN(AllocNvMapHandle(*nvmap, *out_handle, buffer, size));
+}
+
+constexpr auto SharedBufferBlockLinearFormat = android::PixelFormat::Rgba8888;
+constexpr u32 SharedBufferBlockLinearBpp = 4;
+
+constexpr u32 SharedBufferBlockLinearWidth = 1280;
+constexpr u32 SharedBufferBlockLinearHeight = 768;
+constexpr u32 SharedBufferBlockLinearStride =
+ SharedBufferBlockLinearWidth * SharedBufferBlockLinearBpp;
+constexpr u32 SharedBufferNumSlots = 7;
+
+constexpr u32 SharedBufferWidth = 1280;
+constexpr u32 SharedBufferHeight = 720;
+constexpr u32 SharedBufferAsync = false;
+
+constexpr u32 SharedBufferSlotSize =
+ SharedBufferBlockLinearWidth * SharedBufferBlockLinearHeight * SharedBufferBlockLinearBpp;
+constexpr u32 SharedBufferSize = SharedBufferSlotSize * SharedBufferNumSlots;
+
+constexpr SharedMemoryPoolLayout SharedBufferPoolLayout = [] {
+ SharedMemoryPoolLayout layout{};
+ layout.num_slots = SharedBufferNumSlots;
+
+ for (u32 i = 0; i < SharedBufferNumSlots; i++) {
+ layout.slots[i].buffer_offset = i * SharedBufferSlotSize;
+ layout.slots[i].size = SharedBufferSlotSize;
+ layout.slots[i].width = SharedBufferWidth;
+ layout.slots[i].height = SharedBufferHeight;
+ }
+
+ return layout;
+}();
+
+void MakeGraphicBuffer(android::BufferQueueProducer& producer, u32 slot, u32 handle) {
+ auto buffer = std::make_shared<android::GraphicBuffer>();
+ buffer->width = SharedBufferWidth;
+ buffer->height = SharedBufferHeight;
+ buffer->stride = SharedBufferBlockLinearStride;
+ buffer->format = SharedBufferBlockLinearFormat;
+ buffer->buffer_id = handle;
+ buffer->offset = slot * SharedBufferSlotSize;
+ ASSERT(producer.SetPreallocatedBuffer(slot, buffer) == android::Status::NoError);
+}
+
+} // namespace
+
+FbShareBufferManager::FbShareBufferManager(Core::System& system, Nvnflinger& flinger,
+ std::shared_ptr<Nvidia::Module> nvdrv)
+ : m_system(system), m_flinger(flinger), m_nvdrv(std::move(nvdrv)) {}
+
+FbShareBufferManager::~FbShareBufferManager() = default;
+
+Result FbShareBufferManager::Initialize(u64* out_buffer_id, u64* out_layer_id, u64 display_id) {
+ std::scoped_lock lk{m_guard};
+
+ // Ensure we have not already created a buffer.
+ R_UNLESS(m_buffer_id == 0, VI::ResultOperationFailed);
+
+ // Allocate memory and space for the shared buffer.
+ Common::ProcessAddress map_address;
+ R_TRY(AllocateIoForProcessAddressSpace(std::addressof(map_address),
+ std::addressof(m_buffer_page_group), m_system,
+ SharedBufferSize));
+
+ // Create an nvmap handle for the buffer and assign the memory to it.
+ R_TRY(AllocateHandleForBuffer(std::addressof(m_buffer_nvmap_handle), *m_nvdrv, map_address,
+ SharedBufferSize));
+
+ // Record the display id.
+ m_display_id = display_id;
+
+ // Create a layer for the display.
+ m_layer_id = m_flinger.CreateLayer(m_display_id).value();
+
+ // Set up the buffer.
+ m_buffer_id = m_next_buffer_id++;
+
+ // Get the layer.
+ VI::Layer* layer = m_flinger.FindLayer(m_display_id, m_layer_id);
+ ASSERT(layer != nullptr);
+
+ // Get the producer and set preallocated buffers.
+ auto& producer = layer->GetBufferQueue();
+ MakeGraphicBuffer(producer, 0, m_buffer_nvmap_handle);
+ MakeGraphicBuffer(producer, 1, m_buffer_nvmap_handle);
+
+ // Assign outputs.
+ *out_buffer_id = m_buffer_id;
+ *out_layer_id = m_layer_id;
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+Result FbShareBufferManager::GetSharedBufferMemoryHandleId(u64* out_buffer_size,
+ s32* out_nvmap_handle,
+ SharedMemoryPoolLayout* out_pool_layout,
+ u64 buffer_id,
+ u64 applet_resource_user_id) {
+ std::scoped_lock lk{m_guard};
+
+ R_UNLESS(m_buffer_id > 0, VI::ResultNotFound);
+ R_UNLESS(buffer_id == m_buffer_id, VI::ResultNotFound);
+
+ *out_pool_layout = SharedBufferPoolLayout;
+ *out_buffer_size = SharedBufferSize;
+ *out_nvmap_handle = m_buffer_nvmap_handle;
+
+ R_SUCCEED();
+}
+
+Result FbShareBufferManager::GetLayerFromId(VI::Layer** out_layer, u64 layer_id) {
+ // Ensure the layer id is valid.
+ R_UNLESS(m_layer_id > 0 && layer_id == m_layer_id, VI::ResultNotFound);
+
+ // Get the layer.
+ VI::Layer* layer = m_flinger.FindLayer(m_display_id, layer_id);
+ R_UNLESS(layer != nullptr, VI::ResultNotFound);
+
+ // We succeeded.
+ *out_layer = layer;
+ R_SUCCEED();
+}
+
+Result FbShareBufferManager::AcquireSharedFrameBuffer(android::Fence* out_fence,
+ std::array<s32, 4>& out_slot_indexes,
+ s64* out_target_slot, u64 layer_id) {
+ std::scoped_lock lk{m_guard};
+
+ // Get the layer.
+ VI::Layer* layer;
+ R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id));
+
+ // Get the producer.
+ auto& producer = layer->GetBufferQueue();
+
+ // Get the next buffer from the producer.
+ s32 slot;
+ R_UNLESS(producer.DequeueBuffer(std::addressof(slot), out_fence, SharedBufferAsync != 0,
+ SharedBufferWidth, SharedBufferHeight,
+ SharedBufferBlockLinearFormat, 0) == android::Status::NoError,
+ VI::ResultOperationFailed);
+
+ // Assign remaining outputs.
+ *out_target_slot = slot;
+ out_slot_indexes = {0, 1, -1, -1};
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+Result FbShareBufferManager::PresentSharedFrameBuffer(android::Fence fence,
+ Common::Rectangle<s32> crop_region,
+ u32 transform, s32 swap_interval,
+ u64 layer_id, s64 slot) {
+ std::scoped_lock lk{m_guard};
+
+ // Get the layer.
+ VI::Layer* layer;
+ R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id));
+
+ // Get the producer.
+ auto& producer = layer->GetBufferQueue();
+
+ // Request to queue the buffer.
+ std::shared_ptr<android::GraphicBuffer> buffer;
+ R_UNLESS(producer.RequestBuffer(static_cast<s32>(slot), std::addressof(buffer)) ==
+ android::Status::NoError,
+ VI::ResultOperationFailed);
+
+ // Queue the buffer to the producer.
+ android::QueueBufferInput input{};
+ android::QueueBufferOutput output{};
+ input.crop = crop_region;
+ input.fence = fence;
+ input.transform = static_cast<android::NativeWindowTransform>(transform);
+ input.swap_interval = swap_interval;
+ R_UNLESS(producer.QueueBuffer(static_cast<s32>(slot), input, std::addressof(output)) ==
+ android::Status::NoError,
+ VI::ResultOperationFailed);
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+Result FbShareBufferManager::GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event,
+ u64 layer_id) {
+ std::scoped_lock lk{m_guard};
+
+ // Get the layer.
+ VI::Layer* layer;
+ R_TRY(this->GetLayerFromId(std::addressof(layer), layer_id));
+
+ // Get the producer.
+ auto& producer = layer->GetBufferQueue();
+
+ // Set the event.
+ *out_event = std::addressof(producer.GetNativeHandle());
+
+ // We succeeded.
+ R_SUCCEED();
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/math_util.h"
+#include "core/hle/service/nvnflinger/nvnflinger.h"
+#include "core/hle/service/nvnflinger/ui/fence.h"
+
+namespace Kernel {
+class KPageGroup;
+}
+
+namespace Service::Nvnflinger {
+
+struct SharedMemorySlot {
+ u64 buffer_offset;
+ u64 size;
+ s32 width;
+ s32 height;
+};
+static_assert(sizeof(SharedMemorySlot) == 0x18, "SharedMemorySlot has wrong size");
+
+struct SharedMemoryPoolLayout {
+ s32 num_slots;
+ std::array<SharedMemorySlot, 0x10> slots;
+};
+static_assert(sizeof(SharedMemoryPoolLayout) == 0x188, "SharedMemoryPoolLayout has wrong size");
+
+class FbShareBufferManager final {
+public:
+ explicit FbShareBufferManager(Core::System& system, Nvnflinger& flinger,
+ std::shared_ptr<Nvidia::Module> nvdrv);
+ ~FbShareBufferManager();
+
+ Result Initialize(u64* out_buffer_id, u64* out_layer_handle, u64 display_id);
+ Result GetSharedBufferMemoryHandleId(u64* out_buffer_size, s32* out_nvmap_handle,
+ SharedMemoryPoolLayout* out_pool_layout, u64 buffer_id,
+ u64 applet_resource_user_id);
+ Result AcquireSharedFrameBuffer(android::Fence* out_fence, std::array<s32, 4>& out_slots,
+ s64* out_target_slot, u64 layer_id);
+ Result PresentSharedFrameBuffer(android::Fence fence, Common::Rectangle<s32> crop_region,
+ u32 transform, s32 swap_interval, u64 layer_id, s64 slot);
+ Result GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event, u64 layer_id);
+
+private:
+ Result GetLayerFromId(VI::Layer** out_layer, u64 layer_id);
+
+private:
+ u64 m_next_buffer_id = 1;
+ u64 m_display_id = 0;
+ u64 m_buffer_id = 0;
+ u64 m_layer_id = 0;
+ u32 m_buffer_nvmap_handle = 0;
+ SharedMemoryPoolLayout m_pool_layout = {};
+
+ std::unique_ptr<Kernel::KPageGroup> m_buffer_page_group;
+
+ std::mutex m_guard;
+ Core::System& m_system;
+ Nvnflinger& m_flinger;
+ std::shared_ptr<Nvidia::Module> m_nvdrv;
+};
+
+} // 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;
#pragma pack(push, 1)
struct QueueBufferInput final {
explicit QueueBufferInput(InputParcel& parcel);
+ explicit QueueBufferInput() = default;
void Deflate(s64* timestamp_, bool* is_auto_timestamp_, Common::Rectangle<s32>* crop_,
NativeWindowScalingMode* scaling_mode_, NativeWindowTransform* transform_,
@@ -34,7 +35,6 @@ struct QueueBufferInput final {
*fence_ = fence;
}
-private:
s64 timestamp{};
s32 is_auto_timestamp{};
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 @@
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvnflinger/buffer_item_consumer.h"
#include "core/hle/service/nvnflinger/buffer_queue_core.h"
+#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
#include "core/hle/service/nvnflinger/nvnflinger.h"
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
@@ -331,4 +332,14 @@ s64 Nvnflinger::GetNextTicks() const {
return static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
}
+FbShareBufferManager& Nvnflinger::GetSystemBufferManager() {
+ const auto lock_guard = Lock();
+
+ if (!system_buffer_manager) {
+ system_buffer_manager = std::make_unique<FbShareBufferManager>(system, *this, nvdrv);
+ }
+
+ return *system_buffer_manager;
+}
+
} // 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;
namespace Service::Nvnflinger {
+class FbShareBufferManager;
+class HosBinderDriverServer;
+
class Nvnflinger final {
public:
explicit Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_);
@@ -90,12 +93,16 @@ public:
[[nodiscard]] s64 GetNextTicks() const;
+ FbShareBufferManager& GetSystemBufferManager();
+
private:
struct Layer {
std::unique_ptr<android::BufferQueueCore> core;
std::unique_ptr<android::BufferQueueProducer> producer;
};
+ friend class FbShareBufferManager;
+
private:
[[nodiscard]] std::unique_lock<std::mutex> Lock() const {
return std::unique_lock{*guard};
@@ -140,6 +147,8 @@ private:
std::shared_ptr<Core::Timing::EventType> multi_composition_event;
std::shared_ptr<Core::Timing::EventType> single_composition_event;
+ std::unique_ptr<FbShareBufferManager> system_buffer_manager;
+
std::shared_ptr<std::mutex> guard;
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:
static constexpr Fence NoFence() {
Fence fence;
fence.fences[0].id = -1;
+ fence.fences[1].id = -1;
+ fence.fences[2].id = -1;
+ fence.fences[3].id = -1;
return fence;
}
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 @@
namespace Service::android {
-class GraphicBuffer final {
-public:
+struct GraphicBuffer final {
constexpr GraphicBuffer() = default;
constexpr GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_)
@@ -77,7 +76,6 @@ public:
return false;
}
-private:
u32 magic{};
s32 width{};
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:
{1001, &IParentalControlService::CheckFreeCommunicationPermission, "CheckFreeCommunicationPermission"},
{1002, nullptr, "ConfirmLaunchApplicationPermission"},
{1003, nullptr, "ConfirmResumeApplicationPermission"},
- {1004, nullptr, "ConfirmSnsPostPermission"},
+ {1004, &IParentalControlService::ConfirmSnsPostPermission, "ConfirmSnsPostPermission"},
{1005, nullptr, "ConfirmSystemSettingsPermission"},
{1006, &IParentalControlService::IsRestrictionTemporaryUnlocked, "IsRestrictionTemporaryUnlocked"},
{1007, nullptr, "RevertRestrictionTemporaryUnlocked"},
@@ -236,6 +236,13 @@ private:
states.free_communication = true;
}
+ void ConfirmSnsPostPermission(HLERequestContext& ctx) {
+ LOG_WARNING(Service_PCTL, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(Error::ResultNoFreeCommunication);
+ }
+
void IsRestrictionTemporaryUnlocked(HLERequestContext& ctx) {
const bool is_temporary_unlocked = false;
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 @@
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/service/ipc_helpers.h"
+#include "core/hle/service/nvdrv/devices/nvmap.h"
#include "core/hle/service/nvdrv/nvdata.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvnflinger/binder.h"
#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
+#include "core/hle/service/nvnflinger/fb_share_buffer_manager.h"
#include "core/hle/service/nvnflinger/hos_binder_driver_server.h"
#include "core/hle/service/nvnflinger/nvnflinger.h"
#include "core/hle/service/nvnflinger/parcel.h"
@@ -131,8 +134,9 @@ private:
class ISystemDisplayService final : public ServiceFramework<ISystemDisplayService> {
public:
- explicit ISystemDisplayService(Core::System& system_)
- : ServiceFramework{system_, "ISystemDisplayService"} {
+ explicit ISystemDisplayService(Core::System& system_, Nvnflinger::Nvnflinger& nvnflinger_)
+ : ServiceFramework{system_, "ISystemDisplayService"}, nvnflinger{nvnflinger_} {
+ // clang-format off
static const FunctionInfo functions[] = {
{1200, nullptr, "GetZOrderCountMin"},
{1202, nullptr, "GetZOrderCountMax"},
@@ -170,22 +174,126 @@ public:
{3217, nullptr, "SetDisplayCmuLuma"},
{3218, nullptr, "SetDisplayCrcMode"},
{6013, nullptr, "GetLayerPresentationSubmissionTimestamps"},
- {8225, nullptr, "GetSharedBufferMemoryHandleId"},
- {8250, nullptr, "OpenSharedLayer"},
+ {8225, &ISystemDisplayService::GetSharedBufferMemoryHandleId, "GetSharedBufferMemoryHandleId"},
+ {8250, &ISystemDisplayService::OpenSharedLayer, "OpenSharedLayer"},
{8251, nullptr, "CloseSharedLayer"},
- {8252, nullptr, "ConnectSharedLayer"},
+ {8252, &ISystemDisplayService::ConnectSharedLayer, "ConnectSharedLayer"},
{8253, nullptr, "DisconnectSharedLayer"},
- {8254, nullptr, "AcquireSharedFrameBuffer"},
- {8255, nullptr, "PresentSharedFrameBuffer"},
- {8256, nullptr, "GetSharedFrameBufferAcquirableEvent"},
+ {8254, &ISystemDisplayService::AcquireSharedFrameBuffer, "AcquireSharedFrameBuffer"},
+ {8255, &ISystemDisplayService::PresentSharedFrameBuffer, "PresentSharedFrameBuffer"},
+ {8256, &ISystemDisplayService::GetSharedFrameBufferAcquirableEvent, "GetSharedFrameBufferAcquirableEvent"},
{8257, nullptr, "FillSharedFrameBufferColor"},
{8258, nullptr, "CancelSharedFrameBuffer"},
{9000, nullptr, "GetDp2hdmiController"},
};
+ // clang-format on
RegisterHandlers(functions);
}
private:
+ void GetSharedBufferMemoryHandleId(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 buffer_id = rp.PopRaw<u64>();
+
+ LOG_INFO(Service_VI, "called. buffer_id={:#x}", buffer_id);
+
+ struct OutputParameters {
+ s32 nvmap_handle;
+ u64 size;
+ };
+
+ OutputParameters out{};
+ Nvnflinger::SharedMemoryPoolLayout layout{};
+ const auto result = nvnflinger.GetSystemBufferManager().GetSharedBufferMemoryHandleId(
+ &out.size, &out.nvmap_handle, &layout, buffer_id, 0);
+
+ ctx.WriteBuffer(&layout, sizeof(layout));
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(result);
+ rb.PushRaw(out);
+ }
+
+ void OpenSharedLayer(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 layer_id = rp.PopRaw<u64>();
+
+ LOG_INFO(Service_VI, "(STUBBED) called. layer_id={:#x}", layer_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ void ConnectSharedLayer(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 layer_id = rp.PopRaw<u64>();
+
+ LOG_INFO(Service_VI, "(STUBBED) called. layer_id={:#x}", layer_id);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ void GetSharedFrameBufferAcquirableEvent(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_VI, "called");
+
+ IPC::RequestParser rp{ctx};
+ const u64 layer_id = rp.PopRaw<u64>();
+
+ Kernel::KReadableEvent* event{};
+ const auto result = nvnflinger.GetSystemBufferManager().GetSharedFrameBufferAcquirableEvent(
+ &event, layer_id);
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(result);
+ rb.PushCopyObjects(event);
+ }
+
+ void AcquireSharedFrameBuffer(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_VI, "called");
+
+ IPC::RequestParser rp{ctx};
+ const u64 layer_id = rp.PopRaw<u64>();
+
+ struct OutputParameters {
+ android::Fence fence;
+ std::array<s32, 4> slots;
+ s64 target_slot;
+ };
+ static_assert(sizeof(OutputParameters) == 0x40, "OutputParameters has wrong size");
+
+ OutputParameters out{};
+ const auto result = nvnflinger.GetSystemBufferManager().AcquireSharedFrameBuffer(
+ &out.fence, out.slots, &out.target_slot, layer_id);
+
+ IPC::ResponseBuilder rb{ctx, 18};
+ rb.Push(result);
+ rb.PushRaw(out);
+ }
+
+ void PresentSharedFrameBuffer(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_VI, "called");
+
+ struct InputParameters {
+ android::Fence fence;
+ Common::Rectangle<s32> crop_region;
+ u32 window_transform;
+ s32 swap_interval;
+ u64 layer_id;
+ s64 surface_id;
+ };
+ static_assert(sizeof(InputParameters) == 0x50, "InputParameters has wrong size");
+
+ IPC::RequestParser rp{ctx};
+ auto input = rp.PopRaw<InputParameters>();
+
+ const auto result = nvnflinger.GetSystemBufferManager().PresentSharedFrameBuffer(
+ input.fence, input.crop_region, input.window_transform, input.swap_interval,
+ input.layer_id, input.surface_id);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
void SetLayerZ(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 layer_id = rp.Pop<u64>();
@@ -228,6 +336,9 @@ private:
rb.PushRaw<float>(60.0f); // This wouldn't seem to be correct for 30 fps games.
rb.Push<u32>(0);
}
+
+private:
+ Nvnflinger::Nvnflinger& nvnflinger;
};
class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> {
@@ -453,7 +564,7 @@ private:
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<ISystemDisplayService>(system);
+ rb.PushIpcInterface<ISystemDisplayService>(system, nv_flinger);
}
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
return {ResultStatus::ErrorMissingNPDM, {}};
}
- const ResultStatus result2 = metadata.Load(npdm);
+ const ResultStatus result2 = metadata.Reload(npdm);
if (result2 != ResultStatus::Success) {
return {result2, {}};
}
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:
* @param player_index the player number that will take this action
* @param delta_timestamp time passed since last reading
* @param gyro_x,gyro_y,gyro_z the gyro sensor readings
- * @param accel_x,accel_y,accel_z the acelerometer reading
+ * @param accel_x,accel_y,accel_z the accelerometer reading
*/
void SetMotionState(std::size_t player_index, u64 delta_timestamp, float gyro_x, float gyro_y,
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:
/**
* Configures the motion sensor with the specified parameters
- * @param gsen gyroscope sensor sensitvity in degrees per second
+ * @param gsen gyroscope sensor sensitivity in degrees per second
* @param gfrec gyroscope sensor frequency in hertz
* @param asen accelerometer sensitivity in G force
* @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
"MOV.{} {}.{},{};",
type, ret, composite, type, ret, swizzle, object);
} else {
- // The return value is alised so we can just insert the object, it doesn't matter if it's
+ // The return value is aliased so we can just insert the object, it doesn't matter if it's
// aliased
ctx.Add("MOV.{} {}.{},{};", type, ret, swizzle, object);
}
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:
u32 method_count; ///< Current method count
u32 length_pending; ///< Large NI command length pending
GPUVAddr dma_get; ///< Currently read segment
- u64 dma_word_offset; ///< Current word ofset from address
+ u64 dma_word_offset; ///< Current word offset from address
bool non_incrementing; ///< Current command's NI flag
bool is_last_call;
};
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
block_linear_unswizzle_2d.comp
block_linear_unswizzle_3d.comp
convert_abgr8_to_d24s8.frag
+ convert_d32f_to_abgr8.frag
convert_d24s8_to_abgr8.frag
convert_depth_to_float.frag
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 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#version 450
+
+layout(binding = 0) uniform sampler2D depth_tex;
+
+layout(location = 0) out vec4 color;
+
+void main() {
+ ivec2 coord = ivec2(gl_FragCoord.xy);
+ float depth = textureLod(depth_tex, coord, 0).r;
+ color = vec4(depth, depth, depth, 1.0);
+}
diff --git a/src/video_core/query_cache/bank_base.h b/src/video_core/query_cache/bank_base.h
index 420927091..44769ea97 100644
--- a/src/video_core/query_cache/bank_base.h
+++ b/src/video_core/query_cache/bank_base.h
@@ -82,6 +82,7 @@ public:
size_t new_index = bank_indices.front();
bank_indices.pop_front();
bank_pool[new_index].Reset();
+ bank_indices.push_back(new_index);
return new_index;
}
size_t new_index = bank_pool.size();
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:
void RequestScreenshot(void* data, std::function<void(bool)> callback,
const Layout::FramebufferLayout& layout);
- /// This is called to notify the rendering backend of a surface change
- virtual void NotifySurfaceChanged() {}
-
protected:
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
std::unique_ptr<Core::Frontend::GraphicsContext> context;
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
{GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM
+ {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT_24_8}, // X8_D24_UNORM
{GL_STENCIL_INDEX8, GL_STENCIL, GL_UNSIGNED_BYTE}, // S8_UINT
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT
{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 @@
#include "video_core/host_shaders/blit_color_float_frag_spv.h"
#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h"
#include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h"
+#include "video_core/host_shaders/convert_d32f_to_abgr8_frag_spv.h"
#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
#include "video_core/host_shaders/convert_float_to_depth_frag_spv.h"
#include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h"
@@ -433,6 +434,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_,
convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)),
convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)),
convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)),
+ convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)),
convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)),
convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)),
linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO<VK_FILTER_LINEAR>)),
@@ -557,6 +559,13 @@ void BlitImageHelper::ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer,
Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view);
}
+void BlitImageHelper::ConvertD32FToABGR8(const Framebuffer* dst_framebuffer,
+ ImageView& src_image_view) {
+ ConvertPipelineColorTargetEx(convert_d32f_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
+ convert_d32f_to_abgr8_frag);
+ ConvertDepthStencil(*convert_d32f_to_abgr8_pipeline, dst_framebuffer, src_image_view);
+}
+
void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer,
ImageView& src_image_view) {
ConvertPipelineColorTargetEx(convert_d24s8_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
@@ -609,6 +618,8 @@ void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool
const VkPipelineLayout layout = *clear_color_pipeline_layout;
scheduler.RequestRenderpass(dst_framebuffer);
scheduler.Record([pipeline, layout, clear_depth, dst_region](vk::CommandBuffer cmdbuf) {
+ constexpr std::array blend_constants{0.0f, 0.0f, 0.0f, 0.0f};
+ cmdbuf.SetBlendConstants(blend_constants.data());
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
BindBlitState(cmdbuf, dst_region);
cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, clear_depth);
@@ -865,7 +876,7 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline(
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
- .depthTestEnable = VK_FALSE,
+ .depthTestEnable = key.depth_clear,
.depthWriteEnable = key.depth_clear,
.depthCompareOp = VK_COMPARE_OP_ALWAYS,
.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:
void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
+ void ConvertD32FToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
+
void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
void ConvertS8D24ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
@@ -128,6 +130,7 @@ private:
vk::ShaderModule convert_depth_to_float_frag;
vk::ShaderModule convert_float_to_depth_frag;
vk::ShaderModule convert_abgr8_to_d24s8_frag;
+ vk::ShaderModule convert_d32f_to_abgr8_frag;
vk::ShaderModule convert_d24s8_to_abgr8_frag;
vk::ShaderModule convert_s8d24_to_abgr8_frag;
vk::Sampler linear_sampler;
@@ -146,6 +149,7 @@ private:
vk::Pipeline convert_d16_to_r16_pipeline;
vk::Pipeline convert_r16_to_d16_pipeline;
vk::Pipeline convert_abgr8_to_d24s8_pipeline;
+ vk::Pipeline convert_d32f_to_abgr8_pipeline;
vk::Pipeline convert_d24s8_to_abgr8_pipeline;
vk::Pipeline convert_s8d24_to_abgr8_pipeline;
};
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 {
{VK_FORMAT_E5B9G9R9_UFLOAT_PACK32}, // E5B9G9R9_FLOAT
// Depth formats
- {VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT
- {VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM
+ {VK_FORMAT_D32_SFLOAT, Attachable}, // D32_FLOAT
+ {VK_FORMAT_D16_UNORM, Attachable}, // D16_UNORM
+ {VK_FORMAT_X8_D24_UNORM_PACK32, Attachable}, // X8_D24_UNORM
// Stencil formats
{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:
return device.GetDriverName();
}
- void NotifySurfaceChanged() override {
- present_manager.NotifySurfaceChanged();
- }
-
private:
void Report() const;
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) {
VkFormat GetFormat(const Tegra::FramebufferConfig& framebuffer) {
switch (framebuffer.pixel_format) {
case Service::android::PixelFormat::Rgba8888:
+ case Service::android::PixelFormat::Rgbx8888:
return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
case Service::android::PixelFormat::Rgb565:
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_,
surface{surface_}, blit_supported{CanBlitToSwapchain(device.GetPhysical(),
swapchain.GetImageViewFormat())},
use_present_thread{Settings::values.async_presentation.GetValue()},
- image_count{swapchain.GetImageCount()}, last_render_surface{
- render_window_.GetWindowInfo().render_surface} {
+ image_count{swapchain.GetImageCount()} {
auto& dld = device.GetLogical();
cmdpool = dld.CreateCommandPool({
@@ -289,44 +288,36 @@ void PresentManager::PresentThread(std::stop_token token) {
}
}
-void PresentManager::NotifySurfaceChanged() {
-#ifdef ANDROID
- std::scoped_lock lock{recreate_surface_mutex};
- recreate_surface_cv.notify_one();
-#endif
+void PresentManager::RecreateSwapchain(Frame* frame) {
+ swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb);
+ image_count = swapchain.GetImageCount();
}
void PresentManager::CopyToSwapchain(Frame* frame) {
- MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
-
- const auto recreate_swapchain = [&] {
- swapchain.Create(*surface, frame->width, frame->height, frame->is_srgb);
- image_count = swapchain.GetImageCount();
- };
-
-#ifdef ANDROID
- std::unique_lock lock{recreate_surface_mutex};
-
- const auto needs_recreation = [&] {
- if (last_render_surface != render_window.GetWindowInfo().render_surface) {
- return true;
- }
- if (swapchain.NeedsRecreation(frame->is_srgb)) {
- return true;
+ bool requires_recreation = false;
+
+ while (true) {
+ try {
+ // Recreate surface and swapchain if needed.
+ if (requires_recreation) {
+ surface = CreateSurface(instance, render_window.GetWindowInfo());
+ RecreateSwapchain(frame);
+ }
+
+ // Draw to swapchain.
+ return CopyToSwapchainImpl(frame);
+ } catch (const vk::Exception& except) {
+ if (except.GetResult() != VK_ERROR_SURFACE_LOST_KHR) {
+ throw;
+ }
+
+ requires_recreation = true;
}
- return false;
- };
-
- recreate_surface_cv.wait_for(lock, std::chrono::milliseconds(400),
- [&]() { return !needs_recreation(); });
-
- // If the frontend recreated the surface, recreate the renderer surface and swapchain.
- if (last_render_surface != render_window.GetWindowInfo().render_surface) {
- last_render_surface = render_window.GetWindowInfo().render_surface;
- surface = CreateSurface(instance, render_window.GetWindowInfo());
- recreate_swapchain();
}
-#endif
+}
+
+void PresentManager::CopyToSwapchainImpl(Frame* frame) {
+ MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
// If the size or colorspace of the incoming frames has changed, recreate the swapchain
// to account for that.
@@ -334,11 +325,11 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
const bool size_changed =
swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height;
if (srgb_changed || size_changed) {
- recreate_swapchain();
+ RecreateSwapchain(frame);
}
while (swapchain.AcquireNextImage()) {
- recreate_swapchain();
+ RecreateSwapchain(frame);
}
const vk::CommandBuffer cmdbuf{frame->cmdbuf};
@@ -488,4 +479,4 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
swapchain.Present(render_semaphore);
}
-} // namespace Vulkan \ No newline at end of file
+} // 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:
/// Waits for the present thread to finish presenting all queued frames.
void WaitPresent();
- /// This is called to notify the rendering backend of a surface change
- void NotifySurfaceChanged();
-
private:
void PresentThread(std::stop_token token);
void CopyToSwapchain(Frame* frame);
+ void CopyToSwapchainImpl(Frame* frame);
+
+ void RecreateSwapchain(Frame* frame);
+
private:
const vk::Instance& instance;
Core::Frontend::EmuWindow& render_window;
@@ -76,16 +77,13 @@ private:
std::queue<Frame*> free_queue;
std::condition_variable_any frame_cv;
std::condition_variable free_cv;
- std::condition_variable recreate_surface_cv;
std::mutex swapchain_mutex;
- std::mutex recreate_surface_mutex;
std::mutex queue_mutex;
std::mutex free_mutex;
std::jthread present_thread;
bool blit_supported;
bool use_present_thread;
std::size_t image_count{};
- void* last_render_surface{};
};
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp
index a32da3ba3..2edaafa7e 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp
@@ -506,6 +506,7 @@ private:
SetAccumulationValue(query->value);
Free(index);
});
+ rasterizer->SyncOperation(std::move(func));
}
template <bool is_resolve>
@@ -1160,7 +1161,7 @@ struct QueryCacheRuntimeImpl {
cpu_memory_),
primitives_needed_minus_suceeded_streamer(
static_cast<size_t>(QueryType::StreamingPrimitivesNeededMinusSucceeded), runtime, 0u),
- hcr_setup{}, hcr_is_set{}, is_hcr_running{} {
+ hcr_setup{}, hcr_is_set{}, is_hcr_running{}, maxwell3d{} {
hcr_setup.sType = VK_STRUCTURE_TYPE_CONDITIONAL_RENDERING_BEGIN_INFO_EXT;
hcr_setup.pNext = nullptr;
@@ -1235,7 +1236,9 @@ void QueryCacheRuntime::Bind3DEngine(Maxwell3D* maxwell3d) {
template <typename Func>
void QueryCacheRuntime::View3DRegs(Func&& func) {
- func(*impl->maxwell3d);
+ if (impl->maxwell3d) {
+ func(*impl->maxwell3d);
+ }
}
void QueryCacheRuntime::EndHostConditionalRendering() {
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) {
return;
}
- if (use_stencil && regs.stencil_front_mask != 0xFF && regs.stencil_front_mask != 0) {
+ if (use_stencil && framebuffer->HasAspectStencilBit() && regs.stencil_front_mask != 0xFF &&
+ regs.stencil_front_mask != 0) {
Region2D dst_region = {
Offset2D{.x = clear_rect.rect.offset.x, .y = clear_rect.rect.offset.y},
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;
// Maximum potential alignment of a Vulkan buffer
constexpr VkDeviceSize MAX_ALIGNMENT = 256;
-// Maximum size to put elements in the stream buffer
-constexpr VkDeviceSize MAX_STREAM_BUFFER_REQUEST_SIZE = 8_MiB;
// Stream buffer size in bytes
-constexpr VkDeviceSize STREAM_BUFFER_SIZE = 128_MiB;
-constexpr VkDeviceSize REGION_SIZE = STREAM_BUFFER_SIZE / StagingBufferPool::NUM_SYNCS;
+constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB;
-size_t Region(size_t iterator) noexcept {
- return iterator / REGION_SIZE;
+size_t GetStreamBufferSize(const Device& device) {
+ VkDeviceSize size{0};
+ if (device.HasDebuggingToolAttached()) {
+ ForEachDeviceLocalHostVisibleHeap(device, [&size](size_t index, VkMemoryHeap& heap) {
+ size = std::max(size, heap.size);
+ });
+ // If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be
+ // loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue
+ // as the heap will be much larger.
+ if (size <= 256_MiB) {
+ size = size * 40 / 100;
+ }
+ } else {
+ size = MAX_STREAM_BUFFER_SIZE;
+ }
+ return std::min(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE);
}
} // Anonymous namespace
StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_,
Scheduler& scheduler_)
- : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_} {
+ : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
+ stream_buffer_size{GetStreamBufferSize(device)}, region_size{stream_buffer_size /
+ StagingBufferPool::NUM_SYNCS} {
VkBufferCreateInfo stream_ci = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
- .size = STREAM_BUFFER_SIZE,
+ .size = stream_buffer_size,
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
@@ -63,7 +76,7 @@ StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& mem
StagingBufferPool::~StagingBufferPool() = default;
StagingBufferRef StagingBufferPool::Request(size_t size, MemoryUsage usage, bool deferred) {
- if (!deferred && usage == MemoryUsage::Upload && size <= MAX_STREAM_BUFFER_REQUEST_SIZE) {
+ if (!deferred && usage == MemoryUsage::Upload && size <= region_size) {
return GetStreamBuffer(size);
}
return GetStagingBuffer(size, usage, deferred);
@@ -101,7 +114,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) {
used_iterator = iterator;
free_iterator = std::max(free_iterator, iterator + size);
- if (iterator + size >= STREAM_BUFFER_SIZE) {
+ if (iterator + size >= stream_buffer_size) {
std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS,
current_tick);
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:
void ReleaseCache(MemoryUsage usage);
void ReleaseLevel(StagingBuffersCache& cache, size_t log2);
+ size_t Region(size_t iter) const noexcept {
+ return iter / region_size;
+ }
const Device& device;
MemoryAllocator& memory_allocator;
@@ -97,6 +100,8 @@ private:
vk::Buffer stream_buffer;
std::span<u8> stream_pointer;
+ VkDeviceSize stream_buffer_size;
+ VkDeviceSize region_size;
size_t iterator = 0;
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..00ab47268 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) {
return any_r ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
case PixelFormat::D16_UNORM:
case PixelFormat::D32_FLOAT:
+ case PixelFormat::X8_D24_UNORM:
return VK_IMAGE_ASPECT_DEPTH_BIT;
case PixelFormat::S8_UINT:
return VK_IMAGE_ASPECT_STENCIL_BIT;
@@ -1200,6 +1201,9 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im
if (src_view.format == PixelFormat::D24_UNORM_S8_UINT) {
return blit_image_helper.ConvertS8D24ToABGR8(dst, src_view);
}
+ if (src_view.format == PixelFormat::D32_FLOAT) {
+ return blit_image_helper.ConvertD32FToABGR8(dst, src_view);
+ }
break;
case PixelFormat::R32_FLOAT:
if (src_view.format == PixelFormat::D32_FLOAT) {
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) {
return PixelFormat::S8_UINT;
case Tegra::DepthFormat::Z32_FLOAT_X24S8_UINT:
return PixelFormat::D32_FLOAT_S8_UINT;
+ case Tegra::DepthFormat::X8Z24_UNORM:
+ return PixelFormat::X8_D24_UNORM;
default:
UNIMPLEMENTED_MSG("Unimplemented format={}", format);
return PixelFormat::S8_UINT_D24_UNORM;
@@ -202,6 +204,7 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format)
PixelFormat PixelFormatFromGPUPixelFormat(Service::android::PixelFormat format) {
switch (format) {
case Service::android::PixelFormat::Rgba8888:
+ case Service::android::PixelFormat::Rgbx8888:
return PixelFormat::A8B8G8R8_UNORM;
case Service::android::PixelFormat::Rgb565:
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 {
// Depth formats
D32_FLOAT = MaxColorFormat,
D16_UNORM,
+ X8_D24_UNORM,
MaxDepthFormat,
@@ -251,6 +252,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{
1, // E5B9G9R9_FLOAT
1, // D32_FLOAT
1, // D16_UNORM
+ 1, // X8_D24_UNORM
1, // S8_UINT
1, // D24_UNORM_S8_UINT
1, // S8_UINT_D24_UNORM
@@ -360,6 +362,7 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{
1, // E5B9G9R9_FLOAT
1, // D32_FLOAT
1, // D16_UNORM
+ 1, // X8_D24_UNORM
1, // S8_UINT
1, // D24_UNORM_S8_UINT
1, // S8_UINT_D24_UNORM
@@ -469,6 +472,7 @@ constexpr std::array<u8, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{
32, // E5B9G9R9_FLOAT
32, // D32_FLOAT
16, // D16_UNORM
+ 32, // X8_D24_UNORM
8, // S8_UINT
32, // D24_UNORM_S8_UINT
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,
return PixelFormat::E5B9G9R9_FLOAT;
case Hash(TextureFormat::Z32, FLOAT):
return PixelFormat::D32_FLOAT;
+ case Hash(TextureFormat::Z32, FLOAT, UINT, UINT, UINT, LINEAR):
+ return PixelFormat::D32_FLOAT;
case Hash(TextureFormat::Z16, UNORM):
return PixelFormat::D16_UNORM;
case Hash(TextureFormat::Z16, UNORM, UINT, UINT, UINT, LINEAR):
return PixelFormat::D16_UNORM;
+ case Hash(TextureFormat::X8Z24, UNORM):
+ return PixelFormat::X8_D24_UNORM;
+ case Hash(TextureFormat::X8Z24, UNORM, UINT, UINT, UINT, LINEAR):
+ return PixelFormat::X8_D24_UNORM;
case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR):
return PixelFormat::S8_UINT_D24_UNORM;
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
return "D32_FLOAT";
case PixelFormat::D16_UNORM:
return "D16_UNORM";
+ case PixelFormat::X8_D24_UNORM:
+ return "X8_D24_UNORM";
case PixelFormat::S8_UINT:
return "S8_UINT";
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 {
IsRescalable = 1 << 15,
AsynchronousDecode = 1 << 16,
- IsDecoding = 1 << 17, ///< Is currently being decoded asynchornously.
+ IsDecoding = 1 << 17, ///< Is currently being decoded asynchronously.
};
DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)
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 {
// Depth formats
case PixelFormat::D32_FLOAT:
case PixelFormat::D16_UNORM:
+ case PixelFormat::X8_D24_UNORM:
// Stencil formats
case PixelFormat::S8_UINT:
// DepthStencil formats
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 {
Extent2D tile_size;
u32 bpp_log2;
u32 tile_width_spacing;
+ u32 num_levels;
};
[[nodiscard]] constexpr u32 AdjustTileSize(u32 shift, u32 unit_factor, u32 dimension) {
@@ -118,11 +119,11 @@ template <u32 GOB_EXTENT>
}
[[nodiscard]] constexpr Extent3D AdjustMipBlockSize(Extent3D num_tiles, Extent3D block_size,
- u32 level) {
+ u32 level, u32 num_levels) {
return {
.width = AdjustMipBlockSize<GOB_SIZE_X>(num_tiles.width, block_size.width, level),
.height = AdjustMipBlockSize<GOB_SIZE_Y>(num_tiles.height, block_size.height, level),
- .depth = level == 0
+ .depth = level == 0 && num_levels == 1
? block_size.depth
: AdjustMipBlockSize<GOB_SIZE_Z>(num_tiles.depth, block_size.depth, level),
};
@@ -166,13 +167,6 @@ template <u32 GOB_EXTENT>
}
[[nodiscard]] constexpr Extent3D TileShift(const LevelInfo& info, u32 level) {
- if (level == 0) {
- return Extent3D{
- .width = info.block.width,
- .height = info.block.height,
- .depth = info.block.depth,
- };
- }
const Extent3D blocks = NumLevelBlocks(info, level);
return Extent3D{
.width = AdjustTileSize(info.block.width, GOB_SIZE_X, blocks.width),
@@ -257,7 +251,7 @@ template <u32 GOB_EXTENT>
}
[[nodiscard]] constexpr LevelInfo MakeLevelInfo(PixelFormat format, Extent3D size, Extent3D block,
- u32 tile_width_spacing) {
+ u32 tile_width_spacing, u32 num_levels) {
const u32 bytes_per_block = BytesPerBlock(format);
return {
.size =
@@ -270,16 +264,18 @@ template <u32 GOB_EXTENT>
.tile_size = DefaultBlockSize(format),
.bpp_log2 = BytesPerBlockLog2(bytes_per_block),
.tile_width_spacing = tile_width_spacing,
+ .num_levels = num_levels,
};
}
[[nodiscard]] constexpr LevelInfo MakeLevelInfo(const ImageInfo& info) {
- return MakeLevelInfo(info.format, info.size, info.block, info.tile_width_spacing);
+ return MakeLevelInfo(info.format, info.size, info.block, info.tile_width_spacing,
+ info.resources.levels);
}
[[nodiscard]] constexpr u32 CalculateLevelOffset(PixelFormat format, Extent3D size, Extent3D block,
u32 tile_width_spacing, u32 level) {
- const LevelInfo info = MakeLevelInfo(format, size, block, tile_width_spacing);
+ const LevelInfo info = MakeLevelInfo(format, size, block, tile_width_spacing, level);
u32 offset = 0;
for (u32 current_level = 0; current_level < level; ++current_level) {
offset += CalculateLevelSize(info, current_level);
@@ -466,7 +462,7 @@ template <u32 GOB_EXTENT>
};
const u32 bpp_log2 = BytesPerBlockLog2(info.format);
const u32 alignment = StrideAlignment(num_tiles, info.block, bpp_log2, info.tile_width_spacing);
- const Extent3D mip_block = AdjustMipBlockSize(num_tiles, info.block, 0);
+ const Extent3D mip_block = AdjustMipBlockSize(num_tiles, info.block, 0, info.resources.levels);
return Extent3D{
.width = Common::AlignUpLog2(num_tiles.width, alignment),
.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
UNIMPLEMENTED_IF(copy.image_extent != level_size);
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
- const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level);
+ const Extent3D block =
+ AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
size_t host_offset = copy.buffer_offset;
@@ -698,7 +695,7 @@ u32 CalculateLevelStrideAlignment(const ImageInfo& info, u32 level) {
const Extent2D tile_size = DefaultBlockSize(info.format);
const Extent3D level_size = AdjustMipSize(info.size, level);
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
- const Extent3D block = AdjustMipBlockSize(num_tiles, info.block, level);
+ const Extent3D block = AdjustMipBlockSize(num_tiles, info.block, level, info.resources.levels);
const u32 bpp_log2 = BytesPerBlockLog2(info.format);
return StrideAlignment(num_tiles, block, bpp_log2, info.tile_width_spacing);
}
@@ -887,7 +884,8 @@ boost::container::small_vector<BufferImageCopy, 16> UnswizzleImage(Tegra::Memory
.image_extent = level_size,
};
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
- const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level);
+ const Extent3D block =
+ AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
const u32 stride_alignment = StrideAlignment(num_tiles, info.block, gob, bpp_log2);
size_t guest_layer_offset = 0;
@@ -1041,7 +1039,7 @@ Extent3D MipBlockSize(const ImageInfo& info, u32 level) {
const Extent2D tile_size = DefaultBlockSize(info.format);
const Extent3D level_size = AdjustMipSize(info.size, level);
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
- return AdjustMipBlockSize(num_tiles, level_info.block, level);
+ return AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
}
boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const ImageInfo& info) {
@@ -1063,7 +1061,8 @@ boost::container::small_vector<SwizzleParameters, 16> FullUploadSwizzles(const I
for (s32 level = 0; level < num_levels; ++level) {
const Extent3D level_size = AdjustMipSize(size, level);
const Extent3D num_tiles = AdjustTileSize(level_size, tile_size);
- const Extent3D block = AdjustMipBlockSize(num_tiles, level_info.block, level);
+ const Extent3D block =
+ AdjustMipBlockSize(num_tiles, level_info.block, level, level_info.num_levels);
params[level] = SwizzleParameters{
.num_tiles = num_tiles,
.block = block,
@@ -1195,7 +1194,7 @@ std::optional<SubresourceBase> FindSubresource(const ImageInfo& candidate, const
return std::nullopt;
}
} else {
- // Format comaptibility is not relaxed, ensure we are creating a view on a compatible format
+ // Format compatibility is not relaxed, ensure we are creating a view on a compatible format
if (!IsViewCompatible(existing.format, candidate.format, broken_views, native_bgr)) {
return std::nullopt;
}
@@ -1292,11 +1291,11 @@ u32 MapSizeBytes(const ImageBase& image) {
}
}
-static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0}, 0) ==
+static_assert(CalculateLevelSize(LevelInfo{{1920, 1080, 1}, {0, 2, 0}, {1, 1}, 2, 0, 1}, 0) ==
0x7f8000);
-static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0}, 0) == 0x40000);
+static_assert(CalculateLevelSize(LevelInfo{{32, 32, 1}, {0, 0, 4}, {1, 1}, 4, 0, 1}, 0) == 0x4000);
-static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0}, 0) == 0x40000);
+static_assert(CalculateLevelSize(LevelInfo{{128, 8, 1}, {0, 4, 0}, {1, 1}, 4, 0, 1}, 0) == 0x4000);
static_assert(CalculateLevelOffset(PixelFormat::R8_SINT, {1920, 1080, 1}, {0, 2, 0}, 0, 7) ==
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{
} // namespace Alternatives
enum class NvidiaArchitecture {
- AmpereOrNewer,
+ KeplerOrOlder,
+ Maxwell,
+ Pascal,
+ Volta,
Turing,
- VoltaOrOlder,
+ AmpereOrNewer,
};
template <typename T>
@@ -200,6 +203,7 @@ std::unordered_map<VkFormat, VkFormatProperties> GetFormatProperties(vk::Physica
VK_FORMAT_BC7_UNORM_BLOCK,
VK_FORMAT_D16_UNORM,
VK_FORMAT_D16_UNORM_S8_UINT,
+ VK_FORMAT_X8_D24_UNORM_PACK32,
VK_FORMAT_D24_UNORM_S8_UINT,
VK_FORMAT_D32_SFLOAT,
VK_FORMAT_D32_SFLOAT_S8_UINT,
@@ -321,13 +325,38 @@ NvidiaArchitecture GetNvidiaArchitecture(vk::PhysicalDevice physical,
physical.GetProperties2(physical_properties);
if (shading_rate_props.primitiveFragmentShadingRateWithMultipleViewports) {
// Only Ampere and newer support this feature
+ // TODO: Find a way to differentiate Ampere and Ada
return NvidiaArchitecture::AmpereOrNewer;
}
- }
- if (exts.contains(VK_NV_SHADING_RATE_IMAGE_EXTENSION_NAME)) {
return NvidiaArchitecture::Turing;
}
- return NvidiaArchitecture::VoltaOrOlder;
+
+ if (exts.contains(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME)) {
+ VkPhysicalDeviceBlendOperationAdvancedPropertiesEXT advanced_blending_props{};
+ advanced_blending_props.sType =
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_PROPERTIES_EXT;
+ VkPhysicalDeviceProperties2 physical_properties{};
+ physical_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
+ physical_properties.pNext = &advanced_blending_props;
+ physical.GetProperties2(physical_properties);
+ if (advanced_blending_props.advancedBlendMaxColorAttachments == 1) {
+ return NvidiaArchitecture::Maxwell;
+ }
+
+ if (exts.contains(VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME)) {
+ VkPhysicalDeviceConservativeRasterizationPropertiesEXT conservative_raster_props{};
+ conservative_raster_props.sType =
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONSERVATIVE_RASTERIZATION_PROPERTIES_EXT;
+ physical_properties.pNext = &conservative_raster_props;
+ physical.GetProperties2(physical_properties);
+ if (conservative_raster_props.degenerateLinesRasterized) {
+ return NvidiaArchitecture::Volta;
+ }
+ return NvidiaArchitecture::Pascal;
+ }
+ }
+
+ return NvidiaArchitecture::KeplerOrOlder;
}
std::vector<const char*> ExtensionListForVulkan(
@@ -504,19 +533,14 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
if (is_nvidia) {
const u32 nv_major_version = (properties.properties.driverVersion >> 22) & 0x3ff;
const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
- switch (arch) {
- case NvidiaArchitecture::AmpereOrNewer:
+ if (arch >= NvidiaArchitecture::AmpereOrNewer) {
LOG_WARNING(Render_Vulkan, "Ampere and newer have broken float16 math");
features.shader_float16_int8.shaderFloat16 = false;
- break;
- case NvidiaArchitecture::Turing:
- break;
- case NvidiaArchitecture::VoltaOrOlder:
+ } else if (arch <= NvidiaArchitecture::Volta) {
if (nv_major_version < 527) {
LOG_WARNING(Render_Vulkan, "Volta and older have broken VK_KHR_push_descriptor");
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
}
- break;
}
if (nv_major_version >= 510) {
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
"ANV drivers 22.3.0 to 23.1.0 have broken VK_KHR_push_descriptor");
RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
}
+ } else if (extensions.push_descriptor && is_nvidia) {
+ const auto arch = GetNvidiaArchitecture(physical, supported_extensions);
+ if (arch <= NvidiaArchitecture::Pascal) {
+ LOG_WARNING(Render_Vulkan,
+ "Pascal and older architectures have broken VK_KHR_push_descriptor");
+ RemoveExtension(extensions.push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME);
+ }
}
+
if (is_mvk) {
LOG_WARNING(Render_Vulkan,
"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:
return GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY;
}
- /// Returns true if the device suppors float64 natively.
+ /// Returns true if the device supports float64 natively.
bool IsFloat64Supported() const {
return features.features.shaderFloat64;
}
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index 3ef381a38..82767fdf0 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 @@
#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_types.h"
+#include "common/literals.h"
#include "common/logging/log.h"
#include "common/polyfill_ranges.h"
#include "video_core/vulkan_common/vma.h"
@@ -69,8 +70,7 @@ struct Range {
case MemoryUsage::Download:
return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
case MemoryUsage::DeviceLocal:
- return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
- VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT;
+ return {};
}
return {};
}
@@ -212,7 +212,20 @@ MemoryAllocator::MemoryAllocator(const Device& device_)
: device{device_}, allocator{device.GetAllocator()},
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
buffer_image_granularity{
- device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {}
+ device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {
+ // GPUs not supporting rebar may only have a region with less than 256MB host visible/device
+ // local memory. In that case, opening 2 RenderDoc captures side-by-side is not possible due to
+ // the heap running out of memory. With RenderDoc attached and only a small host/device region,
+ // only allow the stream buffer in this memory heap.
+ if (device.HasDebuggingToolAttached()) {
+ using namespace Common::Literals;
+ ForEachDeviceLocalHostVisibleHeap(device, [this](size_t index, VkMemoryHeap& heap) {
+ if (heap.size <= 256_MiB) {
+ valid_memory_types &= ~(1u << index);
+ }
+ });
+ }
+}
MemoryAllocator::~MemoryAllocator() = default;
@@ -244,7 +257,7 @@ vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsa
.usage = MemoryUsageVma(usage),
.requiredFlags = 0,
.preferredFlags = MemoryUsagePreferedVmaFlags(usage),
- .memoryTypeBits = 0,
+ .memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types,
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
.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 @@
#include <span>
#include <vector>
#include "common/common_types.h"
+#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
VK_DEFINE_HANDLE(VmaAllocator)
@@ -26,6 +27,18 @@ enum class MemoryUsage {
Stream, ///< Requests device local host visible buffer, falling back host memory.
};
+template <typename F>
+void ForEachDeviceLocalHostVisibleHeap(const Device& device, F&& f) {
+ auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
+ for (size_t i = 0; i < memory_props.memoryTypeCount; i++) {
+ auto& memory_type = memory_props.memoryTypes[i];
+ if ((memory_type.propertyFlags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) &&
+ (memory_type.propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
+ f(memory_type.heapIndex, memory_props.memoryHeaps[memory_type.heapIndex]);
+ }
+ }
+}
+
/// Ownership handle of a memory commitment.
/// Points to a subregion of a memory allocation.
class MemoryCommit {
@@ -124,6 +137,7 @@ private:
std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations.
VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers
// and optimal images
+ u32 valid_memory_types{~0u};
};
} // 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:
virtual ~Exception() = default;
const char* what() const noexcept override;
+ VkResult GetResult() const noexcept {
+ return result;
+ }
private:
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
multiplayer/state.cpp
multiplayer/state.h
multiplayer/validation.h
+ play_time_manager.cpp
+ play_time_manager.h
precompiled_headers.h
qt_common.cpp
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 @@
#include "yuzu/configuration/configure_vibration.h"
#include "yuzu/configuration/input_profiles.h"
#include "yuzu/main.h"
+#include "yuzu/util/controller_navigation.h"
namespace {
@@ -132,6 +133,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
};
+ ui->labelError->setVisible(false);
+
// Setup/load everything prior to setting up connections.
// This avoids unintentionally changing the states of elements while loading them in.
SetSupportedControllers();
@@ -143,6 +146,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
LoadConfiguration();
+ controller_navigation = new ControllerNavigation(system.HIDCore(), this);
+
for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
SetExplainText(i);
UpdateControllerIcon(i);
@@ -151,6 +156,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
if (checked) {
+ // Hide eventual error message about number of controllers
+ ui->labelError->setVisible(false);
for (std::size_t index = 0; index <= i; ++index) {
connected_controller_checkboxes[index]->setChecked(checked);
}
@@ -199,6 +206,12 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
&QtControllerSelectorDialog::ApplyConfiguration);
+ connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
+ [this](Qt::Key key) {
+ QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
+ QCoreApplication::postEvent(this, event);
+ });
+
// Enhancement: Check if the parameters have already been met before disconnecting controllers.
// If all the parameters are met AND only allows a single player,
// stop the constructor here as we do not need to continue.
@@ -217,6 +230,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
}
QtControllerSelectorDialog::~QtControllerSelectorDialog() {
+ controller_navigation->UnloadController();
system.HIDCore().DisableAllControllerConfiguration();
}
@@ -291,6 +305,31 @@ void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
dialog.exec();
}
+void QtControllerSelectorDialog::keyPressEvent(QKeyEvent* evt) {
+ const auto num_connected_players = static_cast<int>(
+ std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
+ [](const QGroupBox* player) { return player->isChecked(); }));
+
+ const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
+ const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
+
+ if ((evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return) && !parameters_met) {
+ // Display error message when trying to validate using "Enter" and "OK" button is disabled
+ ui->labelError->setVisible(true);
+ return;
+ } else if (evt->key() == Qt::Key_Left && num_connected_players > min_supported_players) {
+ // Remove a player if possible
+ connected_controller_checkboxes[num_connected_players - 1]->setChecked(false);
+ return;
+ } else if (evt->key() == Qt::Key_Right && num_connected_players < max_supported_players) {
+ // Add a player, if possible
+ ui->labelError->setVisible(false);
+ connected_controller_checkboxes[num_connected_players]->setChecked(true);
+ return;
+ }
+ QDialog::keyPressEvent(evt);
+}
+
bool QtControllerSelectorDialog::CheckIfParametersMet() {
// Here, we check and validate the current configuration against all applicable parameters.
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;
enum class NpadStyleIndex : u8;
} // namespace Core::HID
+class ControllerNavigation;
+
class QtControllerSelectorDialog final : public QDialog {
Q_OBJECT
@@ -46,6 +48,8 @@ public:
int exec() override;
+ void keyPressEvent(QKeyEvent* evt) override;
+
private:
// Applies the current configuration.
void ApplyConfiguration();
@@ -110,6 +114,8 @@ private:
Core::System& system;
+ ControllerNavigation* controller_navigation = nullptr;
+
// This is true if and only if all parameters are met. Otherwise, this is false.
// This determines whether the "OK" button can be clicked to exit the applet.
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 @@
</spacer>
</item>
<item alignment="Qt::AlignBottom">
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
+ <widget class="QWidget" name="closeButtons" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_46">
+ <property name="spacing">
+ <number>7</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="labelError">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="styleSheet">
+ <string notr="true">QLabel { color : red; }</string>
+ </property>
+ <property name="text">
+ <string>Not enough controllers</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
</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) {
for (auto* setting : Settings::values.linkage.by_category[category]) {
settings.push_back(setting);
}
+ for (auto* setting : UISettings::values.linkage.by_category[category]) {
+ settings.push_back(setting);
+ }
};
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)
connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
+ connect(ui->show_play_time, &QCheckBox::stateChanged, this,
+ &ConfigureUi::RequestGameListUpdate);
connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConfigureUi::RequestGameListUpdate);
connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
@@ -167,6 +169,7 @@ void ConfigureUi::ApplyConfiguration() {
UISettings::values.show_compat = ui->show_compat->isChecked();
UISettings::values.show_size = ui->show_size->isChecked();
UISettings::values.show_types = ui->show_types->isChecked();
+ UISettings::values.show_play_time = ui->show_play_time->isChecked();
UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt();
UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt();
UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
@@ -179,6 +182,7 @@ void ConfigureUi::ApplyConfiguration() {
const u32 height = ScreenshotDimensionToInt(ui->screenshot_height->currentText());
UISettings::values.screenshot_height.SetValue(height);
+ RequestGameListUpdate();
system.ApplySettings();
}
@@ -194,6 +198,7 @@ void ConfigureUi::SetConfiguration() {
ui->show_compat->setChecked(UISettings::values.show_compat.GetValue());
ui->show_size->setChecked(UISettings::values.show_size.GetValue());
ui->show_types->setChecked(UISettings::values.show_types.GetValue());
+ ui->show_play_time->setChecked(UISettings::values.show_play_time.GetValue());
ui->game_icon_size_combobox->setCurrentIndex(
ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue()));
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 @@
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="show_play_time">
+ <property name="text">
+ <string>Show Play Time Column</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2">
<item>
<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) {
INSERT(Settings, sink_id, "Output Engine:", "");
INSERT(Settings, audio_output_device_id, "Output Device:", "");
INSERT(Settings, audio_input_device_id, "Input Device:", "");
- INSERT(Settings, audio_muted, "Mute audio when in background", "");
+ INSERT(Settings, audio_muted, "Mute audio", "");
INSERT(Settings, volume, "Volume:", "");
INSERT(Settings, dump_audio_commands, "", "");
+ INSERT(UISettings, mute_when_in_background, "Mute audio when in background", "");
// Core
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() {
}
GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvider* provider_,
- Core::System& system_, GMainWindow* parent)
- : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_}, system{system_} {
+ PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
+ GMainWindow* parent)
+ : QWidget{parent}, vfs{std::move(vfs_)}, provider{provider_},
+ play_time_manager{play_time_manager_}, system{system_} {
watcher = new QFileSystemWatcher(this);
connect(watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory);
@@ -340,6 +342,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid
tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons);
tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
+ tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
item_model->setSortRole(GameListItemPath::SortRole);
connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
@@ -548,6 +551,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
QAction* remove_update = remove_menu->addAction(tr("Remove Installed Update"));
QAction* remove_dlc = remove_menu->addAction(tr("Remove All Installed DLC"));
QAction* remove_custom_config = remove_menu->addAction(tr("Remove Custom Configuration"));
+ QAction* remove_play_time_data = remove_menu->addAction(tr("Remove Play Time Data"));
QAction* remove_cache_storage = remove_menu->addAction(tr("Remove Cache Storage"));
QAction* remove_gl_shader_cache = remove_menu->addAction(tr("Remove OpenGL Pipeline Cache"));
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
QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity"));
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
-#ifndef WIN32
QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
+#ifndef WIN32
QAction* create_applications_menu_shortcut =
shortcut_menu->addAction(tr("Add to Applications Menu"));
#endif
@@ -622,6 +626,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
connect(remove_custom_config, &QAction::triggered, [this, program_id, path]() {
emit RemoveFileRequested(program_id, GameListRemoveTarget::CustomConfiguration, path);
});
+ connect(remove_play_time_data, &QAction::triggered,
+ [this, program_id]() { emit RemovePlayTimeRequested(program_id); });
connect(remove_cache_storage, &QAction::triggered, [this, program_id, path] {
emit RemoveFileRequested(program_id, GameListRemoveTarget::CacheStorage, path);
});
@@ -638,10 +644,10 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
});
-#ifndef WIN32
connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
});
+#ifndef WIN32
connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
});
@@ -790,6 +796,7 @@ void GameList::RetranslateUI() {
item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
+ item_model->setHeaderData(COLUMN_PLAY_TIME, Qt::Horizontal, tr("Play time"));
}
void GameListSearchField::changeEvent(QEvent* event) {
@@ -817,6 +824,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types);
tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
+ tree_view->setColumnHidden(COLUMN_PLAY_TIME, !UISettings::values.show_play_time);
// Delete any rows that might already exist if we're repopulating
item_model->removeRows(0, item_model->rowCount());
@@ -825,7 +833,7 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
emit ShouldCancelWorker();
GameListWorker* worker =
- new GameListWorker(vfs, provider, game_dirs, compatibility_list, system);
+ new GameListWorker(vfs, provider, game_dirs, compatibility_list, play_time_manager, system);
connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
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 @@
#include "core/core.h"
#include "uisettings.h"
#include "yuzu/compatibility_list.h"
+#include "yuzu/play_time_manager.h"
namespace Core {
class System;
@@ -75,11 +76,13 @@ public:
COLUMN_ADD_ONS,
COLUMN_FILE_TYPE,
COLUMN_SIZE,
+ COLUMN_PLAY_TIME,
COLUMN_COUNT, // Number of columns
};
explicit GameList(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
- FileSys::ManualContentProvider* provider_, Core::System& system_,
+ FileSys::ManualContentProvider* provider_,
+ PlayTime::PlayTimeManager& play_time_manager_, Core::System& system_,
GMainWindow* parent = nullptr);
~GameList() override;
@@ -113,6 +116,7 @@ signals:
void RemoveInstalledEntryRequested(u64 program_id, InstalledEntryType type);
void RemoveFileRequested(u64 program_id, GameListRemoveTarget target,
const std::string& game_path);
+ void RemovePlayTimeRequested(u64 program_id);
void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
void VerifyIntegrityRequested(const std::string& game_path);
void CopyTIDRequested(u64 program_id);
@@ -168,6 +172,7 @@ private:
friend class GameListSearchField;
+ const PlayTime::PlayTimeManager& play_time_manager;
Core::System& system;
};
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 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/string_util.h"
+#include "yuzu/play_time_manager.h"
#include "yuzu/uisettings.h"
#include "yuzu/util/util.h"
@@ -221,6 +222,31 @@ public:
}
};
+/**
+ * GameListItem for Play Time values.
+ * This object stores the play time of a game in seconds, and its readable
+ * representation in minutes/hours
+ */
+class GameListItemPlayTime : public GameListItem {
+public:
+ static constexpr int PlayTimeRole = SortRole;
+
+ GameListItemPlayTime() = default;
+ explicit GameListItemPlayTime(const qulonglong time_seconds) {
+ setData(time_seconds, PlayTimeRole);
+ }
+
+ void setData(const QVariant& value, int role) override {
+ qulonglong time_seconds = value.toULongLong();
+ GameListItem::setData(PlayTime::ReadablePlayTime(time_seconds), Qt::DisplayRole);
+ GameListItem::setData(value, PlayTimeRole);
+ }
+
+ bool operator<(const QStandardItem& other) const override {
+ return data(PlayTimeRole).toULongLong() < other.data(PlayTimeRole).toULongLong();
+ }
+};
+
class GameListDir : public GameListItem {
public:
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
const std::size_t size, const std::vector<u8>& icon,
Loader::AppLoader& loader, u64 program_id,
const CompatibilityList& compatibility_list,
+ const PlayTime::PlayTimeManager& play_time_manager,
const FileSys::PatchManager& patch) {
const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
@@ -212,6 +213,7 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
new GameListItemCompat(compatibility),
new GameListItem(file_type_string),
new GameListItemSize(size),
+ new GameListItemPlayTime(play_time_manager.GetPlayTime(program_id)),
};
const auto patch_versions = GetGameListCachedObject(
@@ -227,9 +229,12 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs_,
FileSys::ManualContentProvider* provider_,
QVector<UISettings::GameDir>& game_dirs_,
- const CompatibilityList& compatibility_list_, Core::System& system_)
+ const CompatibilityList& compatibility_list_,
+ const PlayTime::PlayTimeManager& play_time_manager_,
+ Core::System& system_)
: vfs{std::move(vfs_)}, provider{provider_}, game_dirs{game_dirs_},
- compatibility_list{compatibility_list_}, system{system_} {}
+ compatibility_list{compatibility_list_},
+ play_time_manager{play_time_manager_}, system{system_} {}
GameListWorker::~GameListWorker() = default;
@@ -280,7 +285,7 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
}
emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, file->GetSize(), icon, *loader,
- program_id, compatibility_list, patch),
+ program_id, compatibility_list, play_time_manager, patch),
parent_dir);
}
}
@@ -357,7 +362,8 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
emit EntryReady(MakeGameListEntry(physical_name, name,
Common::FS::GetSize(physical_name), icon,
- *loader, id, compatibility_list, patch),
+ *loader, id, compatibility_list,
+ play_time_manager, patch),
parent_dir);
}
} else {
@@ -370,10 +376,11 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
const FileSys::PatchManager patch{program_id, system.GetFileSystemController(),
system.GetContentProvider()};
- emit EntryReady(
- MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name),
- icon, *loader, program_id, compatibility_list, patch),
- parent_dir);
+ emit EntryReady(MakeGameListEntry(physical_name, name,
+ Common::FS::GetSize(physical_name), icon,
+ *loader, program_id, compatibility_list,
+ play_time_manager, patch),
+ parent_dir);
}
}
} 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 @@
#include <QString>
#include "yuzu/compatibility_list.h"
+#include "yuzu/play_time_manager.h"
namespace Core {
class System;
@@ -36,7 +37,9 @@ public:
explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs_,
FileSys::ManualContentProvider* provider_,
QVector<UISettings::GameDir>& game_dirs_,
- const CompatibilityList& compatibility_list_, Core::System& system_);
+ const CompatibilityList& compatibility_list_,
+ const PlayTime::PlayTimeManager& play_time_manager_,
+ Core::System& system_);
~GameListWorker() override;
/// Starts the processing of directory tree information.
@@ -76,6 +79,7 @@ private:
FileSys::ManualContentProvider* provider;
QVector<UISettings::GameDir>& game_dirs;
const CompatibilityList& compatibility_list;
+ const PlayTime::PlayTimeManager& play_time_manager;
QStringList watch_list;
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 @@
#include <sys/socket.h>
#endif
+#include <boost/container/flat_set.hpp>
+
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/qt_amiibo_settings.h"
#include "applets/qt_controller.h"
@@ -96,6 +98,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#ifdef _WIN32
+#include <shlobj.h>
#include "common/windows/timer_resolution.h"
#endif
#ifdef ARCHITECTURE_x86_64
@@ -148,6 +151,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/install_dialog.h"
#include "yuzu/loading_screen.h"
#include "yuzu/main.h"
+#include "yuzu/play_time_manager.h"
#include "yuzu/startup_checks.h"
#include "yuzu/uisettings.h"
#include "yuzu/util/clickable_label.h"
@@ -336,6 +340,8 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
discord_rpc->Update();
+ play_time_manager = std::make_unique<PlayTime::PlayTimeManager>();
+
system->GetRoomNetwork().Init();
RegisterMetaTypes();
@@ -984,7 +990,7 @@ void GMainWindow::InitializeWidgets() {
render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem, *system);
render_window->hide();
- game_list = new GameList(vfs, provider.get(), *system, this);
+ game_list = new GameList(vfs, provider.get(), *play_time_manager, *system, this);
ui->horizontalLayout->addWidget(game_list);
game_list_placeholder = new GameListPlaceholder(this);
@@ -1445,6 +1451,7 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
Settings::values.audio_muted = false;
auto_muted = false;
}
+ UpdateVolumeUI();
}
}
@@ -1458,6 +1465,8 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::RemoveInstalledEntryRequested, this,
&GMainWindow::OnGameListRemoveInstalledEntry);
connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile);
+ connect(game_list, &GameList::RemovePlayTimeRequested, this,
+ &GMainWindow::OnGameListRemovePlayTimeData);
connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS);
connect(game_list, &GameList::VerifyIntegrityRequested, this,
&GMainWindow::OnGameListVerifyIntegrity);
@@ -1551,6 +1560,15 @@ void GMainWindow::ConnectMenuEvents() {
// Tools
connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
ReinitializeKeyBehavior::Warning));
+ connect_menu(ui->action_Load_Album, &GMainWindow::OnAlbum);
+ connect_menu(ui->action_Load_Cabinet_Nickname_Owner,
+ [this]() { OnCabinet(Service::NFP::CabinetMode::StartNicknameAndOwnerSettings); });
+ connect_menu(ui->action_Load_Cabinet_Eraser,
+ [this]() { OnCabinet(Service::NFP::CabinetMode::StartGameDataEraser); });
+ connect_menu(ui->action_Load_Cabinet_Restorer,
+ [this]() { OnCabinet(Service::NFP::CabinetMode::StartRestorer); });
+ connect_menu(ui->action_Load_Cabinet_Formatter,
+ [this]() { OnCabinet(Service::NFP::CabinetMode::StartFormatter); });
connect_menu(ui->action_Load_Mii_Edit, &GMainWindow::OnMiiEdit);
connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot);
@@ -1568,6 +1586,7 @@ void GMainWindow::ConnectMenuEvents() {
void GMainWindow::UpdateMenuState() {
const bool is_paused = emu_thread == nullptr || !emu_thread->IsRunning();
+ const bool is_firmware_available = CheckFirmwarePresence();
const std::array running_actions{
ui->action_Stop,
@@ -1578,10 +1597,23 @@ void GMainWindow::UpdateMenuState() {
ui->action_Pause,
};
+ const std::array applet_actions{
+ ui->action_Load_Album,
+ ui->action_Load_Cabinet_Nickname_Owner,
+ ui->action_Load_Cabinet_Eraser,
+ ui->action_Load_Cabinet_Restorer,
+ ui->action_Load_Cabinet_Formatter,
+ ui->action_Load_Mii_Edit,
+ };
+
for (QAction* action : running_actions) {
action->setEnabled(emulation_running);
}
+ for (QAction* action : applet_actions) {
+ action->setEnabled(is_firmware_available && !emulation_running);
+ }
+
ui->action_Capture_Screenshot->setEnabled(emulation_running && !is_paused);
if (emulation_running && is_paused) {
@@ -1591,8 +1623,6 @@ void GMainWindow::UpdateMenuState() {
}
multiplayer_state->UpdateNotificationStatus();
-
- ui->action_Load_Mii_Edit->setEnabled(CheckFirmwarePresence());
}
void GMainWindow::OnDisplayTitleBars(bool show) {
@@ -2103,6 +2133,8 @@ void GMainWindow::OnEmulationStopped() {
OnTasStateChanged();
render_window->FinalizeCamera();
+ system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::None);
+
// Enable all controllers
system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
@@ -2511,6 +2543,17 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
}
}
+void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) {
+ if (QMessageBox::question(this, tr("Remove Play Time Data"), tr("Reset play time?"),
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No) != QMessageBox::Yes) {
+ return;
+ }
+
+ play_time_manager->ResetProgramPlayTime(program_id);
+ game_list->PopulateAsync(UISettings::values.game_dirs);
+}
+
void GMainWindow::RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target) {
const auto target_file_name = [target] {
switch (target) {
@@ -2802,7 +2845,6 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
const QStringList args = QApplication::arguments();
std::filesystem::path yuzu_command = args[0].toStdString();
-#if defined(__linux__) || defined(__FreeBSD__)
// If relative path, make it an absolute path
if (yuzu_command.c_str()[0] == '.') {
yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
@@ -2825,12 +2867,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
UISettings::values.shortcut_already_warned = true;
}
#endif // __linux__
-#endif // __linux__ || __FreeBSD__
std::filesystem::path target_directory{};
// Determine target directory for shortcut
-#if defined(__linux__) || defined(__FreeBSD__)
+#if defined(WIN32)
+ const char* home = std::getenv("USERPROFILE");
+#else
const char* home = std::getenv("HOME");
+#endif
const std::filesystem::path home_path = (home == nullptr ? "~" : home);
const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
@@ -2840,7 +2884,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
QMessageBox::critical(
this, tr("Create Shortcut"),
tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
- .arg(QString::fromStdString(target_directory)),
+ .arg(QString::fromStdString(target_directory.generic_string())),
QMessageBox::StandardButton::Ok);
return;
}
@@ -2848,15 +2892,15 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
"applications";
if (!Common::FS::CreateDirs(target_directory)) {
- QMessageBox::critical(this, tr("Create Shortcut"),
- tr("Cannot create shortcut in applications menu. Path \"%1\" "
- "does not exist and cannot be created.")
- .arg(QString::fromStdString(target_directory)),
- QMessageBox::StandardButton::Ok);
+ QMessageBox::critical(
+ this, tr("Create Shortcut"),
+ tr("Cannot create shortcut in applications menu. Path \"%1\" "
+ "does not exist and cannot be created.")
+ .arg(QString::fromStdString(target_directory.generic_string())),
+ QMessageBox::StandardButton::Ok);
return;
}
}
-#endif
const std::string game_file_name = std::filesystem::path(game_path).filename().string();
// Determine full paths for icon and shortcut
@@ -2878,9 +2922,14 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
const std::filesystem::path shortcut_path =
target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
: fmt::format("yuzu-{:016X}.desktop", program_id));
+#elif defined(WIN32)
+ std::filesystem::path icons_path =
+ Common::FS::GetYuzuPathString(Common::FS::YuzuPath::IconsDir);
+ std::filesystem::path icon_path =
+ icons_path / ((program_id == 0 ? fmt::format("yuzu-{}.ico", game_file_name)
+ : fmt::format("yuzu-{:016X}.ico", program_id)));
#else
- const std::filesystem::path icon_path{};
- const std::filesystem::path shortcut_path{};
+ std::string icon_extension;
#endif
// Get title from game file
@@ -2905,29 +2954,37 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
}
- QImage icon_jpeg =
+ QImage icon_data =
QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
#if defined(__linux__) || defined(__FreeBSD__)
// Convert and write the icon as a PNG
- if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) {
+ if (!icon_data.save(QString::fromStdString(icon_path.string()))) {
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
} else {
LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
}
+#elif defined(WIN32)
+ if (!SaveIconToFile(icon_path.string(), icon_data)) {
+ LOG_ERROR(Frontend, "Could not write icon to file");
+ return;
+ }
#endif // __linux__
-#if defined(__linux__) || defined(__FreeBSD__)
+#ifdef _WIN32
+ // Replace characters that are illegal in Windows filenames by a dash
+ const std::string illegal_chars = "<>:\"/\\|?*";
+ for (char c : illegal_chars) {
+ std::replace(title.begin(), title.end(), c, '_');
+ }
+ const std::filesystem::path shortcut_path = target_directory / (title + ".lnk").c_str();
+#endif
+
const std::string comment =
tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
const std::string categories = "Game;Emulator;Qt;";
const std::string keywords = "Switch;Nintendo;";
-#else
- const std::string comment{};
- const std::string arguments{};
- const std::string categories{};
- const std::string keywords{};
-#endif
+
if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
yuzu_command.string(), arguments, categories, keywords)) {
QMessageBox::critical(this, tr("Create Shortcut"),
@@ -3334,6 +3391,9 @@ void GMainWindow::OnStartGame() {
UpdateMenuState();
OnTasStateChanged();
+ play_time_manager->SetProgramId(system->GetApplicationProcessProgramID());
+ play_time_manager->Start();
+
discord_rpc->Update();
}
@@ -3349,6 +3409,7 @@ void GMainWindow::OnRestartGame() {
void GMainWindow::OnPauseGame() {
emu_thread->SetRunning(false);
+ play_time_manager->Stop();
UpdateMenuState();
AllowOSSleep();
}
@@ -3369,6 +3430,9 @@ void GMainWindow::OnStopGame() {
return;
}
+ play_time_manager->Stop();
+ // Update game list to show new play time
+ game_list->PopulateAsync(UISettings::values.game_dirs);
if (OnShutdownBegin()) {
OnShutdownBeginDialog();
} else {
@@ -3942,6 +4006,34 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st
shortcut_stream.close();
return true;
+#elif defined(WIN32)
+ IShellLinkW* shell_link;
+ auto hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW,
+ (void**)&shell_link);
+ if (FAILED(hres)) {
+ return false;
+ }
+ shell_link->SetPath(
+ Common::UTF8ToUTF16W(command).data()); // Path to the object we are referring to
+ shell_link->SetArguments(Common::UTF8ToUTF16W(arguments).data());
+ shell_link->SetDescription(Common::UTF8ToUTF16W(comment).data());
+ shell_link->SetIconLocation(Common::UTF8ToUTF16W(icon_path).data(), 0);
+
+ IPersistFile* persist_file;
+ hres = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file);
+ if (FAILED(hres)) {
+ return false;
+ }
+
+ hres = persist_file->Save(Common::UTF8ToUTF16W(shortcut_path).data(), TRUE);
+ if (FAILED(hres)) {
+ return false;
+ }
+
+ persist_file->Release();
+ shell_link->Release();
+
+ return true;
#endif
return false;
}
@@ -4134,6 +4226,53 @@ void GMainWindow::OnToggleStatusBar() {
statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
}
+void GMainWindow::OnAlbum() {
+ constexpr u64 AlbumId = 0x010000000000100Dull;
+ auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ QMessageBox::warning(this, tr("No firmware available"),
+ tr("Please install the firmware to use the Album applet."));
+ return;
+ }
+
+ auto album_nca = bis_system->GetEntry(AlbumId, FileSys::ContentRecordType::Program);
+ if (!album_nca) {
+ QMessageBox::warning(this, tr("Album Applet"),
+ tr("Album applet is not available. Please reinstall firmware."));
+ return;
+ }
+
+ system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::PhotoViewer);
+
+ const auto filename = QString::fromStdString(album_nca->GetFullPath());
+ UISettings::values.roms_path = QFileInfo(filename).path();
+ BootGame(filename);
+}
+
+void GMainWindow::OnCabinet(Service::NFP::CabinetMode mode) {
+ constexpr u64 CabinetId = 0x0100000000001002ull;
+ auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ QMessageBox::warning(this, tr("No firmware available"),
+ tr("Please install the firmware to use the Cabinet applet."));
+ return;
+ }
+
+ auto cabinet_nca = bis_system->GetEntry(CabinetId, FileSys::ContentRecordType::Program);
+ if (!cabinet_nca) {
+ QMessageBox::warning(this, tr("Cabinet Applet"),
+ tr("Cabinet applet is not available. Please reinstall firmware."));
+ return;
+ }
+
+ system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::Cabinet);
+ system->GetAppletManager().SetCabinetMode(mode);
+
+ const auto filename = QString::fromStdString(cabinet_nca->GetFullPath());
+ UISettings::values.roms_path = QFileInfo(filename).path();
+ BootGame(filename);
+}
+
void GMainWindow::OnMiiEdit() {
constexpr u64 MiiEditId = 0x0100000000001009ull;
auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
@@ -4150,6 +4289,8 @@ void GMainWindow::OnMiiEdit() {
return;
}
+ system->GetAppletManager().SetCurrentAppletId(Service::AM::Applets::AppletId::MiiEdit);
+
const auto filename = QString::fromStdString((mii_applet_nca->GetFullPath()));
UISettings::values.roms_path = QFileInfo(filename).path();
BootGame(filename);
@@ -4602,8 +4743,8 @@ bool GMainWindow::CheckFirmwarePresence() {
bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id,
u64* selected_title_id, u8* selected_content_record_type) {
- using ContentInfo = std::pair<FileSys::TitleType, FileSys::ContentRecordType>;
- boost::container::flat_map<u64, ContentInfo> available_title_ids;
+ using ContentInfo = std::tuple<u64, FileSys::TitleType, FileSys::ContentRecordType>;
+ boost::container::flat_set<ContentInfo> available_title_ids;
const auto RetrieveEntries = [&](FileSys::TitleType title_type,
FileSys::ContentRecordType record_type) {
@@ -4611,12 +4752,14 @@ bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installe
for (const auto& entry : entries) {
if (FileSys::GetBaseTitleID(entry.title_id) == program_id &&
installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success) {
- available_title_ids[entry.title_id] = {title_type, record_type};
+ available_title_ids.insert({entry.title_id, title_type, record_type});
}
}
};
RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::Program);
+ RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::HtmlDocument);
+ RetrieveEntries(FileSys::TitleType::Application, FileSys::ContentRecordType::LegalInformation);
RetrieveEntries(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
if (available_title_ids.empty()) {
@@ -4627,10 +4770,14 @@ bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installe
if (available_title_ids.size() > 1) {
QStringList list;
- for (auto& [title_id, content_info] : available_title_ids) {
+ for (auto& [title_id, title_type, record_type] : available_title_ids) {
const auto hex_title_id = QString::fromStdString(fmt::format("{:X}", title_id));
- if (content_info.first == FileSys::TitleType::Application) {
- list.push_back(QStringLiteral("Application [%1]").arg(hex_title_id));
+ if (record_type == FileSys::ContentRecordType::Program) {
+ list.push_back(QStringLiteral("Program [%1]").arg(hex_title_id));
+ } else if (record_type == FileSys::ContentRecordType::HtmlDocument) {
+ list.push_back(QStringLiteral("HTML document [%1]").arg(hex_title_id));
+ } else if (record_type == FileSys::ContentRecordType::LegalInformation) {
+ list.push_back(QStringLiteral("Legal information [%1]").arg(hex_title_id));
} else {
list.push_back(
QStringLiteral("DLC %1 [%2]").arg(title_id & 0x7FF).arg(hex_title_id));
@@ -4648,9 +4795,9 @@ bool GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installe
title_index = list.indexOf(res);
}
- const auto selected_info = available_title_ids.nth(title_index);
- *selected_title_id = selected_info->first;
- *selected_content_record_type = static_cast<u8>(selected_info->second.second);
+ const auto& [title_id, title_type, record_type] = *available_title_ids.nth(title_index);
+ *selected_title_id = title_id;
+ *selected_content_record_type = static_cast<u8>(record_type);
return true;
}
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 {
class DiscordInterface;
}
+namespace PlayTime {
+class PlayTimeManager;
+}
+
namespace FileSys {
class ContentProvider;
class ManualContentProvider;
@@ -102,6 +106,10 @@ namespace Service::NFC {
class NfcDevice;
} // namespace Service::NFC
+namespace Service::NFP {
+enum class CabinetMode : u8;
+} // namespace Service::NFP
+
namespace Ui {
class MainWindow;
}
@@ -319,6 +327,7 @@ private slots:
void OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type);
void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
const std::string& game_path);
+ void OnGameListRemovePlayTimeData(u64 program_id);
void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
void OnGameListVerifyIntegrity(const std::string& game_path);
void OnGameListCopyTID(u64 program_id);
@@ -365,6 +374,8 @@ private slots:
void ResetWindowSize720();
void ResetWindowSize900();
void ResetWindowSize1080();
+ void OnAlbum();
+ void OnCabinet(Service::NFP::CabinetMode mode);
void OnMiiEdit();
void OnCaptureScreenshot();
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
@@ -384,6 +395,7 @@ private:
void RemoveVulkanDriverPipelineCache(u64 program_id);
void RemoveAllTransferableShaderCaches(u64 program_id);
void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
+ void RemovePlayTimeData(u64 program_id);
void RemoveCacheStorage(u64 program_id);
bool SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id,
u64* selected_title_id, u8* selected_content_record_type);
@@ -423,6 +435,7 @@ private:
std::unique_ptr<Core::System> system;
std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
+ std::unique_ptr<PlayTime::PlayTimeManager> play_time_manager;
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
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 @@
<property name="title">
<string>&amp;Tools</string>
</property>
+ <widget class="QMenu" name="menu_cabinet_applet">
+ <property name="title">
+ <string>&amp;Amiibo</string>
+ </property>
+ <addaction name="action_Load_Cabinet_Nickname_Owner"/>
+ <addaction name="action_Load_Cabinet_Eraser"/>
+ <addaction name="action_Load_Cabinet_Restorer"/>
+ <addaction name="action_Load_Cabinet_Formatter"/>
+ </widget>
<widget class="QMenu" name="menuTAS">
<property name="title">
<string>&amp;TAS</string>
@@ -150,6 +159,8 @@
<addaction name="action_Rederive"/>
<addaction name="action_Verify_installed_contents"/>
<addaction name="separator"/>
+ <addaction name="menu_cabinet_applet"/>
+ <addaction name="action_Load_Album"/>
<addaction name="action_Load_Mii_Edit"/>
<addaction name="separator"/>
<addaction name="action_Capture_Screenshot"/>
@@ -370,6 +381,31 @@
<string>&amp;Capture Screenshot</string>
</property>
</action>
+ <action name="action_Load_Album">
+ <property name="text">
+ <string>Open &amp;Album</string>
+ </property>
+ </action>
+ <action name="action_Load_Cabinet_Nickname_Owner">
+ <property name="text">
+ <string>&amp;Set Nickname and Owner</string>
+ </property>
+ </action>
+ <action name="action_Load_Cabinet_Eraser">
+ <property name="text">
+ <string>&amp;Delete Game Data</string>
+ </property>
+ </action>
+ <action name="action_Load_Cabinet_Restorer">
+ <property name="text">
+ <string>&amp;Restore Amiibo</string>
+ </property>
+ </action>
+ <action name="action_Load_Cabinet_Formatter">
+ <property name="text">
+ <string>&amp;Format Amiibo</string>
+ </property>
+ </action>
<action name="action_Load_Mii_Edit">
<property name="text">
<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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/alignment.h"
+#include "common/fs/file.h"
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "common/thread.h"
+#include "core/hle/service/acc/profile_manager.h"
+#include "yuzu/play_time_manager.h"
+
+namespace PlayTime {
+
+namespace {
+
+struct PlayTimeElement {
+ ProgramId program_id;
+ PlayTime play_time;
+};
+
+std::optional<std::filesystem::path> GetCurrentUserPlayTimePath() {
+ const Service::Account::ProfileManager manager;
+ const auto uuid = manager.GetUser(static_cast<s32>(Settings::values.current_user));
+ if (!uuid.has_value()) {
+ return std::nullopt;
+ }
+ return Common::FS::GetYuzuPath(Common::FS::YuzuPath::PlayTimeDir) /
+ uuid->RawString().append(".bin");
+}
+
+[[nodiscard]] bool ReadPlayTimeFile(PlayTimeDatabase& out_play_time_db) {
+ const auto filename = GetCurrentUserPlayTimePath();
+
+ if (!filename.has_value()) {
+ LOG_ERROR(Frontend, "Failed to get current user path");
+ return false;
+ }
+
+ out_play_time_db.clear();
+
+ if (Common::FS::Exists(filename.value())) {
+ Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Read,
+ Common::FS::FileType::BinaryFile};
+ if (!file.IsOpen()) {
+ LOG_ERROR(Frontend, "Failed to open play time file: {}",
+ Common::FS::PathToUTF8String(filename.value()));
+ return false;
+ }
+
+ const size_t num_elements = file.GetSize() / sizeof(PlayTimeElement);
+ std::vector<PlayTimeElement> elements(num_elements);
+
+ if (file.ReadSpan<PlayTimeElement>(elements) != num_elements) {
+ return false;
+ }
+
+ for (const auto& [program_id, play_time] : elements) {
+ if (program_id != 0) {
+ out_play_time_db[program_id] = play_time;
+ }
+ }
+ }
+
+ return true;
+}
+
+[[nodiscard]] bool WritePlayTimeFile(const PlayTimeDatabase& play_time_db) {
+ const auto filename = GetCurrentUserPlayTimePath();
+
+ if (!filename.has_value()) {
+ LOG_ERROR(Frontend, "Failed to get current user path");
+ return false;
+ }
+
+ Common::FS::IOFile file{filename.value(), Common::FS::FileAccessMode::Write,
+ Common::FS::FileType::BinaryFile};
+ if (!file.IsOpen()) {
+ LOG_ERROR(Frontend, "Failed to open play time file: {}",
+ Common::FS::PathToUTF8String(filename.value()));
+ return false;
+ }
+
+ std::vector<PlayTimeElement> elements;
+ elements.reserve(play_time_db.size());
+
+ for (auto& [program_id, play_time] : play_time_db) {
+ if (program_id != 0) {
+ elements.push_back(PlayTimeElement{program_id, play_time});
+ }
+ }
+
+ return file.WriteSpan<PlayTimeElement>(elements) == elements.size();
+}
+
+} // namespace
+
+PlayTimeManager::PlayTimeManager() {
+ if (!ReadPlayTimeFile(database)) {
+ LOG_ERROR(Frontend, "Failed to read play time database! Resetting to default.");
+ }
+}
+
+PlayTimeManager::~PlayTimeManager() {
+ Save();
+}
+
+void PlayTimeManager::SetProgramId(u64 program_id) {
+ running_program_id = program_id;
+}
+
+void PlayTimeManager::Start() {
+ play_time_thread = std::jthread([&](std::stop_token stop_token) { AutoTimestamp(stop_token); });
+}
+
+void PlayTimeManager::Stop() {
+ play_time_thread = {};
+}
+
+void PlayTimeManager::AutoTimestamp(std::stop_token stop_token) {
+ Common::SetCurrentThreadName("PlayTimeReport");
+
+ using namespace std::literals::chrono_literals;
+ using std::chrono::seconds;
+ using std::chrono::steady_clock;
+
+ auto timestamp = steady_clock::now();
+
+ const auto GetDuration = [&]() -> u64 {
+ const auto last_timestamp = std::exchange(timestamp, steady_clock::now());
+ const auto duration = std::chrono::duration_cast<seconds>(timestamp - last_timestamp);
+ return static_cast<u64>(duration.count());
+ };
+
+ while (!stop_token.stop_requested()) {
+ Common::StoppableTimedWait(stop_token, 30s);
+
+ database[running_program_id] += GetDuration();
+ Save();
+ }
+}
+
+void PlayTimeManager::Save() {
+ if (!WritePlayTimeFile(database)) {
+ LOG_ERROR(Frontend, "Failed to update play time database!");
+ }
+}
+
+u64 PlayTimeManager::GetPlayTime(u64 program_id) const {
+ auto it = database.find(program_id);
+ if (it != database.end()) {
+ return it->second;
+ } else {
+ return 0;
+ }
+}
+
+void PlayTimeManager::ResetProgramPlayTime(u64 program_id) {
+ database.erase(program_id);
+ Save();
+}
+
+QString ReadablePlayTime(qulonglong time_seconds) {
+ if (time_seconds == 0) {
+ return {};
+ }
+ const auto time_minutes = std::max(static_cast<double>(time_seconds) / 60, 1.0);
+ const auto time_hours = static_cast<double>(time_seconds) / 3600;
+ const bool is_minutes = time_minutes < 60;
+ const char* unit = is_minutes ? "m" : "h";
+ const auto value = is_minutes ? time_minutes : time_hours;
+
+ return QStringLiteral("%L1 %2")
+ .arg(value, 0, 'f', !is_minutes && time_seconds % 60 != 0)
+ .arg(QString::fromUtf8(unit));
+}
+
+} // 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 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <QString>
+
+#include <map>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/polyfill_thread.h"
+
+namespace PlayTime {
+
+using ProgramId = u64;
+using PlayTime = u64;
+using PlayTimeDatabase = std::map<ProgramId, PlayTime>;
+
+class PlayTimeManager {
+public:
+ explicit PlayTimeManager();
+ ~PlayTimeManager();
+
+ YUZU_NON_COPYABLE(PlayTimeManager);
+ YUZU_NON_MOVEABLE(PlayTimeManager);
+
+ u64 GetPlayTime(u64 program_id) const;
+ void ResetProgramPlayTime(u64 program_id);
+ void SetProgramId(u64 program_id);
+ void Start();
+ void Stop();
+
+private:
+ PlayTimeDatabase database;
+ u64 running_program_id;
+ std::jthread play_time_thread;
+ void AutoTimestamp(std::stop_token stop_token);
+ void Save();
+};
+
+QString ReadablePlayTime(qulonglong time_seconds);
+
+} // 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 {
true,
true};
Setting<bool> mute_when_in_background{
- linkage, false, "muteWhenInBackground", Category::Ui, Settings::Specialization::Default,
+ linkage, false, "muteWhenInBackground", Category::Audio, Settings::Specialization::Default,
true, true};
Setting<bool> hide_mouse{
linkage, true, "hideInactiveMouse", Category::UiGeneral, Settings::Specialization::Default,
@@ -183,6 +183,9 @@ struct Values {
Setting<bool> show_size{linkage, true, "show_size", Category::UiGameList};
Setting<bool> show_types{linkage, true, "show_types", Category::UiGameList};
+ // Play time
+ Setting<bool> show_play_time{linkage, true, "show_play_time", Category::UiGameList};
+
bool configuration_applied;
bool reset_to_defaults;
bool shortcut_already_warned{false};
diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp
index 5c3e4589e..61cf00176 100644
--- a/src/yuzu/util/util.cpp
+++ b/src/yuzu/util/util.cpp
@@ -5,6 +5,10 @@
#include <cmath>
#include <QPainter>
#include "yuzu/util/util.h"
+#ifdef _WIN32
+#include <windows.h>
+#include "common/fs/file.h"
+#endif
QFont GetMonospaceFont() {
QFont font(QStringLiteral("monospace"));
@@ -37,3 +41,76 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) {
painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0);
return circle_pixmap;
}
+
+bool SaveIconToFile(const std::string_view path, const QImage& image) {
+#if defined(WIN32)
+#pragma pack(push, 2)
+ struct IconDir {
+ WORD id_reserved;
+ WORD id_type;
+ WORD id_count;
+ };
+
+ struct IconDirEntry {
+ BYTE width;
+ BYTE height;
+ BYTE color_count;
+ BYTE reserved;
+ WORD planes;
+ WORD bit_count;
+ DWORD bytes_in_res;
+ DWORD image_offset;
+ };
+#pragma pack(pop)
+
+ QImage source_image = image.convertToFormat(QImage::Format_RGB32);
+ constexpr int bytes_per_pixel = 4;
+ const int image_size = source_image.width() * source_image.height() * bytes_per_pixel;
+
+ BITMAPINFOHEADER info_header{};
+ info_header.biSize = sizeof(BITMAPINFOHEADER), info_header.biWidth = source_image.width(),
+ info_header.biHeight = source_image.height() * 2, info_header.biPlanes = 1,
+ info_header.biBitCount = bytes_per_pixel * 8, info_header.biCompression = BI_RGB;
+
+ const IconDir icon_dir{.id_reserved = 0, .id_type = 1, .id_count = 1};
+ const IconDirEntry icon_entry{.width = static_cast<BYTE>(source_image.width()),
+ .height = static_cast<BYTE>(source_image.height() * 2),
+ .color_count = 0,
+ .reserved = 0,
+ .planes = 1,
+ .bit_count = bytes_per_pixel * 8,
+ .bytes_in_res =
+ static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
+ .image_offset = sizeof(IconDir) + sizeof(IconDirEntry)};
+
+ Common::FS::IOFile icon_file(path, Common::FS::FileAccessMode::Write,
+ Common::FS::FileType::BinaryFile);
+ if (!icon_file.IsOpen()) {
+ return false;
+ }
+
+ if (!icon_file.Write(icon_dir)) {
+ return false;
+ }
+ if (!icon_file.Write(icon_entry)) {
+ return false;
+ }
+ if (!icon_file.Write(info_header)) {
+ return false;
+ }
+
+ for (int y = 0; y < image.height(); y++) {
+ const auto* line = source_image.scanLine(source_image.height() - 1 - y);
+ std::vector<u8> line_data(source_image.width() * bytes_per_pixel);
+ std::memcpy(line_data.data(), line, line_data.size());
+ if (!icon_file.Write(line_data)) {
+ return false;
+ }
+ }
+ icon_file.Close();
+
+ return true;
+#else
+ return false;
+#endif
+}
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 @@
#include <QString>
/// Returns a QFont object appropriate to use as a monospace font for debugging widgets, etc.
-QFont GetMonospaceFont();
+[[nodiscard]] QFont GetMonospaceFont();
/// Convert a size in bytes into a readable format (KiB, MiB, etc.)
-QString ReadableByteSize(qulonglong size);
+[[nodiscard]] QString ReadableByteSize(qulonglong size);
/**
* Creates a circle pixmap from a specified color
* @param color The color the pixmap shall have
* @return QPixmap circle pixmap
*/
-QPixmap CreateCirclePixmapFromColor(const QColor& color);
+[[nodiscard]] QPixmap CreateCirclePixmapFromColor(const QColor& color);
+
+/**
+ * Saves a windows icon to a file
+ * @param path The icons path
+ * @param image The image to save
+ * @return bool If the operation succeeded
+ */
+[[nodiscard]] bool SaveIconToFile(const std::string_view path, const QImage& image);