aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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();