aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMaufeat <sahyno1996@gmail.com>2026-08-29 01:04:02 +0200
committercrueter <crueter@eden-emu.dev>2026-08-29 01:04:02 +0200
commit54cd5fb8ebf11b71c676b72a32811873caef20de (patch)
tree20fef6adb49da98d3d87ae152853229bf762a3bb /src
parent119291dc77fbe8e4a23eaafa8420c1c8577d1b59 (diff)
[core, hle, video_core, memory] Improve multi-process, display layers, dynamic shader cache and rework overlay (#4238)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions). - [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md) - [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability. ------------------- - Dynamic shader cache reloading capability for qlaunch - Multi-process improvements (thanks to @frank1734), instead of just using the main application we now respect caller process (also did it for HID devices while I was at it) - Layer stack masks & shared buffer screenshot for video core, added different masks (screenshot, recording, etc.) this was discovered as an issue due to how in qlaunch the transition was not right between applications. May not be perfect but also fixes screenshots while using qlaunch - Reworked overlay display management (input and visibility) - instead of random numbers as I've previously did, I decided to add an AppletZIndex enum for better readability. Also split capability of input by touch and gamepad. - etc. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4238 Reviewed-by: Samuel <lizzie@eden-emu.dev> Reviewed-by: CamilleLaVey <camillelavey99@gmail.com> Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Diffstat (limited to 'src')
-rw-r--r--src/android/app/src/main/jni/native.cpp63
-rw-r--r--src/android/app/src/main/jni/native.h6
-rw-r--r--src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp12
-rw-r--r--src/audio_core/opus/hardware_opus.cpp5
-rw-r--r--src/core/core.cpp45
-rw-r--r--src/core/core.h9
-rw-r--r--src/core/file_sys/content_archive.cpp4
-rw-r--r--src/core/file_sys/content_archive.h5
-rw-r--r--src/core/file_sys/fssystem/fssystem_indirect_storage.cpp13
-rw-r--r--src/core/file_sys/fssystem/fssystem_indirect_storage.h12
-rw-r--r--src/core/file_sys/nca_metadata.h4
-rw-r--r--src/core/file_sys/registered_cache.cpp35
-rw-r--r--src/core/file_sys/submission_package.cpp14
-rw-r--r--src/core/hle/kernel/kernel.cpp25
-rw-r--r--src/core/hle/kernel/kernel.h7
-rw-r--r--src/core/hle/service/am/am_results.h4
-rw-r--r--src/core/hle/service/am/am_types.h10
-rw-r--r--src/core/hle/service/am/applet.cpp18
-rw-r--r--src/core/hle/service/am/applet.h8
-rw-r--r--src/core/hle/service/am/display_layer_manager.cpp57
-rw-r--r--src/core/hle/service/am/display_layer_manager.h10
-rw-r--r--src/core/hle/service/am/hid_registration.cpp17
-rw-r--r--src/core/hle/service/am/hid_registration.h2
-rw-r--r--src/core/hle/service/am/service/applet_common_functions.cpp4
-rw-r--r--src/core/hle/service/am/service/application_functions.cpp47
-rw-r--r--src/core/hle/service/am/service/display_controller.cpp27
-rw-r--r--src/core/hle/service/am/service/overlay_functions.cpp28
-rw-r--r--src/core/hle/service/am/service/overlay_functions.h3
-rw-r--r--src/core/hle/service/am/window_system.cpp212
-rw-r--r--src/core/hle/service/am/window_system.h9
-rw-r--r--src/core/hle/service/aoc/addon_content_manager.cpp14
-rw-r--r--src/core/hle/service/bcat/bcat_service.cpp12
-rw-r--r--src/core/hle/service/bcat/bcat_service.h6
-rw-r--r--src/core/hle/service/bcat/service_creator.cpp9
-rw-r--r--src/core/hle/service/caps/caps_manager.cpp9
-rw-r--r--src/core/hle/service/caps/caps_su.cpp3
-rw-r--r--src/core/hle/service/hid/hid_server.cpp2
-rw-r--r--src/core/hle/service/hid/hidbus.cpp2
-rw-r--r--src/core/hle/service/hid/irs.cpp8
-rw-r--r--src/core/hle/service/jit/jit.cpp2
-rw-r--r--src/core/hle/service/ns/read_only_application_control_data_interface.cpp5
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp1
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp19
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.h9
-rw-r--r--src/core/hle/service/nvnflinger/display.h6
-rw-r--r--src/core/hle/service/nvnflinger/hardware_composer.cpp1
-rw-r--r--src/core/hle/service/nvnflinger/hwc_layer.h23
-rw-r--r--src/core/hle/service/nvnflinger/surface_flinger.cpp10
-rw-r--r--src/core/hle/service/nvnflinger/surface_flinger.h3
-rw-r--r--src/core/hle/service/pctl/parental_control_service.cpp7
-rw-r--r--src/core/hle/service/pctl/parental_control_service.h4
-rw-r--r--src/core/hle/service/pctl/parental_control_service_factory.cpp16
-rw-r--r--src/core/hle/service/pm/pm.cpp7
-rw-r--r--src/core/hle/service/prepo/prepo.cpp4
-rw-r--r--src/core/hle/service/vi/container.cpp12
-rw-r--r--src/core/hle/service/vi/container.h3
-rw-r--r--src/core/hle/service/vi/shared_buffer_manager.cpp301
-rw-r--r--src/core/hle/service/vi/shared_buffer_manager.h17
-rw-r--r--src/core/loader/loader.cpp39
-rw-r--r--src/core/loader/nca.cpp46
-rw-r--r--src/core/loader/nca.h12
-rw-r--r--src/core/loader/nso.cpp5
-rw-r--r--src/core/loader/nsp.cpp5
-rw-r--r--src/core/memory/cheat_engine.cpp37
-rw-r--r--src/core/memory/cheat_engine.h11
-rw-r--r--src/core/reporter.cpp2
-rw-r--r--src/hid_core/hidbus/hidbus_base.cpp6
-rw-r--r--src/hid_core/hidbus/hidbus_base.h7
-rw-r--r--src/hid_core/hidbus/ringcon.cpp7
-rw-r--r--src/hid_core/irsensor/image_transfer_processor.cpp29
-rw-r--r--src/hid_core/irsensor/image_transfer_processor.h10
-rw-r--r--src/hid_core/resources/six_axis/seven_six_axis.cpp13
-rw-r--r--src/hid_core/resources/six_axis/seven_six_axis.h10
-rw-r--r--src/hid_core/resources/touch_screen/touch_screen_resource.cpp14
-rw-r--r--src/qt_common/render/emu_thread.cpp39
-rw-r--r--src/qt_common/render/emu_thread.h17
-rw-r--r--src/video_core/framebuffer_config.cpp24
-rw-r--r--src/video_core/framebuffer_config.h15
-rw-r--r--src/video_core/gpu.cpp10
-rw-r--r--src/video_core/gpu.h2
-rw-r--r--src/video_core/renderer_base.cpp4
-rw-r--r--src/video_core/renderer_base.h10
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp13
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h5
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp13
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h3
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp8
-rw-r--r--src/yuzu/main_window.cpp82
-rw-r--r--src/yuzu/main_window.h1
-rw-r--r--src/yuzu_cmd/yuzu.cpp27
90 files changed, 1400 insertions, 356 deletions
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 4d0c39f5c7..e8e9f0a14b 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -76,6 +76,7 @@ extern "C" {
#include "core/frontend/applets/software_keyboard.h"
#include "core/frontend/applets/web_browser.h"
#include "common/android/applets/web_browser.h"
+#include "core/file_sys/common_funcs.h"
#include "core/hle/service/am/applet_manager.h"
#include "core/hle/service/am/frontend/applets.h"
#include "core/hle/service/filesystem/filesystem.h"
@@ -311,11 +312,23 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
ConfigureFilesystemProvider(filepath);
// Load the ROM.
+ const u64 previous_program_id =
+ program_index != 0 && m_next_program_id.load() >
+ static_cast<u64>(Service::AM::AppletProgramId::MaxProgramId)
+ ? m_next_program_id.load()
+ : 0;
+
Service::AM::FrontendAppletParameters params{
+ .program_id = previous_program_id,
.applet_id = static_cast<Service::AM::AppletId>(m_applet_id),
.launch_type = frontend_initiated ? Service::AM::LaunchType::FrontendInitiated
: Service::AM::LaunchType::ApplicationInitiated,
.program_index = static_cast<s32>(program_index),
+ .previous_program_index =
+ previous_program_id != 0
+ ? static_cast<s32>(previous_program_id -
+ FileSys::GetBaseTitleID(previous_program_id))
+ : -1,
};
m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath, params);
@@ -328,8 +341,12 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
m_system.GetCpuManager().OnGpuReady();
m_system.RegisterExitCallback([&] { HaltEmulation(); });
+ m_system.RegisterApplicationChangedCallback(
+ [&](u64 changed_program_id) { RequestDiskShaderCacheReload(changed_program_id); });
+
// Register an ExecuteProgram callback such that Core can execute a sub-program
m_system.RegisterExecuteProgramCallback([&](std::size_t program_index_) {
+ m_next_program_id = m_system.GetApplicationProcessProgramID();
m_next_program_index = program_index_;
EmulationSession::GetInstance().HaltEmulation();
});
@@ -407,20 +424,58 @@ void EmulationSession::RunEmulation() {
}
while (true) {
+ std::optional<u64> reload_title;
{
[[maybe_unused]] std::unique_lock lock(m_mutex);
- if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
- [&]() { return !m_is_running; })) {
- // Emulation halted.
- break;
+ if (m_cv.wait_for(lock, std::chrono::milliseconds(800), [&]() {
+ return !m_is_running || m_pending_shader_cache_title.has_value();
+ })) {
+ if (!m_is_running) {
+ break;
+ }
+ reload_title = std::exchange(m_pending_shader_cache_title, std::nullopt);
}
}
+
+ if (reload_title.has_value())
+ ReloadDiskShaderCache(*reload_title);
}
// Reset current applet ID.
m_applet_id = static_cast<int>(Service::AM::AppletId::Application);
}
+void EmulationSession::RequestDiskShaderCacheReload(u64 program_id) {
+ {
+ std::scoped_lock lock(m_mutex);
+ m_pending_shader_cache_title = program_id;
+ }
+ m_cv.notify_one();
+}
+
+void EmulationSession::ReloadDiskShaderCache(u64 program_id) {
+ if (!Settings::values.use_disk_shader_cache.GetValue())
+ return;
+
+ LOG_INFO(Frontend, "Reloading disk shader cache for {:016X}", program_id);
+
+ const bool was_paused = m_is_paused;
+
+ m_system.Pause();
+ m_system.GPU().WaitForIdle();
+ m_system.GPU().ObtainContext();
+
+ LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
+ m_system.Renderer().ReadRasterizer()->LoadDiskResources(program_id, std::stop_token{},
+ LoadDiskCacheProgress);
+ LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
+
+ m_system.GPU().ReleaseContext();
+
+ if (!was_paused)
+ m_system.Run();
+}
+
Common::Android::SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() {
return m_software_keyboard;
}
diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h
index 6b171d5a66..5794660bbd 100644
--- a/src/android/app/src/main/jni/native.h
+++ b/src/android/app/src/main/jni/native.h
@@ -4,6 +4,8 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <optional>
+
#include <android/native_window_jni.h>
#include "common/android/applets/software_keyboard.h"
#include "core/core.h"
@@ -44,6 +46,7 @@ public:
void HaltEmulation();
void RunEmulation();
void ShutdownEmulation();
+ void RequestDiskShaderCacheReload(u64 program_id);
const Core::PerfStatsResults& PerfStats();
int ShadersBuilding();
@@ -65,6 +68,7 @@ private:
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);
static void OnEmulationStopped(Core::SystemResultStatus result);
static void ChangeProgram(std::size_t program_index);
+ void ReloadDiskShaderCache(u64 program_id);
private:
// Window management
@@ -83,6 +87,7 @@ private:
Common::Android::SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
int m_applet_id{1};
+ std::optional<u64> m_pending_shader_cache_title;
// GPU driver parameters
std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;
@@ -93,4 +98,5 @@ private:
// Program index for next boot
std::atomic<s32> m_next_program_index = -1;
+ std::atomic<u64> m_next_program_id = 0;
};
diff --git a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp
index 05cf3975db..6fecd63309 100644
--- a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp
+++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -7,13 +10,12 @@
namespace AudioCore::ADSP::OpusDecoder {
namespace {
-bool IsValidChannelCount(u32 channel_count) {
- return channel_count == 1 || channel_count == 2;
-}
+constexpr u32 OpusStreamCountMax = 255;
bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
- return total_stream_count > 0 && static_cast<s32>(stereo_stream_count) >= 0 &&
- stereo_stream_count <= total_stream_count && IsValidChannelCount(total_stream_count);
+ return total_stream_count > 0 && total_stream_count <= OpusStreamCountMax &&
+ static_cast<s32>(stereo_stream_count) >= 0 &&
+ stereo_stream_count <= total_stream_count;
}
} // namespace
diff --git a/src/audio_core/opus/hardware_opus.cpp b/src/audio_core/opus/hardware_opus.cpp
index 30805f4a36..0037a3e503 100644
--- a/src/audio_core/opus/hardware_opus.cpp
+++ b/src/audio_core/opus/hardware_opus.cpp
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -105,7 +108,7 @@ Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 chan
shared_memory.host_send_data[4] = total_stream_count;
shared_memory.host_send_data[5] = stereo_stream_count;
- ASSERT(channel_count <= MaxChannels);
+ ASSERT(channel_count <= shared_memory.channel_mapping.size());
std::memcpy(shared_memory.channel_mapping.data(), mappings, channel_count * sizeof(u8));
opus_decoder.Send(ADSP::Direction::DSP,
diff --git a/src/core/core.cpp b/src/core/core.cpp
index d110aa5f8b..afcca1f806 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -295,6 +295,10 @@ struct System::Impl {
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath, Service::AM::FrontendAppletParameters& params) {
InitializeKernel(system);
+ if (params.applet_type == Service::AM::AppletType::Application) {
+ current_application_filepath = filepath;
+ }
+
const auto file = GetGameFileFromPath(virtual_filesystem, filepath);
// Create the application process
@@ -330,7 +334,7 @@ struct System::Impl {
LaunchTimestampCache::SaveLaunchTimestamp(params.program_id);
// Make the process created be the application
- kernel.MakeApplicationProcess(process->GetHandle());
+ kernel.SetApplicationProcess(process->GetHandle());
// Set up the rest of the system.
SystemResultStatus init_result{SetupForApplicationProcess(system, emu_window)};
@@ -467,6 +471,7 @@ struct System::Impl {
Core::SpeedLimiter speed_limiter;
ExecuteProgramCallback execute_program_callback;
ExitCallback exit_callback;
+ ApplicationChangedCallback application_changed_callback;
std::optional<Service::Services> services;
std::optional<Core::Debugger> debugger;
@@ -488,6 +493,8 @@ struct System::Impl {
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
std::array<u8, 0x20> build_id{};
+ std::string current_application_filepath;
+
/// Service manager
std::shared_ptr<Service::SM::ServiceManager> service_manager;
/// ContentProviderUnion instance
@@ -727,7 +734,25 @@ const Core::SpeedLimiter& System::SpeedLimiter() const {
}
u64 System::GetApplicationProcessProgramID() const {
- return impl->kernel.ApplicationProcess()->GetProgramId();
+ const auto* const process = impl->kernel.ApplicationProcess();
+ return process != nullptr ? process->GetProgramId() : 0;
+}
+
+u64 System::GetProgramIdForProcessId(u64 process_id) const {
+ auto process = impl->kernel.GetProcessByProcessId(process_id);
+ return process.IsNull() ? 0 : process->GetProgramId();
+}
+
+u64 System::ResolveCallerProgramId(u64 process_id) const {
+ if (const auto program_id = this->GetProgramIdForProcessId(process_id); program_id != 0) {
+ return program_id;
+ }
+
+ const auto fallback = this->GetApplicationProcessProgramID();
+ LOG_WARNING(Core,
+ "Could not resolve caller process_id={}, falling back to application {:016X}",
+ process_id, fallback);
+ return fallback;
}
Loader::ResultStatus System::GetGameName(std::string& out) const {
@@ -910,6 +935,10 @@ void System::ExecuteProgram(std::size_t program_index) {
}
}
+const std::string& System::GetCurrentApplicationFilePath() const {
+ return impl->current_application_filepath;
+}
+
/// @brief Gets a reference to the user channel stack.
/// It is used to transfer data between programs.
std::vector<std::vector<u8>>& System::GetUserChannel() {
@@ -961,6 +990,18 @@ void System::Exit() {
}
}
+void System::RegisterApplicationChangedCallback(ApplicationChangedCallback&& callback) {
+ impl->application_changed_callback = std::move(callback);
+}
+
+void System::NotifyApplicationChanged(u64 program_id) {
+ //LOG_DEBUG(Core, "Running application changed to {:016X}", program_id);
+
+ if (impl->application_changed_callback) {
+ impl->application_changed_callback(program_id);
+ }
+}
+
void System::ApplySettings() {
impl->RefreshTime(*this);
diff --git a/src/core/core.h b/src/core/core.h
index 93f7b057f7..d88ed64151 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -322,6 +322,10 @@ public:
[[nodiscard]] u64 GetApplicationProcessProgramID() const;
+ [[nodiscard]] u64 GetProgramIdForProcessId(u64 process_id) const;
+
+ [[nodiscard]] u64 ResolveCallerProgramId(u64 process_id) const;
+
/// Gets the name of the current game
[[nodiscard]] Loader::ResultStatus GetGameName(std::string& out) const;
@@ -422,6 +426,7 @@ public:
void PushGeneralChannelData(std::vector<u8>&& data);
bool TryPopGeneralChannel(std::vector<u8>& out_data);
[[nodiscard]] Service::Event& GetGeneralChannelEvent();
+ [[nodiscard]] const std::string& GetCurrentApplicationFilePath() const;
/// Type used for the frontend to designate a callback for System to exit the application.
using ExitCallback = std::function<void()>;
@@ -435,6 +440,10 @@ public:
/// Instructs the frontend to exit the application.
void Exit();
+ using ApplicationChangedCallback = std::function<void(u64 program_id)>;
+ void RegisterApplicationChangedCallback(ApplicationChangedCallback&& callback);
+ void NotifyApplicationChanged(u64 program_id);
+
/// Applies any changes to settings to this core instance.
void ApplySettings();
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 2b478d6b99..b47b9437d0 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -29,7 +29,7 @@ static u8 MasterKeyIdForKeyGeneration(u8 key_generation) {
return std::max<u8>(key_generation, 1) - 1;
}
-NCA::NCA(VirtualFile file_, const NCA* base_nca)
+NCA::NCA(VirtualFile file_, const NCA* base_nca, bool allow_missing_base)
: file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
if (file == nullptr) {
status = Loader::ResultStatus::ErrorNullFile;
@@ -110,7 +110,7 @@ NCA::NCA(VirtualFile file_, const NCA* base_nca)
}
}
- if (is_update && base_nca == nullptr) {
+ if (is_update && base_nca == nullptr && !allow_missing_base) {
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
} else {
status = Loader::ResultStatus::Success;
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 8560617f5b..b88fcdc4ee 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -63,7 +66,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) {
// After construction, use GetStatus to determine if the file is valid and ready to be used.
class NCA : public ReadOnlyVfsDirectory {
public:
- explicit NCA(VirtualFile file, const NCA* base_nca = nullptr);
+ explicit NCA(VirtualFile file, const NCA* base_nca = nullptr, bool allow_missing_base = false);
~NCA() override;
Loader::ResultStatus GetStatus() const;
diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp
index 41f5b75b6e..e66ce49d9a 100644
--- a/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp
+++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/logging.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fssystem_indirect_storage.h"
@@ -99,6 +100,18 @@ Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s
R_SUCCEED();
}
+void IndirectStorage::ReportMissingOriginal(s64 offset, s64 size) {
+ if (m_reported_missing_original) {
+ return;
+ }
+ m_reported_missing_original = true;
+
+ LOG_ERROR(Common_Filesystem,
+ "Patch storage requests {:#x} bytes at {:#x} from a base storage that is not "
+ "present; this data will read as garbage",
+ size, offset);
+}
+
size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const {
// Validate pre-conditions.
ASSERT(this->IsInitialized());
diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.h b/src/core/file_sys/fssystem/fssystem_indirect_storage.h
index cc7b5cd718..81f94e2e19 100644
--- a/src/core/file_sys/fssystem/fssystem_indirect_storage.h
+++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.h
@@ -85,6 +85,9 @@ public:
void SetStorage(s32 idx, VirtualFile storage) {
ASSERT(0 <= idx && idx < StorageCount);
m_data_storage[idx] = storage;
+ if (idx == 0) {
+ m_original_missing = storage == nullptr || storage->GetSize() == 0;
+ }
}
template <typename T>
@@ -130,6 +133,9 @@ protected:
template <bool ContinuousCheck, bool RangeCheck, typename F>
Result OperatePerEntry(s64 offset, s64 size, F func);
+ // Launching another game makes the original storage inaccessable.
+ // This is a helper for multi-nca games.
+ void ReportMissingOriginal(s64 offset, s64 size);
private:
struct ContinuousReadingEntry {
@@ -154,6 +160,8 @@ private:
private:
mutable BucketTree m_table;
std::array<VirtualFile, StorageCount> m_data_storage;
+ bool m_original_missing{false};
+ bool m_reported_missing_original{false};
};
template <bool ContinuousCheck, bool RangeCheck, typename F>
@@ -272,6 +280,10 @@ Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) {
if (needs_operate) {
const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset();
+ if (cur_entry.storage_index == 0 && m_original_missing) {
+ this->ReportMissingOriginal(cur_offset, cur_size);
+ }
+
if constexpr (RangeCheck) {
// Get the current data storage's size.
s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize();
diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h
index 43437d0dd7..07aae1642e 100644
--- a/src/core/file_sys/nca_metadata.h
+++ b/src/core/file_sys/nca_metadata.h
@@ -48,11 +48,11 @@ struct ContentRecord {
std::array<u8, 0x10> nca_id;
std::array<u8, 0x6> size;
ContentRecordType type;
- INSERT_PADDING_BYTES(1);
+ u8 id_offset;
};
static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size.");
-constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, {}};
+constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, 0};
struct MetaRecord {
u64_le title_id;
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 9e9db0ed4d..83f5e265dd 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -567,20 +567,45 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
}
static std::optional<NcaID> CheckMapForContentRecord(const ankerl::unordered_dense::map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
- const auto cmnt_iter = map.find(title_id);
+ auto cmnt_iter = map.find(title_id);
+ u8 id_offset = 0;
+
if (cmnt_iter == map.cend()) {
- return std::nullopt;
+ const auto program_index = title_id & AOC_TITLE_ID_MASK;
+ if (program_index == 0) {
+ return std::nullopt;
+ }
+
+ cmnt_iter = map.find(title_id & ~AOC_TITLE_ID_MASK);
+ if (cmnt_iter == map.cend()) {
+ return std::nullopt;
+ }
+
+ id_offset = static_cast<u8>(program_index);
}
const auto& cnmt = cmnt_iter->second;
const auto& content_records = cnmt.GetContentRecords();
const auto iter = std::find_if(content_records.cbegin(), content_records.cend(),
- [type](const ContentRecord& rec) { return rec.type == type; });
- if (iter == content_records.cend()) {
+ [type, id_offset](const ContentRecord& rec) {
+ return rec.type == type && rec.id_offset == id_offset;
+ });
+ if (iter != content_records.cend()) {
+ return std::make_optional(iter->nca_id);
+ }
+
+ if (id_offset != 0) {
+ return std::nullopt;
+ }
+
+ const auto fallback_iter =
+ std::find_if(content_records.cbegin(), content_records.cend(),
+ [type](const ContentRecord& rec) { return rec.type == type; });
+ if (fallback_iter == content_records.cend()) {
return std::nullopt;
}
- return std::make_optional(iter->nca_id);
+ return std::make_optional(fallback_iter->nca_id);
}
std::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index 630bb0b079..0ea323492b 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -12,6 +12,7 @@
#include "common/hex_util.h"
#include "common/logging.h"
#include "core/crypto/key_manager.h"
+#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/partition_filesystem.h"
@@ -65,10 +66,12 @@ u64 NSP::GetProgramTitleID() const {
}
auto program_id = expected_program_id;
- if (program_id == 0) {
- if (!program_status.empty()) {
- program_id = program_status.begin()->first;
- }
+ if (program_id == 0 && !program_status.empty()) {
+ program_id = std::min_element(program_status.cbegin(), program_status.cend(),
+ [](const auto& lhs, const auto& rhs) {
+ return lhs.first < rhs.first;
+ })
+ ->first;
}
program_id = program_id + program_index;
@@ -273,8 +276,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
// If the last 3 hexadecimal digits of the NCA's TitleID is between 0x1 and
// 0x7FF, this is a multi-program update NCA. Otherwise, this is a regular
// update NCA.
- if ((next_nca->GetTitleId() & 0x7FF) != 0 &&
- (next_nca->GetTitleId() & 0x800) == 0) {
+ if ((next_nca->GetTitleId() & AOC_TITLE_ID_MASK) != 0) {
ncas[next_nca->GetTitleId()][{cnmt.GetType(), rec.type}] =
std::move(next_nca);
} else {
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index d25ac37df4..a739e7020c 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -349,9 +349,18 @@ struct KernelCore::Impl {
object_name_global_data.emplace(kernel);
}
- void MakeApplicationProcess(KernelCore& kernel, KProcess* process) {
+ void SetApplicationProcess(KernelCore& kernel, KProcess* process) {
+ if (application_process == process)
+ return;
+
+ KProcess* const previous = application_process;
application_process = process;
- application_process->Open(kernel);
+
+ if (application_process != nullptr)
+ application_process->Open(kernel);
+
+ if (previous != nullptr)
+ previous->Close(kernel);
}
/// Sets the host thread ID for the caller.
@@ -879,8 +888,8 @@ void KernelCore::RemoveProcess(KProcess* process) {
}
}
-void KernelCore::MakeApplicationProcess(KProcess* process) {
- impl->MakeApplicationProcess(*this, process);
+void KernelCore::SetApplicationProcess(KProcess* process) {
+ impl->SetApplicationProcess(*this, process);
}
KProcess* KernelCore::ApplicationProcess() {
@@ -891,6 +900,14 @@ const KProcess* KernelCore::ApplicationProcess() const {
return impl->application_process;
}
+KScopedAutoObject<KProcess> KernelCore::GetProcessByProcessId(u64 process_id) {
+ std::scoped_lock lk{impl->process_list_lock};
+ for (auto* const process : impl->process_list)
+ if (process != nullptr && process->GetProcessId() == process_id)
+ return {*this, process};
+ return {*this, nullptr};
+}
+
std::list<KScopedAutoObject<KProcess>> KernelCore::GetProcessList() {
std::list<KScopedAutoObject<KProcess>> processes;
std::scoped_lock lk{impl->process_list_lock};
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 3b754e9397..04790c8b38 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -124,8 +124,8 @@ public:
void AppendNewProcess(KProcess* process);
void RemoveProcess(KProcess* process);
- /// Makes the given process the new application process.
- void MakeApplicationProcess(KProcess* process);
+ /// Makes the given process the current application process.
+ void SetApplicationProcess(KProcess* process);
/// Retrieves a pointer to the application process.
KProcess* ApplicationProcess();
@@ -133,6 +133,9 @@ public:
/// Retrieves a const pointer to the application process.
const KProcess* ApplicationProcess() const;
+ /// Retrieves the process with the given process ID, or a null object.
+ KScopedAutoObject<KProcess> GetProcessByProcessId(u64 process_id);
+
/// Retrieves the list of processes.
std::list<KScopedAutoObject<KProcess>> GetProcessList();
diff --git a/src/core/hle/service/am/am_results.h b/src/core/hle/service/am/am_results.h
index 44846aa2e5..d1b86c9546 100644
--- a/src/core/hle/service/am/am_results.h
+++ b/src/core/hle/service/am/am_results.h
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -10,6 +13,7 @@ namespace Service::AM {
constexpr Result ResultNoDataInChannel{ErrorModule::AM, 2};
constexpr Result ResultNoMessages{ErrorModule::AM, 3};
constexpr Result ResultLibraryAppletTerminated{ErrorModule::AM, 22};
+constexpr Result ResultApplicationRecordNotFound{ErrorModule::AM, 37};
constexpr Result ResultInvalidOffset{ErrorModule::AM, 503};
constexpr Result ResultInvalidStorageType{ErrorModule::AM, 511};
constexpr Result ResultFatalSectionCountImbalance{ErrorModule::AM, 512};
diff --git a/src/core/hle/service/am/am_types.h b/src/core/hle/service/am/am_types.h
index beb52b74dd..f88f91ae47 100644
--- a/src/core/hle/service/am/am_types.h
+++ b/src/core/hle/service/am/am_types.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -201,6 +201,14 @@ enum class ProgramSpecifyKind : u32 {
RestartProgram = 2,
};
+// Maufeat: Use enums for zindex instead of using random zindex numbers
+enum AppletZIndex : s32 {
+ Background = 0,
+ Foreground = 1,
+ ForegroundVisible = 2,
+ Overlay = 3,
+};
+
struct CommonArguments {
CommonArgumentVersion arguments_version;
CommonArgumentSize size;
diff --git a/src/core/hle/service/am/applet.cpp b/src/core/hle/service/am/applet.cpp
index efedcc5692..58d038348f 100644
--- a/src/core/hle/service/am/applet.cpp
+++ b/src/core/hle/service/am/applet.cpp
@@ -56,22 +56,22 @@ void Applet::UpdateSuspensionStateLocked(bool force_message) {
}
}
-void Applet::SetInteractibleLocked(bool interactible) {
- if (is_interactible == interactible) {
+void Applet::SetInteractibleLocked(bool pad_interactible, bool touch_interactible) {
+ if (is_pad_interactible == pad_interactible && is_touch_interactible == touch_interactible) {
return;
}
- is_interactible = interactible;
+ is_pad_interactible = pad_interactible;
+ is_touch_interactible = touch_interactible;
const bool exit_requested = lifecycle_manager.GetExitRequested();
- const bool input_enabled = interactible && !exit_requested;
+ const bool pad_enabled = pad_interactible && !exit_requested;
+ const bool touch_enabled = touch_interactible && !exit_requested;
- if (applet_id == AppletId::OverlayDisplay || applet_id == AppletId::Application) {
- LOG_DEBUG(Service_AM, "called, applet={} interactible={} exit_requested={} input_enabled={} overlay_in_foreground={}",
- static_cast<u32>(applet_id), interactible, exit_requested, input_enabled, overlay_in_foreground);
- }
+ LOG_DEBUG(Service_AM, "applet={} pad={} touch={} exit_requested={}",
+ static_cast<u32>(applet_id), pad_enabled, touch_enabled, exit_requested);
- hid_registration.EnableAppletToGetInput(input_enabled);
+ hid_registration.EnableAppletToGetInput(pad_enabled, touch_enabled);
}
void Applet::OnProcessTerminatedLocked() {
diff --git a/src/core/hle/service/am/applet.h b/src/core/hle/service/am/applet.h
index 9a3d7c7c7b..73303b2e66 100644
--- a/src/core/hle/service/am/applet.h
+++ b/src/core/hle/service/am/applet.h
@@ -125,9 +125,11 @@ struct Applet {
bool album_image_taken_notification_enabled{};
bool record_volume_muted{};
bool is_activity_runnable{};
- bool is_interactible{true};
+ bool is_pad_interactible{true};
+ bool is_touch_interactible{true};
bool window_visible{true};
- bool overlay_in_foreground{false};
+ bool overlay_watching_short_home_button{false};
+ bool overlay_handling_touch_input{false};
// Events
Event overlay_event;
@@ -148,7 +150,7 @@ struct Applet {
// Process state management
void UpdateSuspensionStateLocked(bool force_message);
- void SetInteractibleLocked(bool interactible);
+ void SetInteractibleLocked(bool pad_interactible, bool touch_interactible);
void OnProcessTerminatedLocked();
};
diff --git a/src/core/hle/service/am/display_layer_manager.cpp b/src/core/hle/service/am/display_layer_manager.cpp
index 6789629310..4f26794831 100644
--- a/src/core/hle/service/am/display_layer_manager.cpp
+++ b/src/core/hle/service/am/display_layer_manager.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -6,6 +6,7 @@
#include "core/core.h"
#include "core/hle/service/am/display_layer_manager.h"
+#include "core/hle/service/nvnflinger/hwc_layer.h"
#include "core/hle/service/sm/sm.h"
#include "core/hle/service/vi/application_display_service.h"
#include "core/hle/service/vi/container.h"
@@ -33,6 +34,7 @@ void DisplayLayerManager::Initialize(Core::System& system, Kernel::KProcess* pro
m_system_shared_buffer_id = 0;
m_system_shared_layer_id = 0;
m_applet_id = applet_id;
+ m_library_applet_mode = mode;
m_buffer_sharing_enabled = false;
m_blending_enabled = mode == LibraryAppletMode::PartialForeground ||
mode == LibraryAppletMode::PartialForegroundIndirectDisplay;
@@ -72,14 +74,16 @@ Result DisplayLayerManager::CreateManagedDisplayLayer(u64* out_layer_id) {
out_layer_id, 0, display_id, Service::AppletResourceUserId{m_process->GetProcessId()}));
m_manager_display_service->SetLayerVisibility(m_visible, *out_layer_id);
+ (void)m_display_service->GetContainer()->SetLayerStackMask(*out_layer_id,
+ this->GetLayerStackMask());
if (m_applet_id != AppletId::Application) {
(void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id);
if (m_applet_id == AppletId::OverlayDisplay) {
- (void)m_manager_display_service->SetLayerZIndex(-1, *out_layer_id);
+ (void)m_manager_display_service->SetLayerZIndex(Overlay, *out_layer_id);
(void)m_display_service->GetContainer()->SetLayerIsOverlay(*out_layer_id, true);
} else {
- (void)m_manager_display_service->SetLayerZIndex(1, *out_layer_id);
+ (void)m_manager_display_service->SetLayerZIndex(Foreground, *out_layer_id);
}
}
@@ -122,10 +126,12 @@ Result DisplayLayerManager::IsSystemBufferSharingEnabled() {
// Ensure the overlay layer is visible
m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id);
+ (void)m_display_service->GetContainer()->SetLayerStackMask(m_system_shared_layer_id,
+ this->GetLayerStackMask());
m_manager_display_service->SetLayerBlending(m_blending_enabled, m_system_shared_layer_id);
- s32 initial_z = 1;
+ s32 initial_z = Foreground;
if (m_applet_id == AppletId::OverlayDisplay) {
- initial_z = -1;
+ initial_z = Overlay;
(void)m_display_service->GetContainer()->SetLayerIsOverlay(m_system_shared_layer_id, true);
}
m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id);
@@ -142,6 +148,36 @@ Result DisplayLayerManager::GetSystemSharedLayerHandle(u64* out_system_shared_bu
R_SUCCEED();
}
+u32 DisplayLayerManager::GetLayerStackMask() const {
+ using Nvnflinger::LayerStackBit;
+ using Nvnflinger::LayerStackId;
+
+ constexpr u32 Displayed = LayerStackBit(LayerStackId::Default);
+ constexpr u32 Screenshot = LayerStackBit(LayerStackId::Screenshot);
+ constexpr u32 Recording = LayerStackBit(LayerStackId::Recording);
+ constexpr u32 LastFrame = LayerStackBit(LayerStackId::LastFrame);
+ constexpr u32 Debug = LayerStackBit(LayerStackId::ApplicationForDebug);
+
+ switch (m_applet_id) {
+ case AppletId::Application:
+ return Displayed | Screenshot | Recording | LastFrame | Debug;
+ case AppletId::OverlayDisplay:
+ return Displayed;
+ case AppletId::QLaunch:
+ return Displayed;
+ default:
+ break;
+ }
+
+ switch (m_library_applet_mode) {
+ case LibraryAppletMode::AllForeground:
+ case LibraryAppletMode::AllForegroundInitiallyHidden:
+ return Displayed | Screenshot | LastFrame;
+ default:
+ return Displayed | Screenshot;
+ }
+}
+
void DisplayLayerManager::SetWindowVisibility(bool visible) {
if (m_visible == visible) {
return;
@@ -185,10 +221,17 @@ void DisplayLayerManager::SetOverlayZIndex(s32 z_index) {
}
Result DisplayLayerManager::WriteAppletCaptureBuffer(bool* out_was_written,
- s32* out_fbshare_layer_index) {
+ s32* out_fbshare_layer_index,
+ VI::CaptureKind kind) {
R_UNLESS(m_buffer_sharing_enabled, VI::ResultPermissionDenied);
R_RETURN(m_display_service->GetContainer()->GetSharedBufferManager()->WriteAppletCaptureBuffer(
- out_was_written, out_fbshare_layer_index));
+ out_was_written, out_fbshare_layer_index, kind));
+}
+
+Result DisplayLayerManager::ClearAppletCaptureBuffer(s32 fbshare_layer_index, u32 color) {
+ R_UNLESS(m_buffer_sharing_enabled, VI::ResultPermissionDenied);
+ R_RETURN(m_display_service->GetContainer()->GetSharedBufferManager()->ClearAppletCaptureBuffer(
+ fbshare_layer_index, color));
}
} // namespace Service::AM
diff --git a/src/core/hle/service/am/display_layer_manager.h b/src/core/hle/service/am/display_layer_manager.h
index 423eb432fa..e9f48476d2 100644
--- a/src/core/hle/service/am/display_layer_manager.h
+++ b/src/core/hle/service/am/display_layer_manager.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -23,6 +23,7 @@ class KProcess;
namespace Service::VI {
class IApplicationDisplayService;
class IManagerDisplayService;
+enum class CaptureKind : u32;
} // namespace Service::VI
namespace Service::AM {
@@ -48,9 +49,13 @@ public:
void SetOverlayZIndex(s32 z_index);
- Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_fbshare_layer_index);
+ Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_fbshare_layer_index,
+ VI::CaptureKind kind);
+ Result ClearAppletCaptureBuffer(s32 fbshare_layer_index, u32 color);
private:
+ u32 GetLayerStackMask() const;
+
Kernel::KProcess* m_process{};
std::shared_ptr<VI::IApplicationDisplayService> m_display_service{};
std::shared_ptr<VI::IManagerDisplayService> m_manager_display_service{};
@@ -59,6 +64,7 @@ private:
u64 m_system_shared_buffer_id{};
u64 m_system_shared_layer_id{};
AppletId m_applet_id{};
+ LibraryAppletMode m_library_applet_mode{};
bool m_buffer_sharing_enabled{};
bool m_blending_enabled{};
bool m_visible{true};
diff --git a/src/core/hle/service/am/hid_registration.cpp b/src/core/hle/service/am/hid_registration.cpp
index 2613fc29a4..8ce9ae748b 100644
--- a/src/core/hle/service/am/hid_registration.cpp
+++ b/src/core/hle/service/am/hid_registration.cpp
@@ -36,12 +36,17 @@ HidRegistration::~HidRegistration() {
}
}
-void HidRegistration::EnableAppletToGetInput(bool enable) {
- if (m_process.IsInitialized()) {
- m_hid_server->GetResourceManager()->SetAruidValidForVibration(m_process.GetProcessId(),
- enable);
- m_hid_server->GetResourceManager()->EnableInput(m_process.GetProcessId(), enable);
- }
+void HidRegistration::EnableAppletToGetInput(bool enable_pad, bool enable_touch) {
+ if (!m_process.IsInitialized())
+ return;
+
+ const auto resource_manager = m_hid_server->GetResourceManager();
+ const u64 aruid = m_process.GetProcessId();
+
+ resource_manager->EnablePadInput(aruid, enable_pad);
+ resource_manager->EnableTouchScreen(aruid, enable_touch);
+
+ resource_manager->SetAruidValidForVibration(aruid, enable_pad);
}
} // namespace Service::AM
diff --git a/src/core/hle/service/am/hid_registration.h b/src/core/hle/service/am/hid_registration.h
index 560266144f..59184b0bee 100644
--- a/src/core/hle/service/am/hid_registration.h
+++ b/src/core/hle/service/am/hid_registration.h
@@ -28,7 +28,7 @@ public:
~HidRegistration();
void RegisterCurrentProcess();
- void EnableAppletToGetInput(bool enable);
+ void EnableAppletToGetInput(bool enable_pad, bool enable_touch);
private:
Process& m_process;
diff --git a/src/core/hle/service/am/service/applet_common_functions.cpp b/src/core/hle/service/am/service/applet_common_functions.cpp
index ee536221d9..5ffec23b3e 100644
--- a/src/core/hle/service/am/service/applet_common_functions.cpp
+++ b/src/core/hle/service/am/service/applet_common_functions.cpp
@@ -4,6 +4,8 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "core/core.h"
+#include "core/file_sys/common_funcs.h"
#include "core/hle/service/am/applet.h"
#include "core/hle/service/am/service/applet_common_functions.h"
#include "core/hle/service/cmif_serialization.h"
@@ -78,7 +80,7 @@ Result IAppletCommonFunctions::SetCpuBoostRequestPriority(s32 priority) {
Result IAppletCommonFunctions::GetCurrentApplicationId(Out<u64> out_application_id) {
LOG_WARNING(Service_AM, "(STUBBED) called");
- *out_application_id = system.GetApplicationProcessProgramID() & ~0xFFFULL;
+ *out_application_id = FileSys::GetBaseTitleID(system.GetApplicationProcessProgramID());
R_SUCCEED();
}
diff --git a/src/core/hle/service/am/service/application_functions.cpp b/src/core/hle/service/am/service/application_functions.cpp
index 03d88aeeb3..215d204098 100644
--- a/src/core/hle/service/am/service/application_functions.cpp
+++ b/src/core/hle/service/am/service/application_functions.cpp
@@ -6,8 +6,10 @@
#include <openssl/evp.h>
+#include "common/hex_util.h"
#include "common/settings.h"
#include "common/uuid.h"
+#include "core/file_sys/common_funcs.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
@@ -28,6 +30,27 @@
namespace Service::AM {
+namespace {
+
+FileSys::PatchManager::Metadata GetApplicationMetadata(Core::System& system, u64 program_id) {
+ auto metadata = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, program_id);
+ if (metadata.first != nullptr) {
+ return metadata;
+ }
+
+ const auto application_id = FileSys::GetBaseTitleID(program_id);
+ if (application_id == program_id) {
+ return metadata;
+ }
+
+ LOG_DEBUG(Service_AM,
+ "no metadata for program_id={:016X}, falling back to application_id={:016X}",
+ program_id, application_id);
+ return FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, application_id);
+}
+
+} // Anonymous namespace
+
IApplicationFunctions::IApplicationFunctions(Core::System& system_, std::shared_ptr<Applet> applet)
: ServiceFramework{system_, "IApplicationFunctions"}, m_applet{std::move(applet)} {
// clang-format off
@@ -156,7 +179,7 @@ Result IApplicationFunctions::GetDesiredLanguage(Out<u64> out_language_code) {
// Default to 0 (all languages supported)
u32 supported_languages = 0;
- const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
+ const auto res = GetApplicationMetadata(system, m_applet->program_id);
if (res.first != nullptr) {
supported_languages = res.first->GetSupportedLanguages();
}
@@ -194,7 +217,7 @@ Result IApplicationFunctions::SetTerminateResult(Result terminate_result) {
Result IApplicationFunctions::GetDisplayVersion(Out<DisplayVersion> out_display_version) {
LOG_DEBUG(Service_AM, "called");
- const auto res = FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, m_applet->program_id);
+ const auto res = GetApplicationMetadata(system, m_applet->program_id);
if (res.first != nullptr) {
const auto& version = res.first->GetVersionString();
std::memcpy(out_display_version->string.data(), version.data(),
@@ -431,15 +454,21 @@ Result IApplicationFunctions::ExecuteProgram(ProgramSpecifyKind kind, u64 value)
// https://switchbrew.org/wiki/Applet_Manager_services#CreateApplicationAndRequestToStart
Result IApplicationFunctions::CreateApplicationAndRequestToStart(u64 application_id) {
- LOG_INFO(Service_AM, "called, application_id={:016X}", application_id);
+ LOG_INFO(Service_AM, "called, application_id={:016X} current_program_id={:016X}", application_id, m_applet->program_id);
- // If application_id is 0, relaunch the current application
- const u64 target_application_id =
- (application_id == 0) ? m_applet->program_id : application_id;
+ if (application_id == 0 ||
+ FileSys::GetBaseTitleID(application_id) == FileSys::GetBaseTitleID(m_applet->program_id)) {
+ const auto program_index = application_id == 0
+ ? 0
+ : application_id - FileSys::GetBaseTitleID(application_id);
- system.GetUserChannel() = m_applet->user_channel_launch_parameter;
- system.ExecuteProgram(target_application_id);
- R_SUCCEED();
+ system.GetUserChannel() = m_applet->user_channel_launch_parameter;
+ system.ExecuteProgram(program_index);
+ R_SUCCEED();
+ }
+
+ LOG_ERROR(Service_AM, "Launching a different application ({:016X}) is not implemented!", application_id);
+ R_THROW(ResultUnknown);
}
Result IApplicationFunctions::ClearUserChannel() {
diff --git a/src/core/hle/service/am/service/display_controller.cpp b/src/core/hle/service/am/service/display_controller.cpp
index a02f012a92..8f87327b17 100644
--- a/src/core/hle/service/am/service/display_controller.cpp
+++ b/src/core/hle/service/am/service/display_controller.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -8,6 +8,7 @@
#include "core/hle/service/am/applet.h"
#include "core/hle/service/am/service/display_controller.h"
#include "core/hle/service/cmif_serialization.h"
+#include "core/hle/service/vi/shared_buffer_manager.h"
namespace Service::AM {
@@ -71,16 +72,16 @@ Result IDisplayController::TakeScreenShotOfOwnLayer(bool unknown0, s32 fbshare_l
}
Result IDisplayController::ClearCaptureBuffer(bool unknown0, s32 fbshare_layer_index, u32 color) {
- LOG_WARNING(Service_AM, "(STUBBED) called, unknown0={} fbshare_layer_index={} color={:#x}",
- unknown0, fbshare_layer_index, color);
- R_SUCCEED();
+ LOG_DEBUG(Service_AM, "called, unknown0={} fbshare_layer_index={} color={:#x}", unknown0,
+ fbshare_layer_index, color);
+ R_RETURN(applet->display_layer_manager.ClearAppletCaptureBuffer(fbshare_layer_index, color));
}
Result IDisplayController::AcquireLastForegroundCaptureSharedBuffer(
Out<bool> out_was_written, Out<s32> out_fbshare_layer_index) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
- R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(out_was_written,
- out_fbshare_layer_index));
+ LOG_DEBUG(Service_AM, "called");
+ R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(
+ out_was_written, out_fbshare_layer_index, VI::CaptureKind::LastForeground));
}
Result IDisplayController::ReleaseLastForegroundCaptureSharedBuffer() {
@@ -90,9 +91,9 @@ Result IDisplayController::ReleaseLastForegroundCaptureSharedBuffer() {
Result IDisplayController::AcquireCallerAppletCaptureSharedBuffer(
Out<bool> out_was_written, Out<s32> out_fbshare_layer_index) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
- R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(out_was_written,
- out_fbshare_layer_index));
+ LOG_DEBUG(Service_AM, "called");
+ R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(
+ out_was_written, out_fbshare_layer_index, VI::CaptureKind::CallerApplet));
}
Result IDisplayController::ReleaseCallerAppletCaptureSharedBuffer() {
@@ -102,9 +103,9 @@ Result IDisplayController::ReleaseCallerAppletCaptureSharedBuffer() {
Result IDisplayController::AcquireLastApplicationCaptureSharedBuffer(
Out<bool> out_was_written, Out<s32> out_fbshare_layer_index) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
- R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(out_was_written,
- out_fbshare_layer_index));
+ LOG_DEBUG(Service_AM, "called");
+ R_RETURN(applet->display_layer_manager.WriteAppletCaptureBuffer(
+ out_was_written, out_fbshare_layer_index, VI::CaptureKind::LastApplication));
}
Result IDisplayController::ReleaseLastApplicationCaptureSharedBuffer() {
diff --git a/src/core/hle/service/am/service/overlay_functions.cpp b/src/core/hle/service/am/service/overlay_functions.cpp
index 41f2e5c008..8150b5dd07 100644
--- a/src/core/hle/service/am/service/overlay_functions.cpp
+++ b/src/core/hle/service/am/service/overlay_functions.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/hle/service/am/applet.h"
@@ -22,7 +22,7 @@ namespace Service::AM {
{10, nullptr, "StartShutdownSequenceForOverlay"},
{11, nullptr, "StartRebootSequenceForOverlay"},
{20, D<&IOverlayFunctions::SetHandlingHomeButtonShortPressedEnabled>, "SetHandlingHomeButtonShortPressedEnabled"},
- {21, nullptr, "SetHandlingTouchScreenInputEnabled"},
+ {21, D<&IOverlayFunctions::SetHandlingTouchScreenInputEnabled>, "SetHandlingTouchScreenInputEnabled"},
{30, nullptr, "SetHealthWarningShowingState"},
{31, D<&IOverlayFunctions::IsHealthWarningRequired>, "IsHealthWarningRequired"},
{40, nullptr, "GetApplicationNintendoLogo"},
@@ -43,10 +43,12 @@ namespace Service::AM {
Result IOverlayFunctions::BeginToWatchShortHomeButtonMessage() {
LOG_DEBUG(Service_AM, "called");
- m_applet->overlay_in_foreground = true;
- m_applet->home_button_short_pressed_blocked = false;
+ {
+ std::scoped_lock lk{m_applet->lock};
+ m_applet->overlay_watching_short_home_button = true;
+ }
- if (auto *window_system = system.GetAppletManager().GetWindowSystem()) {
+ if (auto* window_system = system.GetAppletManager().GetWindowSystem()) {
window_system->RequestUpdate();
}
@@ -56,16 +58,26 @@ namespace Service::AM {
Result IOverlayFunctions::EndToWatchShortHomeButtonMessage() {
LOG_DEBUG(Service_AM, "called");
- m_applet->overlay_in_foreground = false;
- m_applet->home_button_short_pressed_blocked = false;
+ {
+ std::scoped_lock lk{m_applet->lock};
+ m_applet->overlay_watching_short_home_button = false;
+ }
- if (auto *window_system = system.GetAppletManager().GetWindowSystem()) {
+ if (auto* window_system = system.GetAppletManager().GetWindowSystem()) {
window_system->RequestUpdate();
}
R_SUCCEED();
}
+ Result IOverlayFunctions::SetHandlingTouchScreenInputEnabled(bool enabled) {
+ LOG_DEBUG(Service_AM, "called, enabled={}", enabled);
+
+ std::scoped_lock lk{m_applet->lock};
+ m_applet->overlay_handling_touch_input = enabled;
+ R_SUCCEED();
+ }
+
Result IOverlayFunctions::GetApplicationIdForLogo(Out<u64> out_application_id) {
LOG_DEBUG(Service_AM, "called");
diff --git a/src/core/hle/service/am/service/overlay_functions.h b/src/core/hle/service/am/service/overlay_functions.h
index 870f5eae9c..eefd8d862b 100644
--- a/src/core/hle/service/am/service/overlay_functions.h
+++ b/src/core/hle/service/am/service/overlay_functions.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -20,6 +20,7 @@ namespace Service::AM {
Result SetAutoSleepTimeAndDimmingTimeEnabled(bool enabled);
Result IsHealthWarningRequired(Out<bool> is_required);
Result SetHandlingHomeButtonShortPressedEnabled(bool enabled);
+ Result SetHandlingTouchScreenInputEnabled(bool enabled);
Result Unknown70();
private:
diff --git a/src/core/hle/service/am/window_system.cpp b/src/core/hle/service/am/window_system.cpp
index bbaea8ad12..3858791430 100644
--- a/src/core/hle/service/am/window_system.cpp
+++ b/src/core/hle/service/am/window_system.cpp
@@ -4,7 +4,11 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <utility>
+
+#include "common/settings.h"
#include "core/core.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/service/am/am_results.h"
#include "core/hle/service/am/applet.h"
#include "core/hle/service/am/applet_manager.h"
@@ -32,46 +36,82 @@ void WindowSystem::RequestUpdate() {
}
void WindowSystem::Update() {
- std::scoped_lock lk{m_lock};
+ {
+ std::scoped_lock lk{m_lock};
+
+ LOG_DEBUG(Service_AM, "called, home_menu={} application={} overlay={}",
+ m_home_menu != nullptr, m_application != nullptr, m_overlay_display != nullptr);
+
+ // Loop through all applets and remove terminated applets.
+ this->PruneTerminatedAppletsLocked();
+
+ // If the home menu is being locked into the foreground, handle that.
+ if (!this->LockHomeMenuIntoForegroundLocked()) {
+ const bool overlay_takes_input = this->DoesOverlayTakeInputLocked();
+
+ this->UpdateAppletStateLocked(m_overlay_display, true, overlay_takes_input);
+ this->UpdateAppletStateLocked(m_home_menu, m_foreground_requested_applet == m_home_menu, overlay_takes_input);
+ this->UpdateAppletStateLocked(m_application, m_foreground_requested_applet == m_application, overlay_takes_input);
+ }
+ }
+
+ this->NotifyApplicationChangedIfNeeded();
+}
- LOG_DEBUG(Service_AM, "called, home_menu={} application={} overlay={}",
- m_home_menu != nullptr, m_application != nullptr, m_overlay_display != nullptr);
+void WindowSystem::TrackApplet(std::shared_ptr<Applet> applet, bool is_application) {
+ {
+ std::scoped_lock lk{m_lock};
- // Loop through all applets and remove terminated applets.
- this->PruneTerminatedAppletsLocked();
+ if (applet->applet_id == AppletId::QLaunch) {
+ ASSERT(m_home_menu == nullptr);
+ m_home_menu = applet.get();
+ } else if (applet->applet_id == AppletId::OverlayDisplay) {
+ m_overlay_display = applet.get();
+ } else if (is_application) {
+ ASSERT(m_application == nullptr);
+ m_application = applet.get();
+ }
- // If the home menu is being locked into the foreground, handle that.
- if (this->LockHomeMenuIntoForegroundLocked()) {
+ this->UpdateCurrentApplicationLocked();
+
+ m_event_observer->TrackAppletProcess(*applet);
+ m_applets.emplace(applet->aruid.pid, std::move(applet));
+ }
+
+ this->NotifyApplicationChangedIfNeeded();
+}
+
+void WindowSystem::UpdateCurrentApplicationLocked() {
+ const Applet* const candidate = m_application != nullptr ? m_application : m_home_menu;
+ if (candidate == nullptr) {
return;
}
- bool overlay_blocks_input = false;
- if (m_overlay_display) {
- std::scoped_lock lk_overlay{m_overlay_display->lock};
- overlay_blocks_input = m_overlay_display->overlay_in_foreground;
+ auto* const process = candidate->process->GetHandle();
+ if (process == nullptr || process == m_system.Kernel().ApplicationProcess()) {
+ return;
}
- // Recursively update each applet root.
- this->UpdateAppletStateLocked(m_home_menu, m_foreground_requested_applet == m_home_menu, overlay_blocks_input);
- this->UpdateAppletStateLocked(m_application, m_foreground_requested_applet == m_application, overlay_blocks_input);
- this->UpdateAppletStateLocked(m_overlay_display, true, false); // overlay is always updated, never blocked
+ LOG_INFO(Service_AM, "Current application is now {:016X}", candidate->program_id);
+
+ m_system.Kernel().SetApplicationProcess(process);
+ Settings::SetCurrentProgramID(candidate->program_id);
+
+ m_pending_application_notification = candidate->program_id;
}
-void WindowSystem::TrackApplet(std::shared_ptr<Applet> applet, bool is_application) {
- std::scoped_lock lk{m_lock};
+void WindowSystem::NotifyApplicationChangedIfNeeded() {
+ std::optional<u64> program_id;
+ {
+ std::scoped_lock lk{m_lock};
+ program_id = std::exchange(m_pending_application_notification, std::nullopt);
+ }
- if (applet->applet_id == AppletId::QLaunch) {
- ASSERT(m_home_menu == nullptr);
- m_home_menu = applet.get();
- } else if (applet->applet_id == AppletId::OverlayDisplay) {
- m_overlay_display = applet.get();
- } else if (is_application) {
- ASSERT(m_application == nullptr);
- m_application = applet.get();
+ if (!program_id.has_value()) {
+ return;
}
- m_event_observer->TrackAppletProcess(*applet);
- m_applets.emplace(applet->aruid.pid, std::move(applet));
+ m_system.NotifyApplicationChanged(*program_id);
}
std::shared_ptr<Applet> WindowSystem::GetByAppletResourceUserId(u64 aruid) {
@@ -169,18 +209,40 @@ void WindowSystem::OnExitRequested() {
}
void WindowSystem::SendButtonAppletMessageLocked(AppletMessage message) {
- if (m_home_menu) {
- std::scoped_lock lk_home{m_home_menu->lock};
- m_home_menu->lifecycle_manager.PushUnorderedMessage(m_system.Kernel(), message);
- }
- if (m_overlay_display) {
- std::scoped_lock lk_overlay{m_overlay_display->lock};
- m_overlay_display->lifecycle_manager.PushUnorderedMessage(m_system.Kernel(), message);
- }
- if (m_application) {
- std::scoped_lock lk_application{m_application->lock};
- m_application->lifecycle_manager.PushUnorderedMessage(m_system.Kernel(), message);
- }
+ const auto is_blocked = [message](const Applet& applet) {
+ if (message == AppletMessage::DetectShortPressingHomeButton &&
+ applet.applet_id == AppletId::OverlayDisplay &&
+ !applet.overlay_watching_short_home_button) {
+ return true;
+ }
+
+ switch (message) {
+ case AppletMessage::DetectShortPressingHomeButton:
+ return applet.home_button_short_pressed_blocked;
+ case AppletMessage::DetectLongPressingHomeButton:
+ return applet.home_button_long_pressed_blocked;
+ default:
+ return false;
+ }
+ };
+
+ const auto send_to = [&](Applet* applet) {
+ if (!applet) {
+ return;
+ }
+ std::scoped_lock lk{applet->lock};
+ if (is_blocked(*applet)) {
+ LOG_DEBUG(Service_AM, "Applet {} is blocking message {}",
+ static_cast<u32>(applet->applet_id), static_cast<u32>(message));
+ return;
+ }
+ applet->lifecycle_manager.PushUnorderedMessage(m_system.Kernel(), message);
+ };
+
+ send_to(m_home_menu);
+ send_to(m_overlay_display);
+ send_to(m_application);
+
if (m_event_observer) {
m_event_observer->RequestUpdate();
}
@@ -192,19 +254,9 @@ void WindowSystem::OnSystemButtonPress(SystemButtonType type) {
case SystemButtonType::HomeButtonShortPressing:
SendButtonAppletMessageLocked(AppletMessage::DetectShortPressingHomeButton);
break;
- case SystemButtonType::HomeButtonLongPressing: {
- // Toggle overlay foreground visibility on long home press
- if (m_overlay_display) {
- std::scoped_lock lk_overlay{m_overlay_display->lock};
- m_overlay_display->overlay_in_foreground = !m_overlay_display->overlay_in_foreground;
- LOG_INFO(Service_AM, "Overlay long-press toggle: overlay_in_foreground={} window_visible={}", m_overlay_display->overlay_in_foreground, m_overlay_display->window_visible);
- }
+ case SystemButtonType::HomeButtonLongPressing:
SendButtonAppletMessageLocked(AppletMessage::DetectLongPressingHomeButton);
- // Force a state update after toggling overlay
- if (m_event_observer) {
- m_event_observer->RequestUpdate();
- }
- break; }
+ break;
case SystemButtonType::CaptureButtonShortPressing:
SendButtonAppletMessageLocked(AppletMessage::DetectShortPressingCaptureButton);
break;
@@ -317,6 +369,8 @@ void WindowSystem::PruneTerminatedAppletsLocked() {
m_overlay_display = nullptr;
}
+ this->UpdateCurrentApplicationLocked();
+
// Finalize applet.
applet->OnProcessTerminatedLocked();
@@ -389,7 +443,20 @@ void WindowSystem::TerminateChildAppletsLocked(Applet* applet) {
applet->lock.lock();
}
-void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_blocking) {
+bool WindowSystem::IsOverlayOpenLocked(const Applet& overlay) const {
+ return overlay.window_visible && overlay.overlay_watching_short_home_button;
+}
+
+bool WindowSystem::DoesOverlayTakeInputLocked() const {
+ if (m_overlay_display == nullptr) {
+ return false;
+ }
+
+ std::scoped_lock lk{m_overlay_display->lock};
+ return this->IsOverlayOpenLocked(*m_overlay_display);
+}
+
+void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_takes_input) {
// With no applet, we don't have anything to do.
if (!applet) {
return;
@@ -420,24 +487,18 @@ void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, b
return false;
}();
+ const bool is_overlay = applet->applet_id == AppletId::OverlayDisplay;
+
// Update visibility state.
- // Overlay applets should always be visible when window_visible is true, regardless of foreground state
- const bool should_be_visible = (applet->applet_id == AppletId::OverlayDisplay)
- ? applet->window_visible
- : (is_foreground && applet->window_visible);
+ const bool should_be_visible =
+ is_overlay ? applet->window_visible : (is_foreground && applet->window_visible);
applet->display_layer_manager.SetWindowVisibility(should_be_visible);
+ const bool needs_hid_input =
+ is_overlay ? this->IsOverlayOpenLocked(*applet)
+ : (is_foreground && applet->window_visible && !overlay_takes_input);
- const bool should_be_interactible = (applet->applet_id == AppletId::OverlayDisplay)
- ? applet->overlay_in_foreground
- : (is_foreground && applet->window_visible && !overlay_blocking);
-
- if (applet->applet_id == AppletId::OverlayDisplay || applet->applet_id == AppletId::Application) {
- LOG_DEBUG(Service_AM, "UpdateAppletStateLocked: applet={} overlay_in_foreground={} is_foreground={} window_visible={} overlay_blocking={} should_be_interactible={}",
- static_cast<u32>(applet->applet_id), applet->overlay_in_foreground, is_foreground, applet->window_visible, overlay_blocking, should_be_interactible);
- }
-
- applet->SetInteractibleLocked(should_be_interactible);
+ applet->SetInteractibleLocked(needs_hid_input, needs_hid_input);
// Update focus state and suspension.
const bool is_obscured = has_obscuring_child_applets || !applet->window_visible;
@@ -453,23 +514,18 @@ void WindowSystem::UpdateAppletStateLocked(Applet* applet, bool is_foreground, b
applet->UpdateSuspensionStateLocked(true);
}
- // Z-index logic like in reference C# implementation (tuned for overlay extremes)
- s32 z_index = 0;
- const bool now_foreground = inherited_foreground;
- if (applet->applet_id == AppletId::OverlayDisplay) {
- z_index = applet->overlay_in_foreground ? 100000 : -1;
- } else if (now_foreground && !is_obscured) {
- z_index = 2;
- } else if (now_foreground) {
- z_index = 1;
- } else {
- z_index = 0;
+ // Layer ordering. Composition sorts back-to-front. Now with enums for calrity.
+ s32 z_index = Background;
+ if (is_overlay) {
+ z_index = Overlay;
+ } else if (inherited_foreground) {
+ z_index = is_obscured ? Foreground : ForegroundVisible;
}
applet->display_layer_manager.SetOverlayZIndex(z_index);
// Recurse into child applets.
for (const auto& child_applet : applet->child_applets) {
- this->UpdateAppletStateLocked(child_applet.get(), is_foreground, overlay_blocking);
+ this->UpdateAppletStateLocked(child_applet.get(), is_foreground, overlay_takes_input);
}
}
diff --git a/src/core/hle/service/am/window_system.h b/src/core/hle/service/am/window_system.h
index 485e57f902..824ae14a1d 100644
--- a/src/core/hle/service/am/window_system.h
+++ b/src/core/hle/service/am/window_system.h
@@ -9,6 +9,7 @@
#include <map>
#include <memory>
#include <mutex>
+#include <optional>
#include "common/common_types.h"
#include "core/hle/service/am/am_types.h"
@@ -60,11 +61,16 @@ public:
void OnPowerButtonPressed(ButtonPressDuration type) {}
private:
+ void UpdateCurrentApplicationLocked();
+ void NotifyApplicationChangedIfNeeded();
void PruneTerminatedAppletsLocked();
bool RestartAppletProcessLocked(Applet* applet);
bool LockHomeMenuIntoForegroundLocked();
void TerminateChildAppletsLocked(Applet* applet);
- void UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_blocking = false);
+ bool IsOverlayOpenLocked(const Applet& overlay) const;
+ bool DoesOverlayTakeInputLocked() const;
+
+ void UpdateAppletStateLocked(Applet* applet, bool is_foreground, bool overlay_takes_input);
void SendButtonAppletMessageLocked(AppletMessage message);
private:
@@ -88,6 +94,7 @@ private:
// Applet map by aruid.
std::map<u64, std::shared_ptr<Applet>> m_applets{};
+ std::optional<u64> m_pending_application_notification{};
};
} // namespace Service::AM
diff --git a/src/core/hle/service/aoc/addon_content_manager.cpp b/src/core/hle/service/aoc/addon_content_manager.cpp
index 4d3e640bb3..be2aa34baa 100644
--- a/src/core/hle/service/aoc/addon_content_manager.cpp
+++ b/src/core/hle/service/aoc/addon_content_manager.cpp
@@ -23,6 +23,7 @@
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/server_manager.h"
+#include "core/hle/service/am/am_results.h"
#include "core/loader/loader.h"
namespace Service::AOC {
@@ -31,6 +32,10 @@ static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
return FileSys::GetBaseTitleID(title_id) == base;
}
+static u64 GetCallerBaseTitleID(Core::System& system, const ClientProcessId& process_id) {
+ return FileSys::GetBaseTitleID(system.ResolveCallerProgramId(*process_id));
+}
+
static std::vector<u64> AccumulateAOCTitleIDs(Core::System& system) {
std::vector<u64> add_on_content;
const auto& rcu = system.GetContentProvider();
@@ -91,7 +96,7 @@ IAddOnContentManager::~IAddOnContentManager() {
Result IAddOnContentManager::CountAddOnContent(Out<u32> out_count, ClientProcessId process_id) {
LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid);
- const auto current = system.GetApplicationProcessProgramID();
+ const auto current = GetCallerBaseTitleID(system, process_id);
const auto& disabled = Settings::values.disabled_addons[current];
if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) {
@@ -112,7 +117,7 @@ Result IAddOnContentManager::ListAddOnContent(Out<u32> out_count,
LOG_DEBUG(Service_AOC, "called with offset={}, count={}, process_id={}", offset, count,
process_id.pid);
- const auto current = FileSys::GetBaseTitleID(system.GetApplicationProcessProgramID());
+ const auto current = GetCallerBaseTitleID(system, process_id);
std::vector<u32> out;
const auto& disabled = Settings::values.disabled_addons[current];
@@ -126,8 +131,7 @@ Result IAddOnContentManager::ListAddOnContent(Out<u32> out_count,
}
}
- // TODO(DarkLordZach): Find the correct error code.
- R_UNLESS(out.size() >= offset, ResultUnknown);
+ R_UNLESS(out.size() >= offset, AM::ResultApplicationRecordNotFound);
*out_count = static_cast<u32>(std::min<size_t>(out.size() - offset, count));
std::rotate(out.begin(), out.begin() + offset, out.end());
@@ -141,7 +145,7 @@ Result IAddOnContentManager::GetAddOnContentBaseId(Out<u64> out_title_id,
ClientProcessId process_id) {
LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid);
- const auto title_id = system.GetApplicationProcessProgramID();
+ const auto title_id = system.ResolveCallerProgramId(*process_id);
const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
system.GetContentProvider()};
diff --git a/src/core/hle/service/bcat/bcat_service.cpp b/src/core/hle/service/bcat/bcat_service.cpp
index 210fd77094..bc2a3e3457 100644
--- a/src/core/hle/service/bcat/bcat_service.cpp
+++ b/src/core/hle/service/bcat/bcat_service.cpp
@@ -24,8 +24,8 @@ static u64 GetCurrentBuildID(const Core::System::CurrentBuildProcessID& id) {
return out;
}
-IBcatService::IBcatService(Core::System& system_, BcatBackend& backend_)
- : ServiceFramework{system_, "IBcatService"}, backend{backend_},
+IBcatService::IBcatService(Core::System& system_, BcatBackend& backend_, u64 program_id_)
+ : ServiceFramework{system_, "IBcatService"}, backend{backend_}, program_id{program_id_},
progress{{
ProgressServiceBackend{system_, "Normal"},
ProgressServiceBackend{system_, "Directory"},
@@ -70,8 +70,7 @@ Result IBcatService::RequestSyncDeliveryCache(
LOG_DEBUG(Service_BCAT, "called");
auto& progress_backend{GetProgressBackend(SyncType::Normal)};
- backend.Synchronize(system.Kernel(), {system.GetApplicationProcessProgramID(),
- GetCurrentBuildID(system.GetApplicationProcessBuildID())},
+ backend.Synchronize(system.Kernel(), {program_id, GetCurrentBuildID(system.GetApplicationProcessBuildID())},
GetProgressBackend(SyncType::Normal));
*out_interface = std::make_shared<IDeliveryCacheProgressService>(
@@ -86,9 +85,8 @@ Result IBcatService::RequestSyncDeliveryCacheWithDirectoryName(
LOG_DEBUG(Service_BCAT, "called, name={}", name);
auto& progress_backend{GetProgressBackend(SyncType::Directory)};
- backend.SynchronizeDirectory(system.Kernel(), {system.GetApplicationProcessProgramID(),
- GetCurrentBuildID(system.GetApplicationProcessBuildID())},
- name, progress_backend);
+ backend.SynchronizeDirectory(system.Kernel(), {program_id, GetCurrentBuildID(system.GetApplicationProcessBuildID())},
+ name, progress_backend);
*out_interface = std::make_shared<IDeliveryCacheProgressService>(
system, progress_backend.GetEvent(), progress_backend.GetImpl());
diff --git a/src/core/hle/service/bcat/bcat_service.h b/src/core/hle/service/bcat/bcat_service.h
index dda5a2d5f3..5e11059a78 100644
--- a/src/core/hle/service/bcat/bcat_service.h
+++ b/src/core/hle/service/bcat/bcat_service.h
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
@@ -19,7 +22,7 @@ class IDeliveryCacheProgressService;
class IBcatService final : public ServiceFramework<IBcatService> {
public:
- explicit IBcatService(Core::System& system_, BcatBackend& backend_);
+ explicit IBcatService(Core::System& system_, BcatBackend& backend_, u64 program_id_);
~IBcatService() override;
private:
@@ -39,6 +42,7 @@ private:
const ProgressServiceBackend& GetProgressBackend(SyncType type) const;
BcatBackend& backend;
+ u64 program_id;
std::array<ProgressServiceBackend, static_cast<size_t>(SyncType::Count)> progress;
};
diff --git a/src/core/hle/service/bcat/service_creator.cpp b/src/core/hle/service/bcat/service_creator.cpp
index ca339e5a68..15f59d3056 100644
--- a/src/core/hle/service/bcat/service_creator.cpp
+++ b/src/core/hle/service/bcat/service_creator.cpp
@@ -1,6 +1,10 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "core/core.h"
#include "core/hle/service/bcat/bcat_service.h"
#include "core/hle/service/bcat/delivery_cache_storage_service.h"
#include "core/hle/service/bcat/service_creator.h"
@@ -37,7 +41,8 @@ IServiceCreator::~IServiceCreator() = default;
Result IServiceCreator::CreateBcatService(ClientProcessId process_id,
OutInterface<IBcatService> out_interface) {
LOG_INFO(Service_BCAT, "called, process_id={}", process_id.pid);
- *out_interface = std::make_shared<IBcatService>(system, *backend);
+ *out_interface =
+ std::make_shared<IBcatService>(system, *backend, system.ResolveCallerProgramId(*process_id));
R_SUCCEED();
}
@@ -45,7 +50,7 @@ Result IServiceCreator::CreateDeliveryCacheStorageService(
ClientProcessId process_id, OutInterface<IDeliveryCacheStorageService> out_interface) {
LOG_INFO(Service_BCAT, "called, process_id={}", process_id.pid);
- const auto title_id = system.GetApplicationProcessProgramID();
+ const auto title_id = system.ResolveCallerProgramId(*process_id);
*out_interface =
std::make_shared<IDeliveryCacheStorageService>(system, fsc.GetBCATDirectory(title_id));
R_SUCCEED();
diff --git a/src/core/hle/service/caps/caps_manager.cpp b/src/core/hle/service/caps/caps_manager.cpp
index 08725cb565..407557b472 100644
--- a/src/core/hle/service/caps/caps_manager.cpp
+++ b/src/core/hle/service/caps/caps_manager.cpp
@@ -243,10 +243,15 @@ Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
return SaveScreenShot(out_entry, attribute, report_option, {}, image_data, aruid);
}
-Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry, const ScreenShotAttribute& attribute, AlbumReportOption report_option, const ApplicationData& app_data, std::span<const u8> image_data, u64 aruid) {
+Result AlbumManager::SaveScreenShot(ApplicationAlbumEntry& out_entry,
+ const ScreenShotAttribute& attribute,
+ AlbumReportOption report_option,
+ const ApplicationData& app_data, std::span<const u8> image_data,
+ u64 aruid) {
R_UNLESS(!image_data.empty(), ResultUnknown); //TODO: ???
- const u64 title_id = system.GetApplicationProcessProgramID();
+ const u64 title_id = system.ResolveCallerProgramId(aruid);
+
auto static_service =
system.ServiceManager().GetService<Service::Glue::Time::StaticService>("time:u", true);
diff --git a/src/core/hle/service/caps/caps_su.cpp b/src/core/hle/service/caps/caps_su.cpp
index d89f147a75..3d54564612 100644
--- a/src/core/hle/service/caps/caps_su.cpp
+++ b/src/core/hle/service/caps/caps_su.cpp
@@ -95,7 +95,8 @@ void IScreenShotApplicationService::CaptureAndSaveScreenshot(AlbumReportOption r
manager->FlipVerticallyOnWrite(invert_y);
manager->SaveScreenShot(entry, attribute, report_option, image_data, {});
},
- layout);
+ layout,
+ Nvnflinger::LayerStackId::Screenshot);
}
} // namespace Service::Capture
diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp
index 329a18e23c..9689bce8dd 100644
--- a/src/core/hle/service/hid/hid_server.cpp
+++ b/src/core/hle/service/hid/hid_server.cpp
@@ -1164,7 +1164,7 @@ Result IHidServer::InitializeSevenSixAxisSensor(ClientAppletResourceUserId aruid
GetResourceManager()->GetConsoleSixAxis()->Activate();
GetResourceManager()->GetSevenSixAxis()->Activate();
- GetResourceManager()->GetSevenSixAxis()->SetTransferMemoryAddress(t_mem_1->GetSourceAddress());
+ GetResourceManager()->GetSevenSixAxis()->SetTransferMemoryAddress(t_mem_1->GetSourceAddress(), t_mem_1->GetOwner());
R_SUCCEED();
}
diff --git a/src/core/hle/service/hid/hidbus.cpp b/src/core/hle/service/hid/hidbus.cpp
index dff5df47f4..70bb5437e3 100644
--- a/src/core/hle/service/hid/hidbus.cpp
+++ b/src/core/hle/service/hid/hidbus.cpp
@@ -312,7 +312,7 @@ Result Hidbus::EnableJoyPollingReceiveMode(u32 t_mem_size, JoyPollingMode pollin
auto& device = devices[device_index.value()].device;
device->SetPollingMode(polling_mode);
- device->SetTransferMemoryAddress(t_mem->GetSourceAddress());
+ device->SetTransferMemoryAddress(t_mem->GetSourceAddress(), t_mem->GetOwner());
R_SUCCEED();
}
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index 7d7368ff9c..3d87e6788d 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -147,7 +150,7 @@ Result IRS::RunImageTransferProcessor(
MakeProcessorWithCoreContext<ImageTransferProcessor>(camera_handle, device);
auto& image_transfer_processor = GetProcessor<ImageTransferProcessor>(camera_handle);
image_transfer_processor.SetConfig(processor_config);
- image_transfer_processor.SetTransferMemoryAddress(t_mem->GetSourceAddress());
+ image_transfer_processor.SetTransferMemoryAddress(t_mem->GetSourceAddress(), t_mem->GetOwner());
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::IR);
@@ -295,7 +298,8 @@ Result IRS::RunImageTransferExProcessor(
MakeProcessorWithCoreContext<ImageTransferProcessor>(camera_handle, device);
auto& image_transfer_processor = GetProcessor<ImageTransferProcessor>(camera_handle);
image_transfer_processor.SetConfig(processor_config);
- image_transfer_processor.SetTransferMemoryAddress(t_mem->GetSourceAddress());
+ image_transfer_processor.SetTransferMemoryAddress(t_mem->GetSourceAddress(),
+ t_mem->GetOwner());
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::IR);
diff --git a/src/core/hle/service/jit/jit.cpp b/src/core/hle/service/jit/jit.cpp
index 626b643c00..ec3a3a8dcb 100644
--- a/src/core/hle/service/jit/jit.cpp
+++ b/src/core/hle/service/jit/jit.cpp
@@ -35,7 +35,7 @@ public:
, process{kernel, process_}
, user_rx{std::move(user_rx_)}
, user_ro{std::move(user_ro_)}
- , context{system_.ApplicationMemory()}
+ , context{process_->GetMemory()}
{
// clang-format off
diff --git a/src/core/hle/service/ns/read_only_application_control_data_interface.cpp b/src/core/hle/service/ns/read_only_application_control_data_interface.cpp
index 6e486855d6..e8776d64b1 100644
--- a/src/core/hle/service/ns/read_only_application_control_data_interface.cpp
+++ b/src/core/hle/service/ns/read_only_application_control_data_interface.cpp
@@ -14,6 +14,7 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/vfs/vfs.h"
+#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_transfer_memory.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/ns/language.h"
@@ -332,8 +333,8 @@ void IReadOnlyApplicationControlDataInterface::ListApplicationTitle(HLERequestCo
constexpr s32 data_offset = 0;
- if (t_mem != nullptr && app_count > 0) {
- auto& memory = system.ApplicationMemory();
+ if (t_mem != nullptr && t_mem->GetOwner() != nullptr && app_count > 0) {
+ auto& memory = t_mem->GetOwner()->GetMemory();
const auto t_mem_address = t_mem->GetSourceAddress();
for (size_t i = 0; i < app_count; ++i) {
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
index 6e9fe9ec07..b8a8979315 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
@@ -77,6 +77,7 @@ void nvdisp_disp0::Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers
.transform_flags = layer.transform,
.crop_rect = layer.crop_rect,
.blending = ConvertBlending(layer.blending),
+ .layer_stack_mask = layer.layer_stack_mask,
});
for (size_t i = 0; i < layer.acquire_fence.num_fences; i++) {
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index a9f14df612..358679b831 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -69,7 +69,7 @@ NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> inpu
case 0x3:
return WrapFixed(this, &nvhost_gpu::ChannelSetTimeout, input, output);
case 0x8:
- return WrapFixedVariable(this, &nvhost_gpu::SubmitGPFIFOBase1, input, output, false);
+ return WrapFixedVariable(this, &nvhost_gpu::SubmitGPFIFOBase1, input, output, fd, false);
case 0x9:
return WrapFixed(this, &nvhost_gpu::AllocateObjectContext, input, output);
case 0xb:
@@ -83,7 +83,7 @@ NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> inpu
case 0x1a:
return WrapFixed(this, &nvhost_gpu::AllocGPFIFOEx2, input, output, fd);
case 0x1b:
- return WrapFixedVariable(this, &nvhost_gpu::SubmitGPFIFOBase1, input, output, true);
+ return WrapFixedVariable(this, &nvhost_gpu::SubmitGPFIFOBase1, input, output, fd, true);
case 0x1d:
return WrapFixed(this, &nvhost_gpu::ChannelSetTimeslice, input, output);
default:
@@ -387,8 +387,19 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandL
return NvResult::Success;
}
+Core::Memory::Memory& nvhost_gpu::GetSessionMemory(DeviceFD fd) {
+ if (const auto it = sessions.find(fd); it != sessions.end())
+ if (auto* const session = core.GetSession(it->second);
+ session != nullptr && session->process != nullptr)
+ return session->process->GetMemory();
+
+ LOG_ERROR(Service_NVDRV, "No session for fd={}, falling back to application memory", fd);
+ return system.ApplicationMemory();
+}
+
NvResult nvhost_gpu::SubmitGPFIFOBase1(IoctlSubmitGpfifo& params,
- std::span<Tegra::CommandListHeader> commands, bool kickoff) {
+ std::span<Tegra::CommandListHeader> commands, DeviceFD fd,
+ bool kickoff) {
if (params.num_entries > commands.size()) {
UNIMPLEMENTED();
return NvResult::InvalidSize;
@@ -396,7 +407,7 @@ NvResult nvhost_gpu::SubmitGPFIFOBase1(IoctlSubmitGpfifo& params,
Tegra::CommandList entries(params.num_entries);
if (kickoff) {
- system.ApplicationMemory().ReadBlock(params.address, entries.command_lists.data(),
+ this->GetSessionMemory(fd).ReadBlock(params.address, entries.command_lists.data(),
params.num_entries * sizeof(Tegra::CommandListHeader));
} else {
std::memcpy(entries.command_lists.data(), commands.data(),
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
index c29231dc0c..c49aaa54a2 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
@@ -16,6 +16,10 @@
#include "core/hle/service/nvdrv/nvdata.h"
#include "video_core/dma_pusher.h"
+namespace Core::Memory {
+class Memory;
+}
+
namespace Tegra {
namespace Control {
struct ChannelState;
@@ -196,8 +200,11 @@ private:
NvResult SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandList&& entries);
+ Core::Memory::Memory& GetSessionMemory(DeviceFD fd);
+
NvResult SubmitGPFIFOBase1(IoctlSubmitGpfifo& params,
- std::span<Tegra::CommandListHeader> commands, bool kickoff = false);
+ std::span<Tegra::CommandListHeader> commands, DeviceFD fd,
+ bool kickoff = false);
NvResult SubmitGPFIFOBase2(IoctlSubmitGpfifo& params,
std::span<const Tegra::CommandListHeader> commands);
diff --git a/src/core/hle/service/nvnflinger/display.h b/src/core/hle/service/nvnflinger/display.h
index ada302a79e..0beb52b966 100644
--- a/src/core/hle/service/nvnflinger/display.h
+++ b/src/core/hle/service/nvnflinger/display.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -15,7 +15,8 @@ struct Layer {
explicit Layer(std::shared_ptr<android::BufferItemConsumer> buffer_item_consumer_,
s32 consumer_id_)
: buffer_item_consumer(std::move(buffer_item_consumer_)), consumer_id(consumer_id_),
- blending(LayerBlending::None), visible(true), z_index(0), is_overlay(false) {}
+ blending(LayerBlending::None), visible(true), z_index(0), is_overlay(false),
+ layer_stack_mask(DefaultLayerStackMask) {}
~Layer() {
buffer_item_consumer->Abandon();
}
@@ -26,6 +27,7 @@ struct Layer {
bool visible;
s32 z_index;
bool is_overlay;
+ u32 layer_stack_mask;
};
struct LayerStack {
diff --git a/src/core/hle/service/nvnflinger/hardware_composer.cpp b/src/core/hle/service/nvnflinger/hardware_composer.cpp
index e491b24a8f..5decb791e1 100644
--- a/src/core/hle/service/nvnflinger/hardware_composer.cpp
+++ b/src/core/hle/service/nvnflinger/hardware_composer.cpp
@@ -115,6 +115,7 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display,
.transform = static_cast<android::BufferTransformFlags>(item.transform),
.crop_rect = item.crop,
.acquire_fence = item.fence,
+ .layer_stack_mask = layer->layer_stack_mask,
});
}
diff --git a/src/core/hle/service/nvnflinger/hwc_layer.h b/src/core/hle/service/nvnflinger/hwc_layer.h
index f71a5d8227..fd9819b526 100644
--- a/src/core/hle/service/nvnflinger/hwc_layer.h
+++ b/src/core/hle/service/nvnflinger/hwc_layer.h
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
@@ -23,6 +26,25 @@ enum class LayerBlending : u32 {
Coverage = 0x405,
};
+enum class LayerStackId : u32 {
+ Default = 0,
+ Lcd = 1,
+ Screenshot = 2,
+ Recording = 3,
+ LastFrame = 4,
+ Arbitrary = 5,
+ ApplicationForDebug = 6,
+ Null = 10,
+};
+
+constexpr u32 LayerStackBit(LayerStackId id) {
+ return 1U << static_cast<u32>(id);
+}
+
+constexpr u32 DefaultLayerStackMask =
+ LayerStackBit(LayerStackId::Default) | LayerStackBit(LayerStackId::Screenshot) |
+ LayerStackBit(LayerStackId::Recording) | LayerStackBit(LayerStackId::LastFrame);
+
struct HwcLayer {
u32 buffer_handle;
u32 offset;
@@ -35,6 +57,7 @@ struct HwcLayer {
android::BufferTransformFlags transform;
Common::Rectangle<int> crop_rect;
android::Fence acquire_fence;
+ u32 layer_stack_mask;
};
} // namespace Service::Nvnflinger
diff --git a/src/core/hle/service/nvnflinger/surface_flinger.cpp b/src/core/hle/service/nvnflinger/surface_flinger.cpp
index fbf70592bd..a6136c111d 100644
--- a/src/core/hle/service/nvnflinger/surface_flinger.cpp
+++ b/src/core/hle/service/nvnflinger/surface_flinger.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -104,6 +104,14 @@ void SurfaceFlinger::SetLayerBlending(s32 consumer_binder_id, LayerBlending blen
}
}
+void SurfaceFlinger::SetLayerStackMask(s32 consumer_binder_id, u32 layer_stack_mask) {
+ if (const auto layer = this->FindLayer(consumer_binder_id); layer != nullptr) {
+ layer->layer_stack_mask = layer_stack_mask;
+ LOG_DEBUG(Service_VI, "Layer {} stack mask set to {:#x}", consumer_binder_id,
+ layer_stack_mask);
+ }
+}
+
void SurfaceFlinger::SetLayerIsOverlay(s32 consumer_binder_id, bool is_overlay) {
if (const auto layer = this->FindLayer(consumer_binder_id); layer != nullptr) {
layer->is_overlay = is_overlay;
diff --git a/src/core/hle/service/nvnflinger/surface_flinger.h b/src/core/hle/service/nvnflinger/surface_flinger.h
index 05b4206c34..5eb0b22ca9 100644
--- a/src/core/hle/service/nvnflinger/surface_flinger.h
+++ b/src/core/hle/service/nvnflinger/surface_flinger.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -48,6 +48,7 @@ public:
void SetLayerVisibility(s32 consumer_binder_id, bool visible);
void SetLayerBlending(s32 consumer_binder_id, LayerBlending blending);
void SetLayerIsOverlay(s32 consumer_binder_id, bool is_overlay);
+ void SetLayerStackMask(s32 consumer_binder_id, u32 layer_stack_mask);
std::shared_ptr<Layer> FindLayer(s32 consumer_binder_id);
diff --git a/src/core/hle/service/pctl/parental_control_service.cpp b/src/core/hle/service/pctl/parental_control_service.cpp
index c2ddbcbc51..97c178799e 100644
--- a/src/core/hle/service/pctl/parental_control_service.cpp
+++ b/src/core/hle/service/pctl/parental_control_service.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -13,8 +13,10 @@
namespace Service::PCTL {
-IParentalControlService::IParentalControlService(Core::System& system_, Capability capability_)
+IParentalControlService::IParentalControlService(Core::System& system_, Capability capability_,
+ u64 program_id_)
: ServiceFramework{system_, "IParentalControlService"}, capability{capability_},
+ program_id{program_id_},
service_context{system_, "IParentalControlService"}, synchronization_event{service_context},
unlinked_event{service_context}, request_suspension_event{service_context} {
// clang-format off
@@ -202,7 +204,6 @@ Result IParentalControlService::Initialize() {
// TODO(ogniK): Recovery flag initialization for pctl:r
- const auto program_id = system.GetApplicationProcessProgramID();
if (program_id != 0) {
const FileSys::PatchManager pm{program_id, system.GetFileSystemController(),
system.GetContentProvider()};
diff --git a/src/core/hle/service/pctl/parental_control_service.h b/src/core/hle/service/pctl/parental_control_service.h
index 58e3b6123f..92d1d38429 100644
--- a/src/core/hle/service/pctl/parental_control_service.h
+++ b/src/core/hle/service/pctl/parental_control_service.h
@@ -16,7 +16,8 @@ namespace Service::PCTL {
class IParentalControlService final : public ServiceFramework<IParentalControlService> {
public:
- explicit IParentalControlService(Core::System& system_, Capability capability_);
+ explicit IParentalControlService(Core::System& system_, Capability capability_,
+ u64 program_id_);
~IParentalControlService() override;
private:
@@ -84,6 +85,7 @@ private:
RestrictionSettings restriction_settings{};
std::array<char, 8> pin_code{};
Capability capability{};
+ u64 program_id{};
// TODO: this is raw
PlayTimerSettings raw_play_timer_settings{};
diff --git a/src/core/hle/service/pctl/parental_control_service_factory.cpp b/src/core/hle/service/pctl/parental_control_service_factory.cpp
index 7d8f361e95..ae278a9bbc 100644
--- a/src/core/hle/service/pctl/parental_control_service_factory.cpp
+++ b/src/core/hle/service/pctl/parental_control_service_factory.cpp
@@ -1,6 +1,10 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "core/core.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/pctl/parental_control_service.h"
#include "core/hle/service/pctl/parental_control_service_factory.h"
@@ -23,17 +27,17 @@ IParentalControlServiceFactory::~IParentalControlServiceFactory() = default;
Result IParentalControlServiceFactory::CreateService(
Out<SharedPointer<IParentalControlService>> out_service, ClientProcessId process_id) {
- LOG_DEBUG(Service_PCTL, "called");
- // TODO(ogniK): Get application id from process
- *out_service = std::make_shared<IParentalControlService>(system, capability);
+ LOG_DEBUG(Service_PCTL, "called, process_id={}", process_id.pid);
+ *out_service = std::make_shared<IParentalControlService>(
+ system, capability, system.ResolveCallerProgramId(*process_id));
R_SUCCEED();
}
Result IParentalControlServiceFactory::CreateServiceWithoutInitialize(
Out<SharedPointer<IParentalControlService>> out_service, ClientProcessId process_id) {
- LOG_DEBUG(Service_PCTL, "called");
- // TODO(ogniK): Get application id from process
- *out_service = std::make_shared<IParentalControlService>(system, capability);
+ LOG_DEBUG(Service_PCTL, "called, process_id={}", process_id.pid);
+ *out_service = std::make_shared<IParentalControlService>(
+ system, capability, system.ResolveCallerProgramId(*process_id));
R_SUCCEED();
}
diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp
index 3e192bd840..97efb1a361 100644
--- a/src/core/hle/service/pm/pm.cpp
+++ b/src/core/hle/service/pm/pm.cpp
@@ -132,8 +132,7 @@ private:
LOG_WARNING(Service_PM, "(Partial Implementation) called, pid={:016X}", pid);
- auto list = kernel.GetProcessList();
- auto process = SearchProcessList(system.Kernel(), list, [pid](auto& p) { return p->GetProcessId() == pid; });
+ auto process = kernel.GetProcessByProcessId(pid);
if (process.IsNull()) {
IPC::ResponseBuilder rb{ctx, 2};
@@ -186,9 +185,7 @@ private:
LOG_DEBUG(Service_PM, "called, process_id={:016X}", process_id);
- auto list = kernel.GetProcessList();
- auto process = SearchProcessList(system.Kernel(),
- list, [process_id](auto& p) { return p->GetProcessId() == process_id; });
+ auto process = kernel.GetProcessByProcessId(process_id);
if (process.IsNull()) {
IPC::ResponseBuilder rb{ctx, 2};
diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp
index 02fa82047d..e76c3ea93c 100644
--- a/src/core/hle/service/prepo/prepo.cpp
+++ b/src/core/hle/service/prepo/prepo.cpp
@@ -76,7 +76,7 @@ private:
Type, process_id, data1.size(), data2.size());
const auto& reporter{system.GetReporter()};
- reporter.SavePlayReport(Type, system.GetApplicationProcessProgramID(), {data1, data2},
+ reporter.SavePlayReport(Type, system.ResolveCallerProgramId(process_id), {data1, data2},
process_id);
IPC::ResponseBuilder rb{ctx, 2};
@@ -98,7 +98,7 @@ private:
Type, user_id[1], user_id[0], process_id, data1.size(), data2.size());
const auto& reporter{system.GetReporter()};
- reporter.SavePlayReport(Type, system.GetApplicationProcessProgramID(), {data1, data2},
+ reporter.SavePlayReport(Type, system.ResolveCallerProgramId(process_id), {data1, data2},
process_id, user_id);
IPC::ResponseBuilder rb{ctx, 2};
diff --git a/src/core/hle/service/vi/container.cpp b/src/core/hle/service/vi/container.cpp
index e7255c2b0a..9c45f6435b 100644
--- a/src/core/hle/service/vi/container.cpp
+++ b/src/core/hle/service/vi/container.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -166,6 +166,16 @@ Result Container::GetLayerZIndex(u64 layer_id, s32* out_z_index) {
R_RETURN(VI::ResultNotFound);
}
+Result Container::SetLayerStackMask(u64 layer_id, u32 layer_stack_mask) {
+ std::scoped_lock lk{m_lock};
+
+ auto* const layer = m_layers.GetLayerById(layer_id);
+ R_UNLESS(layer != nullptr, VI::ResultNotFound);
+
+ m_surface_flinger->SetLayerStackMask(layer->GetConsumerBinderId(), layer_stack_mask);
+ R_SUCCEED();
+}
+
Result Container::SetLayerIsOverlay(u64 layer_id, bool is_overlay) {
std::scoped_lock lk{m_lock};
diff --git a/src/core/hle/service/vi/container.h b/src/core/hle/service/vi/container.h
index 5d111e71ab..e4453a6f72 100644
--- a/src/core/hle/service/vi/container.h
+++ b/src/core/hle/service/vi/container.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
@@ -68,6 +68,7 @@ public:
Result SetLayerZIndex(u64 layer_id, s32 z_index);
Result GetLayerZIndex(u64 layer_id, s32* out_z_index);
Result SetLayerIsOverlay(u64 layer_id, bool is_overlay);
+ Result SetLayerStackMask(u64 layer_id, u32 layer_stack_mask);
void LinkVsyncEvent(u64 display_id, Event* event);
void UnlinkVsyncEvent(u64 display_id, Event* event);
diff --git a/src/core/hle/service/vi/shared_buffer_manager.cpp b/src/core/hle/service/vi/shared_buffer_manager.cpp
index 21e42e2094..483c1f71ba 100644
--- a/src/core/hle/service/vi/shared_buffer_manager.cpp
+++ b/src/core/hle/service/vi/shared_buffer_manager.cpp
@@ -4,9 +4,15 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <algorithm>
+#include <array>
#include <random>
+#include "common/assert.h"
+#include "common/logging.h"
+#include "common/scratch_buffer.h"
#include "core/core.h"
+#include "core/hle/kernel/k_page_group.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_system_resource.h"
#include "core/hle/service/nvdrv/devices/nvmap.h"
@@ -47,7 +53,7 @@ Result AllocateSharedBufferMemory(std::unique_ptr<Kernel::KPageGroup>* out_page_
u32* end = system.DeviceMemory().GetPointer<u32>(block.GetAddress() + block.GetSize());
for (; start < end; start++) {
- *start = 0xFF0000FF;
+ *start = 0x00000000;
}
}
@@ -168,7 +174,14 @@ constexpr u32 SharedBufferBlockLinearWidth = 1280;
constexpr u32 SharedBufferBlockLinearHeight = 768;
constexpr u32 SharedBufferBlockLinearStride =
SharedBufferBlockLinearWidth * SharedBufferBlockLinearBpp;
-constexpr u32 SharedBufferNumSlots = 7;
+
+constexpr u32 SharedBufferNumCaptureSlots = 3;
+constexpr u32 SharedBufferSlotsPerSession = 2;
+constexpr u32 SharedBufferMaxSessions = 2;
+
+constexpr u32 SharedBufferNumSlots =
+ SharedBufferNumCaptureSlots + SharedBufferSlotsPerSession * SharedBufferMaxSessions;
+static_assert(SharedBufferNumSlots <= 16, "Shared buffer pool exceeds the maximum texture count");
constexpr u32 SharedBufferWidth = 1280;
constexpr u32 SharedBufferHeight = 720;
@@ -192,7 +205,52 @@ constexpr SharedMemoryPoolLayout SharedBufferPoolLayout = [] {
return layout;
}();
-void MakeGraphicBuffer(android::BufferQueueProducer& producer, u32 slot, u32 handle) {
+constexpr u32 GetCaptureSlot(CaptureKind kind) {
+ return static_cast<u32>(kind);
+}
+static_assert(static_cast<u32>(CaptureKind::CallerApplet) + 1 == SharedBufferNumCaptureSlots,
+ "Capture slot count does not match CaptureKind");
+
+constexpr u32 GetPresentationSlot(u32 slot_base, u32 index) {
+ return SharedBufferNumCaptureSlots + slot_base + index;
+}
+
+constexpr u32 ColorOpaqueBlackRgba32 = 0xFF000000;
+
+template <typename F>
+void ForEachPoolChunk(Core::System& system, Kernel::KPageGroup& page_group, u64 offset, u64 size,
+ F&& writer) {
+ Common::ScratchBuffer<u32> scratch;
+ const u64 range_end = offset + size;
+ u64 pool_pos = 0;
+
+ for (auto& block : page_group) {
+ const u64 block_begin = pool_pos;
+ const u64 block_end = block_begin + block.GetSize();
+ pool_pos = block_end;
+
+ if (block_end <= offset) {
+ continue;
+ }
+ if (block_begin >= range_end) {
+ break;
+ }
+
+ const u64 chunk_begin = (std::max)(block_begin, offset);
+ const u64 chunk_end = (std::min)(block_end, range_end);
+ const u64 chunk_size = chunk_end - chunk_begin;
+
+ u8* const dst =
+ system.DeviceMemory().GetPointer<u8>(block.GetAddress()) + (chunk_begin - block_begin);
+
+ writer(dst, chunk_begin - offset, chunk_size);
+
+ system.GPU().Host1x().MemoryManager().ApplyOpOnPointer(
+ dst, scratch, [&](DAddr addr) { system.GPU().InvalidateRegion(addr, chunk_size); });
+ }
+}
+
+void MakeGraphicBuffer(android::BufferQueueProducer& producer, u32 producer_slot, u32 pool_slot, u32 handle) {
auto buffer = std::make_shared<android::NvGraphicBuffer>();
buffer->width = SharedBufferWidth;
buffer->height = SharedBufferHeight;
@@ -200,8 +258,8 @@ void MakeGraphicBuffer(android::BufferQueueProducer& producer, u32 slot, u32 han
buffer->format = SharedBufferBlockLinearFormat;
buffer->external_format = SharedBufferBlockLinearFormat;
buffer->buffer_id = handle;
- buffer->offset = slot * SharedBufferSlotSize;
- ASSERT(producer.SetPreallocatedBuffer(slot, buffer) == android::Status::NoError);
+ buffer->offset = pool_slot * SharedBufferSlotSize;
+ ASSERT(producer.SetPreallocatedBuffer(producer_slot, buffer) == android::Status::NoError);
}
} // namespace
@@ -215,59 +273,91 @@ SharedBufferManager::~SharedBufferManager() = default;
Result SharedBufferManager::CreateSession(Kernel::KProcess* owner_process, u64* out_buffer_id,
u64* out_layer_handle, u64 display_id,
bool enable_blending) {
- std::scoped_lock lk{m_guard};
+ {
+ std::scoped_lock lk{m_guard};
+
+ // Ensure we haven't already created.
+ const u64 aruid = owner_process->GetProcessId();
+ R_UNLESS(!m_sessions.contains(aruid), VI::ResultPermissionDenied);
+
+ // Allocate memory for the shared buffer if needed.
+ if (!m_buffer_page_group) {
+ R_TRY(AllocateSharedBufferMemory(std::addressof(m_buffer_page_group), m_system,
+ SharedBufferSize));
+
+ // Record buffer id.
+ m_buffer_id = m_next_buffer_id++;
+
+ // Record display id.
+ m_display_id = display_id;
+
+ for (u32 slot = 0; slot < SharedBufferNumCaptureSlots; slot++) {
+ ForEachPoolChunk(m_system, *m_buffer_page_group, u64{slot} * SharedBufferSlotSize,
+ SharedBufferSlotSize, [](u8* dst, u64, u64 length) {
+ std::fill_n(reinterpret_cast<u32*>(dst), length / sizeof(u32),
+ ColorOpaqueBlackRgba32);
+ });
+ }
+ }
- // Ensure we haven't already created.
- const u64 aruid = owner_process->GetProcessId();
- R_UNLESS(!m_sessions.contains(aruid), VI::ResultPermissionDenied);
+ // Claim a presentation slot range.
+ u32 slot_base = 0;
+
+ std::array<bool, SharedBufferMaxSessions> in_use{};
+ for (const auto& [existing_aruid, existing] : m_sessions) {
+ const u32 index = existing.presentation_slot_base / SharedBufferSlotsPerSession;
- // Allocate memory for the shared buffer if needed.
- if (!m_buffer_page_group) {
- R_TRY(AllocateSharedBufferMemory(std::addressof(m_buffer_page_group), m_system,
- SharedBufferSize));
+ if (index < in_use.size())
+ in_use[index] = true;
+ }
- // Record buffer id.
- m_buffer_id = m_next_buffer_id++;
+ u32 index = 0;
+ while (index < in_use.size() && in_use[index])
+ index++;
- // Record display id.
- m_display_id = display_id;
- }
+ if (index >= in_use.size()) {
+ LOG_ERROR(Service_VI, "Out of shared buffer presentation slots ({} sessions)", SharedBufferMaxSessions);
+ R_THROW(VI::ResultOperationFailed);
+ }
- // Map into process.
- Common::ProcessAddress map_address{};
- R_TRY(MapSharedBufferIntoProcessAddressSpace(std::addressof(map_address), m_buffer_page_group,
- owner_process, m_system));
+ slot_base = index * SharedBufferSlotsPerSession;
- // Create new session.
- auto [it, was_emplaced] = m_sessions.emplace(aruid, SharedBufferSession{});
- auto& session = it->second;
+ // Map into process.
+ Common::ProcessAddress map_address{};
+ R_TRY(MapSharedBufferIntoProcessAddressSpace(std::addressof(map_address), m_buffer_page_group,
+ owner_process, m_system));
- auto& container = m_nvdrv->GetContainer();
- session.session_id = container.OpenSession(owner_process);
- session.nvmap_fd = m_nvdrv->Open("/dev/nvmap", session.session_id);
+ // Create new session.
+ auto [it, was_emplaced] = m_sessions.emplace(aruid, SharedBufferSession{});
+ auto& session = it->second;
+ session.presentation_slot_base = slot_base;
- // Create an nvmap handle for the buffer and assign the memory to it.
- R_TRY(AllocateHandleForBuffer(std::addressof(session.buffer_nvmap_handle), *m_nvdrv,
- session.nvmap_fd, map_address, SharedBufferSize));
+ auto& container = m_nvdrv->GetContainer();
+ session.session_id = container.OpenSession(owner_process);
+ session.nvmap_fd = m_nvdrv->Open("/dev/nvmap", session.session_id);
- // Create and open a layer for the display.
- s32 producer_binder_id;
- R_TRY(m_container.CreateStrayLayer(std::addressof(producer_binder_id),
- std::addressof(session.layer_id), display_id));
+ // Create an nvmap handle for the buffer and assign the memory to it.
+ R_TRY(AllocateHandleForBuffer(std::addressof(session.buffer_nvmap_handle), *m_nvdrv,
+ session.nvmap_fd, map_address, SharedBufferSize));
- // Configure blending and z-index
- R_ASSERT(m_container.SetLayerBlending(session.layer_id, enable_blending));
+ // Create and open a layer for the display.
+ s32 producer_binder_id;
+ R_TRY(m_container.CreateStrayLayer(std::addressof(producer_binder_id),
+ std::addressof(session.layer_id), display_id));
- // Get the producer and set preallocated buffers.
- std::shared_ptr<android::BufferQueueProducer> producer;
- R_TRY(m_container.GetLayerProducerHandle(std::addressof(producer), session.layer_id));
- MakeGraphicBuffer(*producer, 0, session.buffer_nvmap_handle);
- MakeGraphicBuffer(*producer, 1, session.buffer_nvmap_handle);
+ // Configure blending and z-index
+ R_ASSERT(m_container.SetLayerBlending(session.layer_id, enable_blending));
- // Assign outputs.
- *out_buffer_id = m_buffer_id;
- *out_layer_handle = session.layer_id;
+ // Get the producer and set preallocated buffers.
+ std::shared_ptr<android::BufferQueueProducer> producer;
+ R_TRY(m_container.GetLayerProducerHandle(std::addressof(producer), session.layer_id));
+ for (u32 i = 0; i < SharedBufferSlotsPerSession; i++)
+ MakeGraphicBuffer(*producer, i, GetPresentationSlot(session.presentation_slot_base, i), session.buffer_nvmap_handle);
+ // Assign outputs.
+ *out_buffer_id = m_buffer_id;
+ *out_layer_handle = session.layer_id;
+ }
// We succeeded.
R_SUCCEED();
}
@@ -336,14 +426,51 @@ Result SharedBufferManager::AcquireSharedFrameBuffer(android::Fence* out_fence,
SharedBufferBlockLinearFormat, 0) == android::Status::NoError,
VI::ResultOperationFailed);
- // Assign remaining outputs.
- *out_target_slot = slot;
- out_slot_indexes = {0, 1, -1, -1};
+ out_slot_indexes.fill(-1);
+
+ {
+ std::scoped_lock lk{m_guard};
+ const auto* const session = this->FindSessionByLayerIdLocked(layer_id);
+ if (session == nullptr) {
+ producer->CancelBuffer(slot, *out_fence);
+ // LOG_DEBUG(Service_VI, "No Session found");
+ R_THROW(VI::ResultNotFound);
+ }
+
+ for (u32 i = 0; i < SharedBufferSlotsPerSession; i++)
+ out_slot_indexes[i] =
+ static_cast<s32>(GetPresentationSlot(session->presentation_slot_base, i));
+
+ *out_target_slot = static_cast<s64>(
+ GetPresentationSlot(session->presentation_slot_base, static_cast<u32>(slot)));
+ }
// We succeeded.
R_SUCCEED();
}
+Result SharedBufferManager::GetProducerSlotLocked(s32* out_producer_slot, u64 layer_id,
+ s64 pool_slot) const {
+ const auto* const session = this->FindSessionByLayerIdLocked(layer_id);
+ R_UNLESS(session != nullptr, VI::ResultNotFound);
+
+ const s64 base = GetPresentationSlot(session->presentation_slot_base, 0);
+ const s64 producer_slot = pool_slot - base;
+ R_UNLESS(producer_slot >= 0 && producer_slot < SharedBufferSlotsPerSession,
+ VI::ResultOperationFailed);
+
+ *out_producer_slot = static_cast<s32>(producer_slot);
+ R_SUCCEED();
+}
+
+const SharedBufferSession* SharedBufferManager::FindSessionByLayerIdLocked(u64 layer_id) const {
+ for (const auto& [aruid, session] : m_sessions)
+ if (session.layer_id == layer_id)
+ return std::addressof(session);
+
+ return nullptr;
+}
+
Result SharedBufferManager::PresentSharedFrameBuffer(android::Fence fence,
Common::Rectangle<s32> crop_region,
u32 transform, s32 swap_interval, u64 layer_id,
@@ -352,14 +479,20 @@ Result SharedBufferManager::PresentSharedFrameBuffer(android::Fence fence,
std::shared_ptr<android::BufferQueueProducer> producer;
R_TRY(m_container.GetLayerProducerHandle(std::addressof(producer), layer_id));
+ s32 producer_slot;
+ {
+ std::scoped_lock lk{m_guard};
+ R_TRY(this->GetProducerSlotLocked(std::addressof(producer_slot), layer_id, slot));
+ }
+
// Request to queue the buffer.
std::shared_ptr<android::GraphicBuffer> buffer;
- R_UNLESS(producer->RequestBuffer(static_cast<s32>(slot), std::addressof(buffer)) ==
+ R_UNLESS(producer->RequestBuffer(producer_slot, std::addressof(buffer)) ==
android::Status::NoError,
VI::ResultOperationFailed);
ON_RESULT_FAILURE {
- producer->CancelBuffer(static_cast<s32>(slot), fence);
+ producer->CancelBuffer(producer_slot, fence);
};
// Queue the buffer to the producer.
@@ -369,12 +502,10 @@ Result SharedBufferManager::PresentSharedFrameBuffer(android::Fence fence,
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)) ==
+ R_UNLESS(producer->QueueBuffer(producer_slot, input, std::addressof(output)) ==
android::Status::NoError,
VI::ResultOperationFailed);
- (void)m_container.SetLayerZIndex(layer_id, 100000);
-
// We succeeded.
R_SUCCEED();
}
@@ -384,8 +515,14 @@ Result SharedBufferManager::CancelSharedFrameBuffer(u64 layer_id, s64 slot) {
std::shared_ptr<android::BufferQueueProducer> producer;
R_TRY(m_container.GetLayerProducerHandle(std::addressof(producer), layer_id));
+ s32 producer_slot;
+ {
+ std::scoped_lock lk{m_guard};
+ R_TRY(this->GetProducerSlotLocked(std::addressof(producer_slot), layer_id, slot));
+ }
+
// Cancel.
- producer->CancelBuffer(static_cast<s32>(slot), android::Fence::NoFence());
+ producer->CancelBuffer(producer_slot, android::Fence::NoFence());
// We succeeded.
R_SUCCEED();
@@ -404,31 +541,47 @@ Result SharedBufferManager::GetSharedFrameBufferAcquirableEvent(Kernel::KReadabl
R_SUCCEED();
}
-Result SharedBufferManager::WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index) {
- std::vector<u8> capture_buffer(m_system.GPU().GetAppletCaptureBuffer());
- Common::ScratchBuffer<u32> scratch;
-
- // TODO: this could be optimized
- s64 e = -1280 * 768 * 4;
- for (auto& block : *m_buffer_page_group) {
- u8* start = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress());
- u8* end = m_system.DeviceMemory().GetPointer<u8>(block.GetAddress() + block.GetSize());
+Result SharedBufferManager::WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index, CaptureKind kind) {
+ std::scoped_lock lk{m_guard};
+ R_UNLESS(m_buffer_page_group != nullptr, VI::ResultNotFound);
- for (; start < end; start++) {
- *start = 0;
- if (e >= 0 && e < static_cast<s64>(capture_buffer.size())) {
- *start = capture_buffer[e];
- }
- e++;
- }
+ const std::vector<u8> capture = m_system.GPU().GetAppletCaptureBuffer();
+ const u32 slot = GetCaptureSlot(kind);
- m_system.GPU().Host1x().MemoryManager().ApplyOpOnPointer(start, scratch, [&](DAddr addr) {
- m_system.GPU().InvalidateRegion(addr, end - start);
- });
+ if (capture.size() < SharedBufferSlotSize) {
+ //LOG_WARNING(Service_VI, "Capture buffer is {} bytes, expected at least {}; not writing",
+ // capture.size(), SharedBufferSlotSize);
+ *out_was_written = false;
+ *out_layer_index = static_cast<s32>(slot);
+ R_SUCCEED();
}
+ ForEachPoolChunk(m_system, *m_buffer_page_group, u64{slot} * SharedBufferSlotSize,
+ SharedBufferSlotSize,
+ [&](u8* dst, u64 src_offset, u64 length) {
+ std::memcpy(dst, capture.data() + src_offset, length);
+ });
+
*out_was_written = true;
- *out_layer_index = 1;
+ *out_layer_index = static_cast<s32>(slot);
+ R_SUCCEED();
+}
+
+Result SharedBufferManager::ClearAppletCaptureBuffer(s32 layer_index, u32 color) {
+ std::scoped_lock lk{m_guard};
+ R_UNLESS(m_buffer_page_group != nullptr, VI::ResultNotFound);
+
+ if (layer_index < 0 || layer_index >= static_cast<s32>(SharedBufferNumCaptureSlots)) {
+ LOG_WARNING(Service_VI, "Couldnt clear non-capture slot {}", layer_index);
+ R_SUCCEED();
+ }
+
+ ForEachPoolChunk(m_system, *m_buffer_page_group, u64{static_cast<u32>(layer_index)} * SharedBufferSlotSize,
+ SharedBufferSlotSize, [&](u8* dst, u64 src_offset, u64 length) {
+ ASSERT(src_offset % sizeof(u32) == 0 && length % sizeof(u32) == 0);
+ std::fill_n(reinterpret_cast<u32*>(dst), length / sizeof(u32), color);
+ });
+
R_SUCCEED();
}
diff --git a/src/core/hle/service/vi/shared_buffer_manager.h b/src/core/hle/service/vi/shared_buffer_manager.h
index 9ef6234867..807db10f31 100644
--- a/src/core/hle/service/vi/shared_buffer_manager.h
+++ b/src/core/hle/service/vi/shared_buffer_manager.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -48,6 +48,12 @@ static_assert(sizeof(SharedMemoryPoolLayout) == 0x188, "SharedMemoryPoolLayout h
struct SharedBufferSession;
+enum class CaptureKind : u32 {
+ LastApplication,
+ LastForeground,
+ CallerApplet,
+};
+
class SharedBufferManager final {
public:
explicit SharedBufferManager(Core::System& system, Container& container,
@@ -68,9 +74,15 @@ public:
Result CancelSharedFrameBuffer(u64 layer_id, s64 slot);
Result GetSharedFrameBufferAcquirableEvent(Kernel::KReadableEvent** out_event, u64 layer_id);
- Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index);
+ Result WriteAppletCaptureBuffer(bool* out_was_written, s32* out_layer_index, CaptureKind kind);
+ Result ClearAppletCaptureBuffer(s32 layer_index, u32 color);
private:
+ const SharedBufferSession* FindSessionByLayerIdLocked(u64 layer_id) const;
+
+ /// Converts a pool slot index, which is what the guest works in, back to the buffer queues slot index
+ Result GetProducerSlotLocked(s32* out_producer_slot, u64 layer_id, s64 pool_slot) const;
+
u64 m_next_buffer_id = 1;
u64 m_display_id = 0;
u64 m_buffer_id = 0;
@@ -89,6 +101,7 @@ struct SharedBufferSession {
Nvidia::NvCore::SessionId session_id = {};
u64 layer_id = {};
u32 buffer_nvmap_handle = 0;
+ u32 presentation_slot_base = 0;
};
} // namespace Service::VI
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 69bfa2e879..90e23ccc11 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -17,6 +17,8 @@
#include "core/core.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/common_funcs.h"
+#include "core/file_sys/content_archive.h"
+#include "core/file_sys/registered_cache.h"
#include "core/file_sys/submission_package.h"
#include "core/hle/kernel/k_process.h"
#include "core/loader/deconstructed_rom_directory.h"
@@ -41,6 +43,38 @@ std::optional<FileType> IdentifyFileLoader(FileSys::VirtualFile file) {
return std::nullopt;
}
+struct IndexedProgram {
+ FileSys::VirtualFile file;
+ u64 program_id;
+ bool update_only;
+};
+
+std::optional<IndexedProgram> ResolveIndexedProgram(Core::System& system, u64 program_id,
+ std::size_t program_index) {
+ if (program_index == 0 || program_id == 0) {
+ return std::nullopt;
+ }
+
+ const auto target_id = FileSys::GetBaseTitleIDWithProgramIndex(program_id, program_index);
+ const auto& provider = system.GetContentProvider();
+
+ if (auto base = provider.GetEntryRaw(target_id, FileSys::ContentRecordType::Program)) {
+ LOG_INFO(Loader, "Program index {} resolved to {:016X}", program_index, target_id);
+ return IndexedProgram{std::move(base), target_id, false};
+ }
+
+ const auto update_id = FileSys::GetUpdateTitleID(target_id);
+ if (auto update = provider.GetEntryRaw(update_id, FileSys::ContentRecordType::Program)) {
+ LOG_INFO(Loader, "Program index {} has no base program, loading it from update {:016X}",
+ program_index, update_id);
+ return IndexedProgram{std::move(update), target_id, true};
+ }
+
+ LOG_WARNING(Loader, "No program NCA for {:016X} (index {}), falling back to the container",
+ target_id, program_index);
+ return std::nullopt;
+}
+
std::shared_ptr<FileSys::NSP> OpenContainerAsNsp(FileSys::VirtualFile file, FileType type,
u64 program_id = 0,
std::size_t program_index = 0) {
@@ -321,6 +355,11 @@ std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile
return nullptr;
}
+ if (const auto indexed = ResolveIndexedProgram(system, program_id, program_index)) {
+ return std::make_unique<AppLoader_NCA>(
+ indexed->file, indexed->update_only ? indexed->program_id : 0);
+ }
+
FileType type = IdentifyFile(file);
const FileType filename_type = GuessFromFilename(file->GetName());
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index b656b9fe96..2f9cd68e9c 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -12,7 +12,9 @@
#include "common/scope_exit.h"
#include "core/core.h"
#include "core/file_sys/content_archive.h"
+#include "core/file_sys/control_metadata.h"
#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs_factory.h"
#include "core/hle/kernel/k_process.h"
@@ -33,8 +35,14 @@ static u32 CalculatePointerBufferSize(size_t heap_size) {
}
}
-AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file_)
- : AppLoader(std::move(file_)), nca(std::make_unique<FileSys::NCA>(file)) {}
+AppLoader_NCA::AppLoader_NCA(FileSys::VirtualFile file_, u64 update_only_program_id_)
+ : AppLoader(std::move(file_)),
+ nca(std::make_unique<FileSys::NCA>(file, nullptr, update_only_program_id_ != 0)),
+ update_only_program_id(update_only_program_id_) {}
+
+u64 AppLoader_NCA::GetProgramId() const {
+ return update_only_program_id != 0 ? update_only_program_id : nca->GetTitleId();
+}
AppLoader_NCA::~AppLoader_NCA() = default;
@@ -110,11 +118,21 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S
}
LOG_INFO(Loader, "Set pointer buffer size to {:#x} bytes for ProgramID {:#018x} (Heap size: {:#x})",
- process.GetPointerBufferSize(), nca->GetTitleId(), heap_size);
+ process.GetPointerBufferSize(), GetProgramId(), heap_size);
+
+ auto metadata =
+ FileSys::PatchManager::GetMetadataFromBaseOrUpdate(system, this->GetProgramId());
+ if (metadata.first != nullptr) {
+ nacp = std::move(metadata.first);
+ LOG_INFO(Loader, "Control data for {:016X}: name=\"{}\" supported_languages={:08X}",
+ this->GetProgramId(), nacp->GetApplicationName(), nacp->GetSupportedLanguages());
+ } else {
+ LOG_WARNING(Loader, "No control data found for program {:016X}", this->GetProgramId());
+ }
// Register the process in the file system controller
system.GetFileSystemController().RegisterProcess(
- process.GetProcessId(), nca->GetTitleId(),
+ process.GetProcessId(), GetProgramId(),
std::make_shared<FileSys::RomFSFactory>(*this, system.GetContentProvider(),
system.GetFileSystemController()));
@@ -222,7 +240,25 @@ ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) {
return ResultStatus::ErrorNotInitialized;
}
- out_program_id = nca->GetTitleId();
+ out_program_id = GetProgramId();
+ return ResultStatus::Success;
+}
+
+ResultStatus AppLoader_NCA::ReadControlData(FileSys::NACP& out_control) {
+ if (nacp == nullptr) {
+ return ResultStatus::ErrorNoControl;
+ }
+
+ out_control = *nacp;
+ return ResultStatus::Success;
+}
+
+ResultStatus AppLoader_NCA::ReadTitle(std::string& out_title) {
+ if (nacp == nullptr) {
+ return ResultStatus::ErrorNoControl;
+ }
+
+ out_title = nacp->GetApplicationName();
return ResultStatus::Success;
}
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h
index 96779e27fc..cf63822120 100644
--- a/src/core/loader/nca.h
+++ b/src/core/loader/nca.h
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -11,6 +14,7 @@ class System;
}
namespace FileSys {
+class NACP;
class NCA;
}
@@ -21,7 +25,7 @@ class AppLoader_DeconstructedRomDirectory;
/// Loads an NCA file
class AppLoader_NCA final : public AppLoader {
public:
- explicit AppLoader_NCA(FileSys::VirtualFile file_);
+ explicit AppLoader_NCA(FileSys::VirtualFile file_, u64 update_only_program_id = 0);
~AppLoader_NCA() override;
/**
@@ -43,14 +47,20 @@ public:
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
ResultStatus ReadProgramId(u64& out_program_id) override;
+ ResultStatus ReadControlData(FileSys::NACP& out_control) override;
+ ResultStatus ReadTitle(std::string& out_title) override;
ResultStatus ReadBanner(std::vector<u8>& buffer) override;
ResultStatus ReadLogo(std::vector<u8>& buffer) override;
ResultStatus ReadNSOModules(Modules& modules) override;
+ u64 GetProgramId() const;
+
private:
std::unique_ptr<FileSys::NCA> nca;
+ u64 update_only_program_id{};
+ std::unique_ptr<FileSys::NACP> nacp;
std::unique_ptr<AppLoader_DeconstructedRomDirectory> directory_loader;
};
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index d8b4e018f7..0a3dc5885b 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -209,7 +209,10 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
// Apply cheats if they exist and the program has a valid title ID
if (pm) {
- system.SetApplicationProcessBuildID(nso_header.build_id);
+ // TODO(Maufeat): Check if there is a better way to check
+ if (name == "main")
+ system.SetApplicationProcessBuildID(nso_header.build_id);
+
const auto cheats = pm->CreateCheatList(nso_header.build_id);
if (!cheats.empty()) {
system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size);
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index 726bb428f6..cfc908b4ef 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -8,6 +8,7 @@
#include "common/common_types.h"
#include "core/core.h"
+#include "core/file_sys/common_funcs.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/nca_metadata.h"
@@ -37,13 +38,13 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file_,
secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS(), false);
} else {
const auto control_nca =
- nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control);
+ nsp->GetNCA(FileSys::GetBaseTitleID(nsp->GetProgramTitleID()), FileSys::ContentRecordType::Control);
if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) {
return;
}
std::tie(nacp_file, icon_file) = [this, &content_provider, &control_nca, &fsc] {
- const FileSys::PatchManager pm{nsp->GetProgramTitleID(), fsc, content_provider};
+ const FileSys::PatchManager pm{FileSys::GetBaseTitleID(nsp->GetProgramTitleID()), fsc, content_provider};
return pm.ParseControlNCA(*control_nca);
}();
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
index 8e23a54a0d..e14147f5c9 100644
--- a/src/core/memory/cheat_engine.cpp
+++ b/src/core/memory/cheat_engine.cpp
@@ -51,24 +51,41 @@ StandardVmCallbacks::StandardVmCallbacks(System& system_, const CheatProcessMeta
StandardVmCallbacks::~StandardVmCallbacks() = default;
+Kernel::KProcess* StandardVmCallbacks::GetProcess() const {
+ if (cached_process != nullptr && cached_process_id == metadata.process_id) {
+ return cached_process;
+ }
+
+ auto process = system.Kernel().GetProcessByProcessId(metadata.process_id);
+ cached_process = process.IsNull() ? nullptr : process.GetPointerUnsafe();
+ cached_process_id = metadata.process_id;
+ return cached_process;
+}
+
void StandardVmCallbacks::MemoryReadUnsafe(VAddr address, void* data, u64 size) {
+ auto* const process = this->GetProcess();
+
// Return zero on invalid address
- if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) {
+ if (process == nullptr || !IsAddressInRange(address) ||
+ !process->GetMemory().IsValidVirtualAddress(address)) {
std::memset(data, 0, size);
return;
}
- system.ApplicationMemory().ReadBlock(address, data, size);
+ process->GetMemory().ReadBlock(address, data, size);
}
void StandardVmCallbacks::MemoryWriteUnsafe(VAddr address, const void* data, u64 size) {
+ auto* const process = this->GetProcess();
+
// Skip invalid memory write address
- if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) {
+ if (process == nullptr || !IsAddressInRange(address) ||
+ !process->GetMemory().IsValidVirtualAddress(address)) {
return;
}
- if (system.ApplicationMemory().WriteBlock(address, data, size)) {
- Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), address, size);
+ if (process->GetMemory().WriteBlock(address, data, size)) {
+ Core::InvalidateInstructionCacheRange(process, address, size);
}
}
@@ -91,14 +108,16 @@ u64 StandardVmCallbacks::HidKeysDown() {
}
void StandardVmCallbacks::PauseProcess() {
- if (!system.ApplicationProcess()->IsSuspended()) {
- system.ApplicationProcess()->SetActivity(system.Kernel(), Kernel::Svc::ProcessActivity::Paused);
+ auto* const process = this->GetProcess();
+ if (process != nullptr && !process->IsSuspended()) {
+ process->SetActivity(system.Kernel(), Kernel::Svc::ProcessActivity::Paused);
}
}
void StandardVmCallbacks::ResumeProcess() {
- if (system.ApplicationProcess()->IsSuspended()) {
- system.ApplicationProcess()->SetActivity(system.Kernel(), Kernel::Svc::ProcessActivity::Runnable);
+ auto* const process = this->GetProcess();
+ if (process != nullptr && process->IsSuspended()) {
+ process->SetActivity(system.Kernel(), Kernel::Svc::ProcessActivity::Runnable);
}
}
diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h
index f52f2be7c3..abd3abc93f 100644
--- a/src/core/memory/cheat_engine.h
+++ b/src/core/memory/cheat_engine.h
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -15,6 +18,10 @@ namespace Core {
class System;
}
+namespace Kernel {
+class KProcess;
+}
+
namespace Core::Timing {
class CoreTiming;
struct EventType;
@@ -38,8 +45,12 @@ public:
private:
bool IsAddressInRange(VAddr address) const;
+ Kernel::KProcess* GetProcess() const;
+
const CheatProcessMetadata& metadata;
Core::System& system;
+ mutable Kernel::KProcess* cached_process{};
+ mutable u64 cached_process_id{};
};
// Intermediary class that parses a text file or other disk format for storing cheats into a
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
index 2a5212fed8..d7edb3ffcb 100644
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -239,7 +239,7 @@ void Reporter::SaveUnimplementedFunctionReport(Service::HLERequestContext& ctx,
const auto title_id = system.GetApplicationProcessProgramID();
auto out = GetFullDataAuto(timestamp, title_id, system);
- auto function_out = GetHLERequestContextData(ctx, system.ApplicationMemory());
+ auto function_out = GetHLERequestContextData(ctx, ctx.GetMemory());
function_out["command_id"] = command_id;
function_out["function_name"] = name;
function_out["service_name"] = service_name;
diff --git a/src/hid_core/hidbus/hidbus_base.cpp b/src/hid_core/hidbus/hidbus_base.cpp
index 632bb173b6..e57121fa31 100644
--- a/src/hid_core/hidbus/hidbus_base.cpp
+++ b/src/hid_core/hidbus/hidbus_base.cpp
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -62,8 +65,9 @@ void HidbusBase::DisablePollingMode() {
polling_mode_enabled = false;
}
-void HidbusBase::SetTransferMemoryAddress(Common::ProcessAddress t_mem) {
+void HidbusBase::SetTransferMemoryAddress(Common::ProcessAddress t_mem, Kernel::KProcess* owner) {
transfer_memory = t_mem;
+ transfer_memory_owner = owner;
}
Kernel::KReadableEvent& HidbusBase::GetSendCommandAsycEvent() const {
diff --git a/src/hid_core/hidbus/hidbus_base.h b/src/hid_core/hidbus/hidbus_base.h
index c948606e40..5c29ada2e2 100644
--- a/src/hid_core/hidbus/hidbus_base.h
+++ b/src/hid_core/hidbus/hidbus_base.h
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -14,6 +17,7 @@ class System;
namespace Kernel {
class KEvent;
+class KProcess;
class KReadableEvent;
} // namespace Kernel
@@ -138,7 +142,7 @@ public:
void DisablePollingMode();
// Called on EnableJoyPollingReceiveMode
- void SetTransferMemoryAddress(Common::ProcessAddress t_mem);
+ void SetTransferMemoryAddress(Common::ProcessAddress t_mem, Kernel::KProcess* owner);
Kernel::KReadableEvent& GetSendCommandAsycEvent() const;
@@ -175,6 +179,7 @@ protected:
ButtonOnlyPollingDataAccessor button_only_data{};
Common::ProcessAddress transfer_memory{};
+ Kernel::KProcess* transfer_memory_owner{};
Core::System& system;
Kernel::KEvent* send_command_async_event;
diff --git a/src/hid_core/hidbus/ringcon.cpp b/src/hid_core/hidbus/ringcon.cpp
index 74b4d7189b..a6798d22aa 100644
--- a/src/hid_core/hidbus/ringcon.cpp
+++ b/src/hid_core/hidbus/ringcon.cpp
@@ -6,6 +6,7 @@
#include "core/core.h"
#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/memory.h"
#include "hid_core/frontend/emulated_controller.h"
@@ -67,8 +68,10 @@ void RingController::OnUpdate() {
curr_entry.polling_data.out_size = sizeof(ringcon_value);
std::memcpy(curr_entry.polling_data.data.data(), &ringcon_value, sizeof(ringcon_value));
- system.ApplicationMemory().WriteBlock(transfer_memory, &enable_sixaxis_data,
- sizeof(enable_sixaxis_data));
+ if (transfer_memory_owner != nullptr) {
+ transfer_memory_owner->GetMemory().WriteBlock(transfer_memory, &enable_sixaxis_data,
+ sizeof(enable_sixaxis_data));
+ }
break;
}
default:
diff --git a/src/hid_core/irsensor/image_transfer_processor.cpp b/src/hid_core/irsensor/image_transfer_processor.cpp
index fb3e424d91..229433df79 100644
--- a/src/hid_core/irsensor/image_transfer_processor.cpp
+++ b/src/hid_core/irsensor/image_transfer_processor.cpp
@@ -1,10 +1,11 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/core.h"
+#include "core/hle/kernel/k_process.h"
#include "core/memory.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
@@ -48,10 +49,12 @@ void ImageTransferProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType
if (type != Core::HID::ControllerTriggerType::IrSensor) {
return;
}
- if (transfer_memory == 0) {
+ if (transfer_memory == 0 || transfer_memory_owner == nullptr) {
return;
}
+ auto& memory = transfer_memory_owner->GetMemory();
+
const auto& camera_data = npad_device->GetCamera();
// This indicates how much ambient light is present
@@ -61,16 +64,14 @@ void ImageTransferProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType
if (camera_data.format != current_config.origin_format) {
LOG_WARNING(Service_IRS, "Wrong Input format {} expected {}", camera_data.format,
current_config.origin_format);
- system.ApplicationMemory().ZeroBlock(transfer_memory,
- GetDataSize(current_config.trimming_format));
+ memory.ZeroBlock(transfer_memory, GetDataSize(current_config.trimming_format));
return;
}
if (current_config.origin_format > current_config.trimming_format) {
LOG_WARNING(Service_IRS, "Origin format {} is smaller than trimming format {}",
current_config.origin_format, current_config.trimming_format);
- system.ApplicationMemory().ZeroBlock(transfer_memory,
- GetDataSize(current_config.trimming_format));
+ memory.ZeroBlock(transfer_memory, GetDataSize(current_config.trimming_format));
return;
}
@@ -87,8 +88,7 @@ void ImageTransferProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType
"Trimming area ({}, {}, {}, {}) is outside of origin area ({}, {})",
current_config.trimming_start_x, current_config.trimming_start_y,
trimming_width, trimming_height, origin_width, origin_height);
- system.ApplicationMemory().ZeroBlock(transfer_memory,
- GetDataSize(current_config.trimming_format));
+ memory.ZeroBlock(transfer_memory, GetDataSize(current_config.trimming_format));
return;
}
@@ -102,8 +102,8 @@ void ImageTransferProcessor::OnControllerUpdate(Core::HID::ControllerTriggerType
}
}
- system.ApplicationMemory().WriteBlock(transfer_memory, window_data.data(),
- GetDataSize(current_config.trimming_format));
+ memory.WriteBlock(transfer_memory, window_data.data(),
+ GetDataSize(current_config.trimming_format));
if (!IsProcessorActive()) {
StartProcessor();
@@ -143,14 +143,19 @@ void ImageTransferProcessor::SetConfig(
npad_device->SetCameraFormat(current_config.origin_format);
}
-void ImageTransferProcessor::SetTransferMemoryAddress(Common::ProcessAddress t_mem) {
+void ImageTransferProcessor::SetTransferMemoryAddress(Common::ProcessAddress t_mem,
+ Kernel::KProcess* owner) {
transfer_memory = t_mem;
+ transfer_memory_owner = owner;
}
Core::IrSensor::ImageTransferProcessorState ImageTransferProcessor::GetState(
std::span<u8> data) const {
+ if (transfer_memory_owner == nullptr)
+ return processor_state;
+
const auto size = (std::min)(GetDataSize(current_config.trimming_format), data.size());
- system.ApplicationMemory().ReadBlock(transfer_memory, data.data(), size);
+ transfer_memory_owner->GetMemory().ReadBlock(transfer_memory, data.data(), size);
return processor_state;
}
diff --git a/src/hid_core/irsensor/image_transfer_processor.h b/src/hid_core/irsensor/image_transfer_processor.h
index df1c9d9208..f2f3dcba83 100644
--- a/src/hid_core/irsensor/image_transfer_processor.h
+++ b/src/hid_core/irsensor/image_transfer_processor.h
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
@@ -9,6 +12,10 @@
#include "hid_core/irsensor/irs_types.h"
#include "hid_core/irsensor/processor_base.h"
+namespace Kernel {
+class KProcess;
+}
+
namespace Core {
class System;
}
@@ -39,7 +46,7 @@ public:
void SetConfig(Core::IrSensor::PackedImageTransferProcessorExConfig config);
// Transfer memory where the image data will be stored
- void SetTransferMemoryAddress(Common::ProcessAddress t_mem);
+ void SetTransferMemoryAddress(Common::ProcessAddress t_mem, Kernel::KProcess* owner);
Core::IrSensor::ImageTransferProcessorState GetState(std::span<u8> data) const;
@@ -75,5 +82,6 @@ private:
Core::System& system;
Common::ProcessAddress transfer_memory{};
+ Kernel::KProcess* transfer_memory_owner{};
};
} // namespace Service::IRS
diff --git a/src/hid_core/resources/six_axis/seven_six_axis.cpp b/src/hid_core/resources/six_axis/seven_six_axis.cpp
index d84ef31e10..584e6d6505 100644
--- a/src/hid_core/resources/six_axis/seven_six_axis.cpp
+++ b/src/hid_core/resources/six_axis/seven_six_axis.cpp
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
@@ -6,6 +9,7 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/frontend/emu_window.h"
+#include "core/hle/kernel/k_process.h"
#include "core/memory.h"
#include "hid_core/frontend/emulated_console.h"
#include "hid_core/frontend/emulated_devices.h"
@@ -24,7 +28,7 @@ void SevenSixAxis::OnInit() {}
void SevenSixAxis::OnRelease() {}
void SevenSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
- if (!IsControllerActivated() || transfer_memory == 0) {
+ if (!IsControllerActivated() || transfer_memory == 0 || transfer_memory_owner == nullptr) {
seven_sixaxis_lifo.buffer_count = 0;
seven_sixaxis_lifo.buffer_tail = 0;
return;
@@ -51,12 +55,13 @@ void SevenSixAxis::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
};
seven_sixaxis_lifo.WriteNextEntry(next_seven_sixaxis_state);
- system.ApplicationMemory().WriteBlock(transfer_memory, &seven_sixaxis_lifo,
- sizeof(seven_sixaxis_lifo));
+ transfer_memory_owner->GetMemory().WriteBlock(transfer_memory, &seven_sixaxis_lifo,
+ sizeof(seven_sixaxis_lifo));
}
-void SevenSixAxis::SetTransferMemoryAddress(Common::ProcessAddress t_mem) {
+void SevenSixAxis::SetTransferMemoryAddress(Common::ProcessAddress t_mem, Kernel::KProcess* owner) {
transfer_memory = t_mem;
+ transfer_memory_owner = owner;
}
void SevenSixAxis::ResetTimestamp() {
diff --git a/src/hid_core/resources/six_axis/seven_six_axis.h b/src/hid_core/resources/six_axis/seven_six_axis.h
index 0a26c77c91..00afb8c00b 100644
--- a/src/hid_core/resources/six_axis/seven_six_axis.h
+++ b/src/hid_core/resources/six_axis/seven_six_axis.h
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
@@ -9,6 +12,10 @@
#include "hid_core/resources/controller_base.h"
#include "hid_core/resources/ring_lifo.h"
+namespace Kernel {
+class KProcess;
+}
+
namespace Core {
class System;
} // namespace Core
@@ -33,7 +40,7 @@ public:
void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
// Called on InitializeSevenSixAxisSensor
- void SetTransferMemoryAddress(Common::ProcessAddress t_mem);
+ void SetTransferMemoryAddress(Common::ProcessAddress t_mem, Kernel::KProcess* owner);
// Called on ResetSevenSixAxisSensorTimestamp
void ResetTimestamp();
@@ -58,6 +65,7 @@ private:
SevenSixAxisState next_seven_sixaxis_state{};
Common::ProcessAddress transfer_memory{};
+ Kernel::KProcess* transfer_memory_owner{};
Core::HID::EmulatedConsole* console = nullptr;
Core::System& system;
diff --git a/src/hid_core/resources/touch_screen/touch_screen_resource.cpp b/src/hid_core/resources/touch_screen/touch_screen_resource.cpp
index b095d6b458..51932678da 100644
--- a/src/hid_core/resources/touch_screen/touch_screen_resource.cpp
+++ b/src/hid_core/resources/touch_screen/touch_screen_resource.cpp
@@ -548,8 +548,18 @@ void TouchResource::OnTouchUpdate(s64 timestamp) {
}
auto& touch_shared = applet_data->shared_memory_format->touch_screen;
- StorePreviousTouchState(previous_touch_state, data.finger_map, current_touch_state, bool(applet_data->flag.enable_touchscreen));
- touch_shared.touch_screen_lifo.WriteNextEntry(current_touch_state);
+
+ if (applet_data->flag.enable_touchscreen) {
+ StorePreviousTouchState(previous_touch_state, data.finger_map, current_touch_state, true);
+ touch_shared.touch_screen_lifo.WriteNextEntry(current_touch_state);
+ } else {
+ TouchScreenState denied{};
+ denied.sampling_number = current_touch_state.sampling_number;
+ denied.entry_count = 0;
+ data.finger_map.finger_count = 0;
+ data.finger_map.finger_ids = {};
+ touch_shared.touch_screen_lifo.WriteNextEntry(denied);
+ }
}
}
}
diff --git a/src/qt_common/render/emu_thread.cpp b/src/qt_common/render/emu_thread.cpp
index 1b65867509..d5e5522837 100644
--- a/src/qt_common/render/emu_thread.cpp
+++ b/src/qt_common/render/emu_thread.cpp
@@ -51,7 +51,18 @@ void EmuThread::run() {
QtCommon::system->Run();
m_stopped.Reset();
- m_should_run_cv.wait(lk, stop_token, [&] { return !m_should_run; });
+ m_should_run_cv.wait(lk, stop_token, [&] {
+ return !m_should_run || m_pending_shader_cache_title.has_value();
+ });
+
+ if (m_should_run && m_pending_shader_cache_title.has_value()) {
+ const u64 program_id = *m_pending_shader_cache_title;
+ m_pending_shader_cache_title.reset();
+
+ lk.unlock();
+ this->ReloadDiskShaderCache(program_id);
+ lk.lock();
+ }
} else {
QtCommon::system->Pause();
m_stopped.Set();
@@ -67,6 +78,32 @@ void EmuThread::run() {
QtCommon::system->ShutdownMainProcess();
}
+void EmuThread::ReloadDiskShaderCache(u64 program_id) {
+ if (!Settings::values.use_disk_shader_cache.GetValue()) {
+ return;
+ }
+
+ LOG_INFO(Frontend, "Reloading disk shader cache for {:016X}", program_id);
+
+ auto& system = *QtCommon::system;
+ auto& gpu = system.GPU();
+
+ system.Pause();
+ gpu.WaitForIdle();
+ gpu.ObtainContext();
+
+ emit ShaderCacheReloadStarted();
+ emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
+ system.Renderer().ReadRasterizer()->LoadDiskResources(program_id, m_stop_source.get_token(),
+ [this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
+ emit LoadProgress(stage, value, total);
+ });
+ emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
+ emit ShaderCacheReloadFinished();
+
+ gpu.ReleaseContext();
+}
+
// Unlock while emitting signals so that the main thread can
// continue pumping events.
diff --git a/src/qt_common/render/emu_thread.h b/src/qt_common/render/emu_thread.h
index 92bae50979..f7d0376ac9 100644
--- a/src/qt_common/render/emu_thread.h
+++ b/src/qt_common/render/emu_thread.h
@@ -3,7 +3,10 @@
#pragma once
+#include <optional>
+
#include <QThread>
+#include "common/common_types.h"
#include "common/logging.h"
#include "common/thread.h"
@@ -63,9 +66,19 @@ public:
m_stop_source.request_stop();
}
+ /**
+ * Requests that the disk shader cache be reloaded for a different title.
+ */
+ void RequestDiskShaderCacheReload(u64 program_id) {
+ std::unique_lock run_lk{m_should_run_mutex};
+ m_pending_shader_cache_title = program_id;
+ m_should_run_cv.notify_one();
+ }
+
private:
void EmulationPaused(std::unique_lock<std::mutex>& lk);
void EmulationResumed(std::unique_lock<std::mutex>& lk);
+ void ReloadDiskShaderCache(u64 program_id);
private:
std::stop_source m_stop_source;
@@ -73,6 +86,7 @@ private:
std::condition_variable_any m_should_run_cv;
Common::Event m_stopped;
bool m_should_run{true};
+ std::optional<u64> m_pending_shader_cache_title;
signals:
/**
@@ -94,4 +108,7 @@ signals:
void DebugModeLeft();
void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
+
+ void ShaderCacheReloadStarted();
+ void ShaderCacheReloadFinished();
};
diff --git a/src/video_core/framebuffer_config.cpp b/src/video_core/framebuffer_config.cpp
index e28d41f84c..7145b02c07 100644
--- a/src/video_core/framebuffer_config.cpp
+++ b/src/video_core/framebuffer_config.cpp
@@ -1,11 +1,35 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <algorithm>
+
#include "common/assert.h"
#include "video_core/framebuffer_config.h"
namespace Tegra {
+std::span<const FramebufferConfig> FilterLayerStack(
+ std::span<const FramebufferConfig> layers, Service::Nvnflinger::LayerStackId stack,
+ std::vector<FramebufferConfig>& scratch) {
+ const u32 bit = Service::Nvnflinger::LayerStackBit(stack);
+
+ if (std::ranges::all_of(layers,
+ [bit](const auto& layer) { return (layer.layer_stack_mask & bit) != 0; }))
+ return layers;
+
+ scratch.clear();
+ for (const auto& layer : layers) {
+ if ((layer.layer_stack_mask & bit) != 0) {
+ scratch.push_back(layer);
+ }
+ }
+
+ return scratch;
+}
+
Common::Rectangle<f32> NormalizeCrop(const FramebufferConfig& framebuffer, u32 texture_width,
u32 texture_height) {
f32 left, top, right, bottom;
diff --git a/src/video_core/framebuffer_config.h b/src/video_core/framebuffer_config.h
index 8b2a49de5e..1fc70583f9 100644
--- a/src/video_core/framebuffer_config.h
+++ b/src/video_core/framebuffer_config.h
@@ -1,11 +1,18 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
+#include <span>
+#include <vector>
+
#include "common/common_types.h"
#include "common/math_util.h"
#include "core/hle/service/nvnflinger/buffer_transform_flags.h"
+#include "core/hle/service/nvnflinger/hwc_layer.h"
#include "core/hle/service/nvnflinger/pixel_format.h"
#include "core/hle/service/nvnflinger/ui/fence.h"
@@ -30,9 +37,17 @@ struct FramebufferConfig {
Service::android::BufferTransformFlags transform_flags{};
Common::Rectangle<int> crop_rect{};
BlendMode blending{};
+ u32 layer_stack_mask{Service::Nvnflinger::DefaultLayerStackMask};
};
Common::Rectangle<f32> NormalizeCrop(const FramebufferConfig& framebuffer, u32 texture_width,
u32 texture_height);
+/**
+ * Returns the subset of layers belonging to a stack.
+ */
+std::span<const FramebufferConfig> FilterLayerStack(std::span<const FramebufferConfig> layers,
+ Service::Nvnflinger::LayerStackId stack,
+ std::vector<FramebufferConfig>& scratch);
+
} // namespace Tegra
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index a44015c0dd..dbc7d43553 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -144,6 +144,12 @@ struct GPU::Impl {
sync_request_cv.wait(lck, [this, fence] { return CurrentSyncRequestFence() >= fence; });
}
+ void WaitForIdle() {
+ const u64 fence = RequestSyncOperation([] {});
+ gpu_thread.TickGPU(is_async);
+ WaitForSyncOperation(fence);
+ }
+
/// Tick pending requests within the GPU.
void TickWork() {
std::unique_lock lck{sync_request_mutex};
@@ -484,6 +490,10 @@ void GPU::NotifyShutdown() {
impl->NotifyShutdown();
}
+void GPU::WaitForIdle() {
+ impl->WaitForIdle();
+}
+
void GPU::ObtainContext() {
impl->ObtainContext();
}
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index 8d4a101fd4..75956ea199 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -168,6 +168,8 @@ public:
void WaitForSyncOperation(u64 fence);
+ void WaitForIdle();
+
/// Tick pending requests within the GPU.
void TickWork();
diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp
index 9b6fb69af3..e19a70ea01 100644
--- a/src/video_core/renderer_base.cpp
+++ b/src/video_core/renderer_base.cpp
@@ -36,7 +36,8 @@ bool RendererBase::IsScreenshotPending() const {
}
void RendererBase::RequestScreenshot(void* data, std::function<void(bool)> callback,
- const Layout::FramebufferLayout& layout) {
+ const Layout::FramebufferLayout& layout,
+ Service::Nvnflinger::LayerStackId layer_stack) {
if (renderer_settings.screenshot_requested) {
LOG_ERROR(Render, "A screenshot is already requested or in progress, ignoring the request");
return;
@@ -48,6 +49,7 @@ void RendererBase::RequestScreenshot(void* data, std::function<void(bool)> callb
renderer_settings.screenshot_bits = data;
renderer_settings.screenshot_complete_callback = async_callback;
renderer_settings.screenshot_framebuffer_layout = layout;
+ renderer_settings.screenshot_layer_stack = layer_stack;
renderer_settings.screenshot_requested = true;
}
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 67427f9372..46f8276074 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -26,6 +29,7 @@ struct RendererSettings {
void* screenshot_bits{};
std::function<void(bool)> screenshot_complete_callback;
Layout::FramebufferLayout screenshot_framebuffer_layout;
+ Service::Nvnflinger::LayerStackId screenshot_layer_stack{Service::Nvnflinger::LayerStackId::Default};
};
class RendererBase {
@@ -88,9 +92,11 @@ public:
/// Returns true if a screenshot is being processed
bool IsScreenshotPending() const;
- /// Request a screenshot of the next frame
+ /// Request a screenshot of the next frame.
void RequestScreenshot(void* data, std::function<void(bool)> callback,
- const Layout::FramebufferLayout& layout);
+ const Layout::FramebufferLayout& layout,
+ Service::Nvnflinger::LayerStackId layer_stack =
+ Service::Nvnflinger::LayerStackId::Default);
protected:
Core::Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 1c72147113..f9ec32e652 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -199,7 +199,10 @@ void RendererOpenGL::RenderScreenshot(std::span<const Tegra::FramebufferConfig>
return;
}
- RenderToBuffer(framebuffers, renderer_settings.screenshot_framebuffer_layout,
+ const auto screenshot_layers = Tegra::FilterLayerStack(
+ framebuffers, renderer_settings.screenshot_layer_stack, screenshot_layer_scratch);
+
+ RenderToBuffer(screenshot_layers, renderer_settings.screenshot_framebuffer_layout,
renderer_settings.screenshot_bits);
renderer_settings.screenshot_complete_callback(true);
@@ -208,6 +211,12 @@ void RendererOpenGL::RenderScreenshot(std::span<const Tegra::FramebufferConfig>
void RendererOpenGL::RenderAppletCaptureLayer(
std::span<const Tegra::FramebufferConfig> framebuffers) {
+ const auto capture_layers = Tegra::FilterLayerStack(
+ framebuffers, Service::Nvnflinger::LayerStackId::LastFrame, applet_capture_layers);
+
+ if (capture_layers.empty())
+ return;
+
GLint old_read_fb;
GLint old_draw_fb;
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old_read_fb);
@@ -217,7 +226,7 @@ void RendererOpenGL::RenderAppletCaptureLayer(
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
capture_renderbuffer.handle);
- blit_applet->DrawScreen(framebuffers, VideoCore::Capture::Layout, true);
+ blit_applet->DrawScreen(capture_layers, VideoCore::Capture::Layout, true);
glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb);
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index f694dc413a..5a6757e805 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
@@ -61,6 +61,9 @@ private:
void RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers);
void RenderAppletCaptureLayer(std::span<const Tegra::FramebufferConfig> framebuffers);
+ std::vector<Tegra::FramebufferConfig> applet_capture_layers;
+ std::vector<Tegra::FramebufferConfig> screenshot_layer_scratch;
+
Core::Frontend::EmuWindow& emu_window;
Tegra::MaxwellDeviceMemoryManager& device_memory;
Tegra::GPU& gpu;
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index d815278c85..6290e79d93 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -292,8 +292,11 @@ void RendererVulkan::RenderScreenshot(std::span<const Tegra::FramebufferConfig>
return;
}
+ const auto screenshot_layers = Tegra::FilterLayerStack(
+ framebuffers, renderer_settings.screenshot_layer_stack, screenshot_layer_scratch);
+
const auto& layout{renderer_settings.screenshot_framebuffer_layout};
- const auto dst_buffer = RenderToBuffer(framebuffers, layout, VK_FORMAT_B8G8R8A8_UNORM,
+ const auto dst_buffer = RenderToBuffer(screenshot_layers, layout, VK_FORMAT_B8G8R8A8_UNORM,
layout.width * layout.height * 4);
std::memcpy(renderer_settings.screenshot_bits, dst_buffer.Mapped().data(),
@@ -332,6 +335,12 @@ std::vector<u8> RendererVulkan::GetAppletCaptureBuffer() {
void RendererVulkan::RenderAppletCaptureLayer(
std::span<const Tegra::FramebufferConfig> framebuffers) {
+ const auto capture_layers = Tegra::FilterLayerStack(
+ framebuffers, Service::Nvnflinger::LayerStackId::LastFrame, applet_capture_layers);
+
+ if (capture_layers.empty())
+ return;
+
if (!applet_frame.image) {
applet_frame.image = CreateWrappedImage(memory_allocator, CaptureImageSize, CaptureFormat);
applet_frame.image_view = CreateWrappedImageView(device, applet_frame.image, CaptureFormat);
@@ -340,7 +349,7 @@ void RendererVulkan::RenderAppletCaptureLayer(
}
scheduler.RequestOutsideRenderPassOperationContext();
- blit_applet.DrawToFrame(device, rasterizer, &applet_frame, framebuffers, VideoCore::Capture::Layout, 1,
+ blit_applet.DrawToFrame(device, rasterizer, &applet_frame, capture_layers, VideoCore::Capture::Layout, 1,
CaptureFormat);
}
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index 5125fadcd7..87422dd935 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -75,6 +75,9 @@ private:
void RenderScreenshot(std::span<const Tegra::FramebufferConfig> framebuffers);
void RenderAppletCaptureLayer(std::span<const Tegra::FramebufferConfig> framebuffers);
+ std::vector<Tegra::FramebufferConfig> applet_capture_layers;
+ std::vector<Tegra::FramebufferConfig> screenshot_layer_scratch;
+
Tegra::MaxwellDeviceMemoryManager& device_memory;
Tegra::GPU& gpu;
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 130b5e4cc2..6e6120d1b1 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -592,6 +592,14 @@ void PipelineCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading
if (title_id == 0) {
return;
}
+ if (!pipeline_cache_filename.empty()) {
+ serialization_thread.WaitForRequests();
+ if (use_vulkan_pipeline_cache && !vulkan_pipeline_cache_filename.empty()) {
+ SerializeVulkanPipelineCache(vulkan_pipeline_cache_filename, vulkan_pipeline_cache,
+ CACHE_VERSION);
+ }
+ }
+
const auto shader_dir{Common::FS::GetEdenPath(Common::FS::EdenPath::ShaderDir)};
const auto base_dir{shader_dir / fmt::format("{:016x}", title_id)};
if (!Common::FS::CreateDir(shader_dir) || !Common::FS::CreateDir(base_dir)) {
diff --git a/src/yuzu/main_window.cpp b/src/yuzu/main_window.cpp
index f18a989255..934879ade5 100644
--- a/src/yuzu/main_window.cpp
+++ b/src/yuzu/main_window.cpp
@@ -139,6 +139,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/hle/service/am/frontend/applet_web_browser_types.h"
#include "core/file_sys/card_image.h"
+#include "core/file_sys/common_funcs.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/savedata_factory.h"
@@ -1967,12 +1968,37 @@ void MainWindow::BootGame(const QString& filename, Service::AM::FrontendAppletPa
render_window->Exit();
});
+ QtCommon::system->RegisterApplicationChangedCallback([this](u64 changed_program_id) {
+ if (QtCommon::emu_thread)
+ QtCommon::emu_thread->RequestDiskShaderCacheReload(changed_program_id);
+
+ QMetaObject::invokeMethod(
+ this, [this, changed_program_id] { this->OnApplicationChanged(changed_program_id); },
+ Qt::QueuedConnection);
+ });
+
connect(render_window, &GRenderWindow::Closed, this, &MainWindow::OnStopGame);
connect(render_window, &GRenderWindow::MouseActivity, this, &MainWindow::OnMouseActivity);
connect(QtCommon::emu_thread.get(), &EmuThread::LoadProgress, loading_screen,
&LoadingScreen::OnLoadProgress, Qt::QueuedConnection);
+ connect(
+ QtCommon::emu_thread.get(), &EmuThread::ShaderCacheReloadStarted, this,
+ [this] {
+ loading_screen->Prepare(QtCommon::system->GetAppLoader());
+ loading_screen->show();
+ render_window->hide();
+ },
+ Qt::QueuedConnection);
+
+ connect(
+ QtCommon::emu_thread.get(), &EmuThread::ShaderCacheReloadFinished, this,
+ [this] {
+ loading_screen->OnLoadComplete();
+ },
+ Qt::QueuedConnection);
+
// Update the GUI
UpdateStatusButtons();
if (ui->action_Single_Window_Mode->isChecked()) {
@@ -3061,12 +3087,31 @@ void MainWindow::OnLoadComplete() {
}
void MainWindow::OnExecuteProgram(std::size_t program_index) {
+ const u64 previous_program_id = QtCommon::system->GetApplicationProcessProgramID();
+
+ const auto current_path =
+ QString::fromStdString(QtCommon::system->GetCurrentApplicationFilePath());
+
+ LOG_INFO(Frontend, "ExecuteProgram requested, program_index={} previous_program_id={:016X}",
+ program_index, previous_program_id);
+
ShutdownGame();
auto params = ApplicationAppletParameters();
params.program_index = static_cast<s32>(program_index);
params.launch_type = Service::AM::LaunchType::ApplicationInitiated;
- BootGame(last_filename_booted, params);
+
+ if (previous_program_id > static_cast<u64>(Service::AM::AppletProgramId::MaxProgramId)) {
+ params.previous_program_index =
+ static_cast<s32>(previous_program_id - FileSys::GetBaseTitleID(previous_program_id));
+ params.program_id = previous_program_id;
+ }
+
+ const auto filename = current_path.isEmpty() ? last_filename_booted : current_path;
+
+ LOG_DEBUG(Frontend, "ExecuteProgram booting from path: {}", filename.toStdString());
+
+ BootGame(filename, params);
}
void MainWindow::OnExit() {
@@ -3995,6 +4040,41 @@ void MainWindow::OnEmulatorUpdateAvailable() {
}
#endif
+void MainWindow::OnApplicationChanged(u64 program_id) {
+ if (!emulation_running || program_id == 0)
+ return;
+
+ std::string title_name;
+ std::string title_version;
+
+ const FileSys::PatchManager pm(program_id, QtCommon::system->GetFileSystemController(),
+ QtCommon::system->GetContentProvider());
+ if (const auto metadata = pm.GetControlMetadata(); metadata.first != nullptr) {
+ title_version = metadata.first->GetVersionString();
+ title_name = metadata.first->GetApplicationName();
+ }
+ if (title_name.empty()) {
+ title_name = fmt::format("{:016X}", program_id);
+ }
+
+ if (const auto* process = QtCommon::system->Kernel().ApplicationProcess(); process != nullptr) {
+ const auto instruction_set_suffix = process->Is64Bit() ? tr("(64-bit)") : tr("(32-bit)");
+ title_name =
+ tr("%1 %2", "%1 is the title name. %2 indicates if the title is 64-bit or 32-bit")
+ .arg(QString::fromStdString(title_name), instruction_set_suffix)
+ .toStdString();
+ }
+
+ LOG_INFO(Frontend, "Now running: {:016X} | {} | {}", program_id, title_name, title_version);
+
+ UpdateWindowTitle(title_name, title_version,
+ QtCommon::system->GPU().Renderer().GetDeviceVendor());
+
+ // Switch to record playtime for the running program
+ if (play_time_manager)
+ play_time_manager->SetProgramId(program_id);
+}
+
void MainWindow::UpdateWindowTitle(std::string_view title_name, std::string_view title_version,
std::string_view gpu_vendor) {
static const std::string build_id = std::string{Common::g_build_id};
diff --git a/src/yuzu/main_window.h b/src/yuzu/main_window.h
index 2b50e3af91..3fe7ec620f 100644
--- a/src/yuzu/main_window.h
+++ b/src/yuzu/main_window.h
@@ -438,6 +438,7 @@ private:
ContentManager::InstallResult InstallNCA(const QString& filename);
void UpdateWindowTitle(std::string_view title_name = {}, std::string_view title_version = {},
std::string_view gpu_vendor = {});
+ void OnApplicationChanged(u64 program_id);
void UpdateDockedButton();
void UpdateAPIText();
void UpdateFilterText();
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 27b5746ef3..a161418289 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -6,6 +6,7 @@
#include <iostream>
#include <memory>
+#include <mutex>
#include <regex>
#include <string>
#include "common/settings_enums.h"
@@ -34,6 +35,8 @@
#include "input_common/main.h"
#include "network/network.h"
#include "sdl_config.h"
+#include "video_core/gpu.h"
+#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_base.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3.h"
#ifdef HAS_OPENGL
@@ -463,6 +466,30 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
// don't do anything, SDL3 already exists for us :D
state->system.RegisterExitCallback([] {});
+
+ // QLaunch launched applications (should) now reload shader cache.
+ static std::mutex shader_cache_reload_mutex;
+ state->system.RegisterApplicationChangedCallback([&state](u64 changed_program_id) {
+ if (!Settings::values.use_disk_shader_cache.GetValue()) {
+ return;
+ }
+
+ std::scoped_lock lk{shader_cache_reload_mutex};
+
+ LOG_INFO(Frontend, "Reloading disk shader cache for {:016X}", changed_program_id);
+
+ state->system.Pause();
+ state->system.GPU().WaitForIdle();
+ state->system.GPU().ObtainContext();
+
+ state->system.Renderer().ReadRasterizer()->LoadDiskResources(
+ changed_program_id, std::stop_token{},
+ [](VideoCore::LoadCallbackStage, size_t, size_t) {});
+
+ state->system.GPU().ReleaseContext();
+ state->system.Run();
+ });
+
void(state->system.Run());
if (state->system.DebuggerEnabled())
state->system.InitializeDebugger();