From 3c6ef765af9b946240cedf053e593c0fc86a3f01 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Thu, 9 Oct 2025 21:37:27 +0200 Subject: [PATCH 01/10] revert [vk] Fast UBO: fix tracking, resize heuristics, add debug guard (#2695) (#2706) revert [vk] Fast UBO: fix tracking, resize heuristics, add debug guard (#2695) Well, stuff showed up after testing phase, that showed us this change break SMO and some mods after being merged directly into master, we will keep stuying why happens this and add a better handling later. Co-authored-by: Ribbit Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2695 Reviewed-by: CamilleLaVey Co-authored-by: Ribbit Co-committed-by: Ribbit Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2706 Co-authored-by: CamilleLaVey Co-committed-by: CamilleLaVey --- src/core/device_memory_manager.h | 6 --- src/core/device_memory_manager.inc | 26 ------------ src/video_core/buffer_cache/buffer_cache.h | 42 +++++++++---------- .../buffer_cache/buffer_cache_base.h | 5 +-- .../vk_staging_buffer_pool.cpp | 26 +++++------- .../renderer_vulkan/vk_staging_buffer_pool.h | 4 -- 6 files changed, 33 insertions(+), 76 deletions(-) diff --git a/src/core/device_memory_manager.h b/src/core/device_memory_manager.h index 192c6e5c01..6dcf7bb228 100644 --- a/src/core/device_memory_manager.h +++ b/src/core/device_memory_manager.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2025 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 @@ -112,9 +109,6 @@ public: void ReadBlock(DAddr address, void* dest_pointer, size_t size); void ReadBlockUnsafe(DAddr address, void* dest_pointer, size_t size); -#ifdef YUZU_DEBUG - bool ReadBlockFastChecked(DAddr address, void* dest_pointer, size_t size); -#endif void WriteBlock(DAddr address, const void* src_pointer, size_t size); void WriteBlockUnsafe(DAddr address, const void* src_pointer, size_t size); diff --git a/src/core/device_memory_manager.inc b/src/core/device_memory_manager.inc index 3629579c09..52dff5df9a 100644 --- a/src/core/device_memory_manager.inc +++ b/src/core/device_memory_manager.inc @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2025 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 @@ -470,29 +467,6 @@ void DeviceMemoryManager::ReadBlockUnsafe(DAddr address, void* dest_poin }); } -#ifdef YUZU_DEBUG -template -bool DeviceMemoryManager::ReadBlockFastChecked(DAddr address, void* dest_pointer, - size_t size) { - bool success = true; - WalkBlock( - address, size, - [&](size_t copy_amount, DAddr current_vaddr) { - LOG_CRITICAL(Render, "DeviceMemory OOB/unmapped: addr=0x{:x} size={}", current_vaddr, - size); - std::memset(dest_pointer, 0, copy_amount); - success = false; - }, - [&](size_t copy_amount, const u8* const src_ptr) { - std::memcpy(dest_pointer, src_ptr, copy_amount); - }, - [&](const std::size_t copy_amount) { - dest_pointer = static_cast(dest_pointer) + copy_amount; - }); - return success; -} -#endif - template void DeviceMemoryManager::WriteBlockUnsafe(DAddr address, const void* src_pointer, size_t size) { diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 388c8034c5..5223afe937 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -386,10 +386,11 @@ void BufferCache

::BindHostComputeBuffers() { template void BufferCache

::SetUniformBuffersState(const std::array& mask, const UniformBufferSizes* sizes) { - const bool mask_changed = channel_state->enabled_uniform_buffer_masks != mask; - if (mask_changed) { - channel_state->fast_bound_uniform_buffers.fill(0); - if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { + if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { + if (channel_state->enabled_uniform_buffer_masks != mask) { + if constexpr (IS_OPENGL) { + channel_state->fast_bound_uniform_buffers.fill(0); + } channel_state->dirty_uniform_buffers.fill(~u32{0}); channel_state->uniform_buffer_binding_sizes.fill({}); } @@ -805,7 +806,7 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 channel_state->uniform_buffer_binding_sizes[stage][binding_index] != size; if (should_fast_bind) { // We only have to bind when the currently bound buffer is not the fast version - channel_state->fast_bound_uniform_buffers[stage] |= 1u << binding_index; + channel_state->fast_bound_uniform_buffers[stage] |= 1U << binding_index; channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; runtime.BindFastUniformBuffer(stage, binding_index, size); } @@ -814,22 +815,13 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 return; } } - channel_state->fast_bound_uniform_buffers[stage] |= 1u << binding_index; - channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; + if constexpr (IS_OPENGL) { + channel_state->fast_bound_uniform_buffers[stage] |= 1U << binding_index; + channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; + } // Stream buffer path to avoid stalling on non-Nvidia drivers or Vulkan const std::span span = runtime.BindMappedUniformBuffer(stage, binding_index, size); -#ifdef YUZU_DEBUG - ASSERT(binding_index < NUM_GRAPHICS_UNIFORM_BUFFERS); - ASSERT(span.size() >= size && "UBO stream span too small"); - if (!device_memory.ReadBlockFastChecked(device_addr, span.data(), size)) { - LOG_CRITICAL(Render, "DeviceMemory OOB/unmapped: addr=0x{:x} size={}", device_addr, size); - channel_state->fast_bound_uniform_buffers[stage] &= ~(1u << binding_index); - ASSERT(false); - return; - } -#else device_memory.ReadBlockUnsafe(device_addr, span.data(), size); -#endif return; } // Classic cached path @@ -838,8 +830,7 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 } // Skip binding if it's not needed and if the bound buffer is not the fast version // This exists to avoid instances where the fast buffer is bound and a GPU write happens - const bool was_fast_bound = HasFastUniformBufferBound(stage, binding_index); - needs_bind |= was_fast_bound; + needs_bind |= HasFastUniformBufferBound(stage, binding_index); if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { needs_bind |= channel_state->uniform_buffer_binding_sizes[stage][binding_index] != size; } @@ -848,6 +839,9 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 } const u32 offset = buffer.Offset(device_addr); if constexpr (IS_OPENGL) { + // Fast buffer will be unbound + channel_state->fast_bound_uniform_buffers[stage] &= ~(1U << binding_index); + // Mark the index as dirty if offset doesn't match const bool is_copy_bind = offset != 0 && !runtime.SupportsNonZeroUniformOffset(); channel_state->dirty_uniform_buffers[stage] |= (is_copy_bind ? 1U : 0U) << index; @@ -861,7 +855,6 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 } else { runtime.BindUniformBuffer(buffer, offset, size); } - channel_state->fast_bound_uniform_buffers[stage] &= ~(1u << binding_index); } template @@ -1796,7 +1789,12 @@ std::span BufferCache

::ImmediateBuffer(size_t wanted_capacity) { template bool BufferCache

::HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept { - return ((channel_state->fast_bound_uniform_buffers[stage] >> binding_index) & 1u) != 0; + if constexpr (IS_OPENGL) { + return ((channel_state->fast_bound_uniform_buffers[stage] >> binding_index) & 1) != 0; + } else { + // Only OpenGL has fast uniform buffers + return false; + } } template diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 09631ffd83..486d19fb79 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -53,7 +53,6 @@ constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8; constexpr u32 NUM_STORAGE_BUFFERS = 16; constexpr u32 NUM_TEXTURE_BUFFERS = 32; constexpr u32 NUM_STAGES = 5; -static_assert(NUM_GRAPHICS_UNIFORM_BUFFERS <= 32, "fast bitmask must fit u32"); using UniformBufferSizes = std::array, NUM_STAGES>; using ComputeUniformBufferSizes = std::array; @@ -138,8 +137,8 @@ public: u32 written_compute_texture_buffers = 0; u32 image_compute_texture_buffers = 0; - std::array uniform_cache_hits{}; - std::array uniform_cache_shots{}; + std::array uniform_cache_hits{}; + std::array uniform_cache_shots{}; u32 uniform_buffer_skip_cache_size = DEFAULT_SKIP_CACHE_SIZE; diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp index 0fbe707b04..ecc4f77dc7 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp @@ -25,12 +25,12 @@ namespace { using namespace Common::Literals; -// Minimum alignment we want to enforce for the streaming ring -constexpr VkDeviceSize MIN_STREAM_ALIGNMENT = 256; +// Maximum potential alignment of a Vulkan buffer +constexpr VkDeviceSize MAX_ALIGNMENT = 256; // Stream buffer size in bytes constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB; -size_t GetStreamBufferSize(const Device& device, VkDeviceSize alignment) { +size_t GetStreamBufferSize(const Device& device) { VkDeviceSize size{0}; if (device.HasDebuggingToolAttached()) { bool found_heap = false; @@ -53,9 +53,8 @@ size_t GetStreamBufferSize(const Device& device, VkDeviceSize alignment) { // Clamp to the configured maximum, align up for safety, and ensure a sane minimum so // region_size (stream_buffer_size / NUM_SYNCS) never becomes zero. - const VkDeviceSize aligned = - (std::min)(Common::AlignUp(size, alignment), MAX_STREAM_BUFFER_SIZE); - const VkDeviceSize min_size = alignment * StagingBufferPool::NUM_SYNCS; + const VkDeviceSize aligned = (std::min)(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE); + const VkDeviceSize min_size = MAX_ALIGNMENT * StagingBufferPool::NUM_SYNCS; return static_cast((std::max)(aligned, min_size)); } } // Anonymous namespace @@ -63,10 +62,8 @@ size_t GetStreamBufferSize(const Device& device, VkDeviceSize alignment) { StagingBufferPool::StagingBufferPool(const Device& device_, MemoryAllocator& memory_allocator_, Scheduler& scheduler_) : device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_}, - stream_alignment{std::max(device_.GetUniformBufferAlignment(), - MIN_STREAM_ALIGNMENT)}, - stream_buffer_size{GetStreamBufferSize(device_, stream_alignment)}, - region_size{stream_buffer_size / StagingBufferPool::NUM_SYNCS} { + stream_buffer_size{GetStreamBufferSize(device)}, region_size{stream_buffer_size / + StagingBufferPool::NUM_SYNCS} { VkBufferCreateInfo stream_ci = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .pNext = nullptr, @@ -119,11 +116,10 @@ void StagingBufferPool::TickFrame() { } StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { - const size_t alignment = static_cast(stream_alignment); - const size_t aligned_size = Common::AlignUp(size, alignment); + const size_t aligned_size = Common::AlignUp(size, MAX_ALIGNMENT); const bool wraps = iterator + size >= stream_buffer_size; const size_t new_iterator = - wraps ? aligned_size : Common::AlignUp(iterator + size, alignment); + wraps ? aligned_size : Common::AlignUp(iterator + size, MAX_ALIGNMENT); const size_t begin_region = wraps ? 0 : Region(iterator); const size_t last_byte = new_iterator == 0 ? 0 : new_iterator - 1; const size_t end_region = (std::min)(Region(last_byte) + 1, NUM_SYNCS); @@ -149,7 +145,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { current_tick); used_iterator = 0; iterator = 0; - free_iterator = aligned_size; + free_iterator = size; const size_t head_last_byte = aligned_size == 0 ? 0 : aligned_size - 1; const size_t head_end_region = (std::min)(Region(head_last_byte) + 1, NUM_SYNCS); if (AreRegionsActive(0, head_end_region)) { @@ -164,7 +160,7 @@ StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { iterator = new_iterator; if (!wraps) { - free_iterator = (std::max)(free_iterator, offset + aligned_size); + free_iterator = (std::max)(free_iterator, offset + size); } return StagingBufferRef{ diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h index 5c40ca069f..f63a203272 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: Copyright 2025 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 @@ -105,7 +102,6 @@ private: MemoryAllocator& memory_allocator; Scheduler& scheduler; - VkDeviceSize stream_alignment; vk::Buffer stream_buffer; std::span stream_pointer; VkDeviceSize stream_buffer_size; From bfffafe68bea923ddc56bb1b7da73d549865dc11 Mon Sep 17 00:00:00 2001 From: MaranBr Date: Fri, 10 Oct 2025 01:36:55 +0200 Subject: [PATCH 02/10] [common] Change web offline applet default setting to HLE (#2705) This prevents some games from ignoring the disable web applet setting. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2705 Co-authored-by: MaranBr Co-committed-by: MaranBr --- src/common/settings.h | 2 +- src/yuzu/configuration/configure_debug.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/common/settings.h b/src/common/settings.h index c6b52f7ba3..dd9b03f28e 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -161,7 +161,7 @@ struct Values { Category::LibraryApplet}; Setting photo_viewer_applet_mode{ linkage, AppletMode::LLE, "photo_viewer_applet_mode", Category::LibraryApplet}; - Setting offline_web_applet_mode{linkage, AppletMode::LLE, "offline_web_applet_mode", + Setting offline_web_applet_mode{linkage, AppletMode::HLE, "offline_web_applet_mode", Category::LibraryApplet}; Setting login_share_applet_mode{linkage, AppletMode::HLE, "login_share_applet_mode", Category::LibraryApplet}; diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 18f629f639..b825348760 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -83,8 +83,7 @@ void ConfigureDebug::SetConfiguration() { #ifdef YUZU_USE_QT_WEB_ENGINE ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue()); #else - ui->disable_web_applet->setEnabled(false); - ui->disable_web_applet->setText(tr("Web applet not compiled")); + ui->disable_web_applet->setVisible(false); #endif } From b6241e4148d4a7bccedd3081707106edc58ff365 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Fri, 10 Oct 2025 01:55:43 +0200 Subject: [PATCH 03/10] revert [vk] StreamBuffer Changes (#2684) (#2707) revert [vk] StreamBuffer Changes (#2684) Streambuffer changes did broke stuff in other games that got out of our scope of testing, we're going to study this changes in the future for better graphic stability. Co-authored-by: Ribbit Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2684 Reviewed-by: MaranBr Reviewed-by: Lizzie Reviewed-by: CamilleLaVey Co-authored-by: Ribbit Co-committed-by: Ribbit Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2707 Reviewed-by: MaranBr Co-authored-by: CamilleLaVey Co-committed-by: CamilleLaVey --- .../vk_staging_buffer_pool.cpp | 60 +++++-------------- 1 file changed, 14 insertions(+), 46 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp index ecc4f77dc7..08513d1534 100644 --- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp @@ -33,29 +33,19 @@ constexpr VkDeviceSize MAX_STREAM_BUFFER_SIZE = 128_MiB; size_t GetStreamBufferSize(const Device& device) { VkDeviceSize size{0}; if (device.HasDebuggingToolAttached()) { - bool found_heap = false; - ForEachDeviceLocalHostVisibleHeap(device, [&size, &found_heap](size_t /*index*/, VkMemoryHeap& heap) { + ForEachDeviceLocalHostVisibleHeap(device, [&size](size_t index, VkMemoryHeap& heap) { size = (std::max)(size, heap.size); - found_heap = true; }); - // If no suitable heap was found fall back to the default cap to avoid creating a zero-sized stream buffer. - if (!found_heap) { - size = MAX_STREAM_BUFFER_SIZE; - } else if (size <= 256_MiB) { - // If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be - // loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue - // as the heap will be much larger. + // If rebar is not supported, cut the max heap size to 40%. This will allow 2 captures to be + // loaded at the same time in RenderDoc. If rebar is supported, this shouldn't be an issue + // as the heap will be much larger. + if (size <= 256_MiB) { size = size * 40 / 100; } } else { size = MAX_STREAM_BUFFER_SIZE; } - - // Clamp to the configured maximum, align up for safety, and ensure a sane minimum so - // region_size (stream_buffer_size / NUM_SYNCS) never becomes zero. - const VkDeviceSize aligned = (std::min)(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE); - const VkDeviceSize min_size = MAX_ALIGNMENT * StagingBufferPool::NUM_SYNCS; - return static_cast((std::max)(aligned, min_size)); + return (std::min)(Common::AlignUp(size, MAX_ALIGNMENT), MAX_STREAM_BUFFER_SIZE); } } // Anonymous namespace @@ -116,53 +106,31 @@ void StagingBufferPool::TickFrame() { } StagingBufferRef StagingBufferPool::GetStreamBuffer(size_t size) { - const size_t aligned_size = Common::AlignUp(size, MAX_ALIGNMENT); - const bool wraps = iterator + size >= stream_buffer_size; - const size_t new_iterator = - wraps ? aligned_size : Common::AlignUp(iterator + size, MAX_ALIGNMENT); - const size_t begin_region = wraps ? 0 : Region(iterator); - const size_t last_byte = new_iterator == 0 ? 0 : new_iterator - 1; - const size_t end_region = (std::min)(Region(last_byte) + 1, NUM_SYNCS); - const size_t guard_begin = (std::min)(Region(free_iterator) + 1, NUM_SYNCS); - - if (!wraps) { - if (guard_begin < end_region && AreRegionsActive(guard_begin, end_region)) { - // Avoid waiting for the previous usages to be free - return GetStagingBuffer(size, MemoryUsage::Upload); - } - } else if (guard_begin < NUM_SYNCS && AreRegionsActive(guard_begin, NUM_SYNCS)) { + if (AreRegionsActive(Region(free_iterator) + 1, + (std::min)(Region(iterator + size) + 1, NUM_SYNCS))) { // Avoid waiting for the previous usages to be free return GetStagingBuffer(size, MemoryUsage::Upload); } - const u64 current_tick = scheduler.CurrentTick(); std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + Region(iterator), current_tick); used_iterator = iterator; + free_iterator = (std::max)(free_iterator, iterator + size); - if (wraps) { + if (iterator + size >= stream_buffer_size) { std::fill(sync_ticks.begin() + Region(used_iterator), sync_ticks.begin() + NUM_SYNCS, current_tick); used_iterator = 0; iterator = 0; free_iterator = size; - const size_t head_last_byte = aligned_size == 0 ? 0 : aligned_size - 1; - const size_t head_end_region = (std::min)(Region(head_last_byte) + 1, NUM_SYNCS); - if (AreRegionsActive(0, head_end_region)) { + + if (AreRegionsActive(0, Region(size) + 1)) { // Avoid waiting for the previous usages to be free return GetStagingBuffer(size, MemoryUsage::Upload); } } - - std::fill(sync_ticks.begin() + begin_region, sync_ticks.begin() + end_region, current_tick); - - const size_t offset = wraps ? 0 : iterator; - iterator = new_iterator; - - if (!wraps) { - free_iterator = (std::max)(free_iterator, offset + size); - } - + const size_t offset = iterator; + iterator = Common::AlignUp(iterator + size, MAX_ALIGNMENT); return StagingBufferRef{ .buffer = *stream_buffer, .offset = static_cast(offset), From 3656253262fd1c22e5a1ab43ae373173e571e11d Mon Sep 17 00:00:00 2001 From: crueter Date: Fri, 10 Oct 2025 05:59:31 +0200 Subject: [PATCH 04/10] [acc] do not consider system profile as orphaned (#2708) Profile 00000000000000000000000000000000 is apparently needed for acnh, etc Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2708 --- src/core/hle/service/acc/profile_manager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index 4a892f7c65..12ea5f7aa1 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -509,6 +509,9 @@ std::vector ProfileManager::FindOrphanedProfiles() good_uuids.emplace_back(uuid_string); } + // used for acnh, etc + good_uuids.emplace_back("00000000000000000000000000000000"); + // TODO: fetch save_id programmatically const auto path = Common::FS::GetEdenPath(Common::FS::EdenPath::NANDDir) / "user/save/0000000000000000"; From 776958c79d2aa3e3e1c8545a700e8fe2bb3ca94a Mon Sep 17 00:00:00 2001 From: Shinmegumi Date: Fri, 10 Oct 2025 19:24:20 +0200 Subject: [PATCH 05/10] [vk] Introduce Ring Buffers for Uniform Buffer (#2698) Create 3 ring buffers which rotates between buffers each frame to avoid GPU/CPU conflicts BindMappedUniformBuffer first tries to allocate from the ring buffer and falls back to staging pool only if allocation is too large. Note to testers:- please test the performance since it is primarily a performance optimization and also look for visual bugs. Co-authored-by: wildcard Co-authored-by: MaranBr Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2698 Reviewed-by: MaranBr Co-authored-by: Shinmegumi Co-committed-by: Shinmegumi --- .../renderer_vulkan/vk_buffer_cache.cpp | 42 +++++++++++++++++++ .../renderer_vulkan/vk_buffer_cache.h | 32 +++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp index 55565e3d79..0f0d27c6f7 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -337,6 +337,11 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m uint8_pass = std::make_unique(device, scheduler, descriptor_pool, staging_pool, compute_pass_descriptor_queue); } + const u32 ubo_align = static_cast( + device.GetUniformBufferAlignment() //check if the device has it + ); + // add the ability to change the size in settings in future + uniform_ring.Init(device, memory_allocator, 8 * 1024 * 1024 /* 8 MiB */, ubo_align ? ubo_align : 256); quad_array_index_buffer = std::make_shared(device_, memory_allocator_, scheduler_, staging_pool_); quad_strip_index_buffer = std::make_shared(device_, memory_allocator_, @@ -355,6 +360,42 @@ void BufferCacheRuntime::FreeDeferredStagingBuffer(StagingBufferRef& ref) { staging_pool.FreeDeferred(ref); } +void BufferCacheRuntime::UniformRing::Init(const Device& device, + MemoryAllocator& alloc, + u64 bytes, u32 alignment) { + for (size_t i = 0; i < NUM_FRAMES; ++i) { + VkBufferCreateInfo ci{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = bytes, + .usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; + buffers[i] = alloc.CreateBuffer(ci, MemoryUsage::Upload); + mapped[i] = buffers[i].Mapped().data(); + } + size = bytes; + align = alignment ? alignment : 256; + head = 0; + current_frame = 0; +} + +std::span BufferCacheRuntime::UniformRing::Alloc(u32 bytes, u32& out_offset) { + const u64 aligned = Common::AlignUp(head, static_cast(align)); + u64 end = aligned + bytes; + + if (end > size) { + return {}; // Fallback to staging pool + } + + out_offset = static_cast(aligned); + head = end; + return {mapped[current_frame] + out_offset, bytes}; +} + u64 BufferCacheRuntime::GetDeviceLocalMemory() const { return device.GetDeviceLocalMemory(); } @@ -375,6 +416,7 @@ void BufferCacheRuntime::TickFrame(Common::SlotVector& slot_buffers) noe for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) { it->ResetUsageTracking(); } + uniform_ring.BeginFrame(); } void BufferCacheRuntime::Finish() { diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h index efe960258c..86bce01596 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.h +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -124,8 +127,15 @@ public: void BindTransformFeedbackBuffers(VideoCommon::HostBindings& bindings); - std::span BindMappedUniformBuffer([[maybe_unused]] size_t stage, - [[maybe_unused]] u32 binding_index, u32 size) { + std::span BindMappedUniformBuffer([[maybe_unused]] size_t /*stage*/, + [[maybe_unused]] u32 /*binding_index*/, + u32 size) { + u32 offset = 0; + if (auto span = uniform_ring.Alloc(size, offset); !span.empty()) { + BindBuffer(*uniform_ring.buffers[uniform_ring.current_frame], offset, size); + return span; + } + // Fallback for giant requests const StagingBufferRef ref = staging_pool.Request(size, MemoryUsage::Upload); BindBuffer(ref.buffer, static_cast(ref.offset), size); return ref.mapped_span; @@ -153,6 +163,24 @@ private: void ReserveNullBuffer(); vk::Buffer CreateNullBuffer(); + struct UniformRing { + static constexpr size_t NUM_FRAMES = 3; + std::array buffers{}; + std::array mapped{}; + u64 size = 0; + u64 head = 0; + u32 align = 256; + size_t current_frame = 0; + + void Init(const Device& device, MemoryAllocator& alloc, u64 bytes, u32 alignment); + void BeginFrame() { + current_frame = (current_frame + 1) % NUM_FRAMES; + head = 0; + } + std::span Alloc(u32 bytes, u32& out_offset); + }; + UniformRing uniform_ring; + const Device& device; MemoryAllocator& memory_allocator; Scheduler& scheduler; From 8a017951aa81069ea3ea9140c5b13ca7834e9363 Mon Sep 17 00:00:00 2001 From: Marcin Serwin Date: Fri, 10 Oct 2025 22:33:15 +0200 Subject: [PATCH 06/10] [qt_common] fix building with Qt 6.10 (#2713) Qt old style include variables are deprecated in Qt, see , and Qt 6.10 stopped exporting them after . Signed-off-by: Marcin Serwin Co-authored-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2713 Co-authored-by: Marcin Serwin Co-committed-by: Marcin Serwin --- src/qt_common/CMakeLists.txt | 8 ++++++-- src/yuzu/CMakeLists.txt | 8 -------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/qt_common/CMakeLists.txt b/src/qt_common/CMakeLists.txt index aa931f113e..9b3a8fdb8d 100644 --- a/src/qt_common/CMakeLists.txt +++ b/src/qt_common/CMakeLists.txt @@ -45,6 +45,10 @@ if (NOT APPLE AND ENABLE_OPENGL) target_compile_definitions(qt_common PUBLIC HAS_OPENGL) endif() -if (NOT WIN32) - target_include_directories(qt_common PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) +if (UNIX AND NOT APPLE) + if (TARGET Qt6::GuiPrivate) + target_link_libraries(qt_common PRIVATE Qt6::GuiPrivate) + else() + target_include_directories(yuzu PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) + endif() endif() diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index c03f7a3abf..a8e3953716 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -393,16 +393,8 @@ target_link_libraries(yuzu PRIVATE common core input_common frontend_common netw target_link_libraries(yuzu PRIVATE Boost::headers glad Qt6::Widgets) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) -if (NOT WIN32) - target_include_directories(yuzu PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) -endif() - if (UNIX AND NOT APPLE) target_link_libraries(yuzu PRIVATE Qt6::DBus) - - if (TARGET Qt6::GuiPrivate) - target_link_libraries(yuzu PRIVATE Qt6::GuiPrivate) - endif() endif() target_compile_definitions(yuzu PRIVATE From 973a65c4c545fffa8ecf46cc01b453338ac3cf5b Mon Sep 17 00:00:00 2001 From: crueter Date: Sat, 11 Oct 2025 04:31:14 +0200 Subject: [PATCH 07/10] [qt_common] fix typo (#2715) Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2715 --- src/qt_common/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt_common/CMakeLists.txt b/src/qt_common/CMakeLists.txt index 9b3a8fdb8d..2c47a37526 100644 --- a/src/qt_common/CMakeLists.txt +++ b/src/qt_common/CMakeLists.txt @@ -49,6 +49,6 @@ if (UNIX AND NOT APPLE) if (TARGET Qt6::GuiPrivate) target_link_libraries(qt_common PRIVATE Qt6::GuiPrivate) else() - target_include_directories(yuzu PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) + target_include_directories(qt_common PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) endif() endif() From 91493fa39bb40288b2005a705fbc27713ca3e906 Mon Sep 17 00:00:00 2001 From: MaranBr Date: Sat, 11 Oct 2025 06:34:21 +0200 Subject: [PATCH 08/10] [vk] Fast UBO: fix tracking (#2712) Fixes or mitigates memory errors in TOTK and possibly other games as well. Credit: Ribbit Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2712 Reviewed-by: Lizzie Reviewed-by: crueter Co-authored-by: MaranBr Co-committed-by: MaranBr --- src/video_core/buffer_cache/buffer_cache.h | 27 ++++++------------- .../buffer_cache/buffer_cache_base.h | 6 +++-- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 5223afe937..9eaa322c9c 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -386,11 +386,9 @@ void BufferCache

::BindHostComputeBuffers() { template void BufferCache

::SetUniformBuffersState(const std::array& mask, const UniformBufferSizes* sizes) { - if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { - if (channel_state->enabled_uniform_buffer_masks != mask) { - if constexpr (IS_OPENGL) { - channel_state->fast_bound_uniform_buffers.fill(0); - } + if (channel_state->enabled_uniform_buffer_masks != mask) { + channel_state->fast_bound_uniform_buffers.fill(0); + if constexpr (HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS) { channel_state->dirty_uniform_buffers.fill(~u32{0}); channel_state->uniform_buffer_binding_sizes.fill({}); } @@ -806,7 +804,7 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 channel_state->uniform_buffer_binding_sizes[stage][binding_index] != size; if (should_fast_bind) { // We only have to bind when the currently bound buffer is not the fast version - channel_state->fast_bound_uniform_buffers[stage] |= 1U << binding_index; + channel_state->fast_bound_uniform_buffers[stage] |= 1u << binding_index; channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; runtime.BindFastUniformBuffer(stage, binding_index, size); } @@ -815,10 +813,8 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 return; } } - if constexpr (IS_OPENGL) { - channel_state->fast_bound_uniform_buffers[stage] |= 1U << binding_index; - channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; - } + channel_state->fast_bound_uniform_buffers[stage] |= 1u << binding_index; + channel_state->uniform_buffer_binding_sizes[stage][binding_index] = size; // Stream buffer path to avoid stalling on non-Nvidia drivers or Vulkan const std::span span = runtime.BindMappedUniformBuffer(stage, binding_index, size); device_memory.ReadBlockUnsafe(device_addr, span.data(), size); @@ -839,9 +835,6 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 } const u32 offset = buffer.Offset(device_addr); if constexpr (IS_OPENGL) { - // Fast buffer will be unbound - channel_state->fast_bound_uniform_buffers[stage] &= ~(1U << binding_index); - // Mark the index as dirty if offset doesn't match const bool is_copy_bind = offset != 0 && !runtime.SupportsNonZeroUniformOffset(); channel_state->dirty_uniform_buffers[stage] |= (is_copy_bind ? 1U : 0U) << index; @@ -855,6 +848,7 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 } else { runtime.BindUniformBuffer(buffer, offset, size); } + channel_state->fast_bound_uniform_buffers[stage] &= ~(1u << binding_index); } template @@ -1789,12 +1783,7 @@ std::span BufferCache

::ImmediateBuffer(size_t wanted_capacity) { template bool BufferCache

::HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept { - if constexpr (IS_OPENGL) { - return ((channel_state->fast_bound_uniform_buffers[stage] >> binding_index) & 1) != 0; - } else { - // Only OpenGL has fast uniform buffers - return false; - } + return ((channel_state->fast_bound_uniform_buffers[stage] >> binding_index) & 1u) != 0; } template diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h index 486d19fb79..1b551931a4 100644 --- a/src/video_core/buffer_cache/buffer_cache_base.h +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -54,6 +54,8 @@ constexpr u32 NUM_STORAGE_BUFFERS = 16; constexpr u32 NUM_TEXTURE_BUFFERS = 32; constexpr u32 NUM_STAGES = 5; +static_assert(NUM_GRAPHICS_UNIFORM_BUFFERS <= 32, "fast bitmask must fit u32"); + using UniformBufferSizes = std::array, NUM_STAGES>; using ComputeUniformBufferSizes = std::array; @@ -137,8 +139,8 @@ public: u32 written_compute_texture_buffers = 0; u32 image_compute_texture_buffers = 0; - std::array uniform_cache_hits{}; - std::array uniform_cache_shots{}; + std::array uniform_cache_hits{}; + std::array uniform_cache_shots{}; u32 uniform_buffer_skip_cache_size = DEFAULT_SKIP_CACHE_SIZE; From 1e1b8ad33face3f3409aec50818d2b4fda8e6a0d Mon Sep 17 00:00:00 2001 From: MaranBr Date: Sat, 11 Oct 2025 14:45:14 +0200 Subject: [PATCH 09/10] [common] Properly skip Custom Web Applet if YUZU_USE_QT_WEB_ENGINE is not defined (#2717) This restores the Offline Web Applet LLE setting as default and properly skip Custom Web Applet if YUZU_USE_QT_WEB_ENGINE is not defined preventing crashes. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2717 Reviewed-by: crueter Reviewed-by: Maufeat Co-authored-by: MaranBr Co-committed-by: MaranBr --- src/common/settings.h | 2 +- .../am/frontend/applet_web_browser.cpp | 84 +++++++++++-------- 2 files changed, 49 insertions(+), 37 deletions(-) diff --git a/src/common/settings.h b/src/common/settings.h index dd9b03f28e..c6b52f7ba3 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -161,7 +161,7 @@ struct Values { Category::LibraryApplet}; Setting photo_viewer_applet_mode{ linkage, AppletMode::LLE, "photo_viewer_applet_mode", Category::LibraryApplet}; - Setting offline_web_applet_mode{linkage, AppletMode::HLE, "offline_web_applet_mode", + Setting offline_web_applet_mode{linkage, AppletMode::LLE, "offline_web_applet_mode", Category::LibraryApplet}; Setting login_share_applet_mode{linkage, AppletMode::HLE, "login_share_applet_mode", Category::LibraryApplet}; diff --git a/src/core/hle/service/am/frontend/applet_web_browser.cpp b/src/core/hle/service/am/frontend/applet_web_browser.cpp index e35a1daa1e..53fe867b9e 100644 --- a/src/core/hle/service/am/frontend/applet_web_browser.cpp +++ b/src/core/hle/service/am/frontend/applet_web_browser.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -68,42 +71,6 @@ std::string ResolveURL(const std::string& url) { return url.substr(0, index) + "lp1" + url.substr(index + 1); } -WebArgInputTLVMap ReadWebArgs(const std::vector& web_arg, WebArgHeader& web_arg_header) { - std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader)); - - if (web_arg.size() == sizeof(WebArgHeader)) { - return {}; - } - - WebArgInputTLVMap input_tlv_map; - - u64 current_offset = sizeof(WebArgHeader); - - for (std::size_t i = 0; i < web_arg_header.total_tlv_entries; ++i) { - if (web_arg.size() < current_offset + sizeof(WebArgInputTLV)) { - return input_tlv_map; - } - - WebArgInputTLV input_tlv; - std::memcpy(&input_tlv, web_arg.data() + current_offset, sizeof(WebArgInputTLV)); - - current_offset += sizeof(WebArgInputTLV); - - if (web_arg.size() < current_offset + input_tlv.arg_data_size) { - return input_tlv_map; - } - - std::vector data(input_tlv.arg_data_size); - std::memcpy(data.data(), web_arg.data() + current_offset, input_tlv.arg_data_size); - - current_offset += input_tlv.arg_data_size; - - input_tlv_map.insert_or_assign(input_tlv.input_tlv_type, std::move(data)); - } - - return input_tlv_map; -} - FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id, FileSys::ContentRecordType nca_type) { if (nca_type == FileSys::ContentRecordType::Data) { @@ -144,6 +111,43 @@ FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id, } } +#ifdef YUZU_USE_QT_WEB_ENGINE +WebArgInputTLVMap ReadWebArgs(const std::vector& web_arg, WebArgHeader& web_arg_header) { + std::memcpy(&web_arg_header, web_arg.data(), sizeof(WebArgHeader)); + + if (web_arg.size() == sizeof(WebArgHeader)) { + return {}; + } + + WebArgInputTLVMap input_tlv_map; + + u64 current_offset = sizeof(WebArgHeader); + + for (std::size_t i = 0; i < web_arg_header.total_tlv_entries; ++i) { + if (web_arg.size() < current_offset + sizeof(WebArgInputTLV)) { + return input_tlv_map; + } + + WebArgInputTLV input_tlv; + std::memcpy(&input_tlv, web_arg.data() + current_offset, sizeof(WebArgInputTLV)); + + current_offset += sizeof(WebArgInputTLV); + + if (web_arg.size() < current_offset + input_tlv.arg_data_size) { + return input_tlv_map; + } + + std::vector data(input_tlv.arg_data_size); + std::memcpy(data.data(), web_arg.data() + current_offset, input_tlv.arg_data_size); + + current_offset += input_tlv.arg_data_size; + + input_tlv_map.insert_or_assign(input_tlv.input_tlv_type, std::move(data)); + } + + return input_tlv_map; +} + void ExtractSharedFonts(Core::System& system) { static constexpr std::array DECRYPTED_SHARED_FONTS{ "FontStandard.ttf", @@ -221,6 +225,7 @@ void ExtractSharedFonts(Core::System& system) { FileSys::VfsRawCopy(decrypted_font, out_file); } } +#endif } // namespace @@ -232,6 +237,7 @@ WebBrowser::WebBrowser(Core::System& system_, std::shared_ptr applet_, WebBrowser::~WebBrowser() = default; void WebBrowser::Initialize() { +#ifdef YUZU_USE_QT_WEB_ENGINE FrontendApplet::Initialize(); LOG_INFO(Service_AM, "Initializing Web Browser Applet."); @@ -284,6 +290,7 @@ void WebBrowser::Initialize() { ASSERT_MSG(false, "Invalid ShimKind={}", web_arg_header.shim_kind); break; } +#endif } Result WebBrowser::GetStatus() const { @@ -295,6 +302,7 @@ void WebBrowser::ExecuteInteractive() { } void WebBrowser::Execute() { +#ifdef YUZU_USE_QT_WEB_ENGINE switch (web_arg_header.shim_kind) { case ShimKind::Shop: ExecuteShop(); @@ -322,6 +330,10 @@ void WebBrowser::Execute() { WebBrowserExit(WebExitReason::EndButtonPressed); break; } +#else + LOG_INFO(Service_AM, "Web Browser Applet disabled, skipping."); + WebBrowserExit(WebExitReason::EndButtonPressed); +#endif } void WebBrowser::ExtractOfflineRomFS() { From a3ef2cc1838a007a2f424e88212aa6f1eb93eab3 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 Oct 2025 17:03:14 +0200 Subject: [PATCH 10/10] [audio_core/hid] Audio REV12+15 support + HID fixes (#2719) This fixes newer updates / games. Implements partial audio rev15, rev13, rev12 and HID issues on SDK20+ games. Credits to LotP (Ryubing) and Zephyron (Citron) for their research and implementation. Co-authored-by: Zephyron Co-authored-by: Shinmegumi Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2719 Reviewed-by: MaranBr Reviewed-by: CamilleLaVey Co-authored-by: unknown Co-committed-by: unknown --- src/audio_core/common/feature_support.h | 10 +- .../renderer/behavior/behavior_info.cpp | 16 +++ .../renderer/behavior/behavior_info.h | 35 +++++ .../renderer/behavior/info_updater.cpp | 129 +++++++++++++++++- .../renderer/command/command_buffer.cpp | 17 +++ .../renderer/command/effect/biquad_filter.cpp | 44 +++++- .../renderer/command/effect/biquad_filter.h | 21 +++ .../effect/multi_tap_biquad_filter.cpp | 13 +- .../command/effect/multi_tap_biquad_filter.h | 7 + .../renderer/splitter/splitter_context.cpp | 115 ++++++++++++++-- .../renderer/splitter/splitter_context.h | 12 +- .../splitter/splitter_destinations_data.cpp | 13 ++ .../splitter/splitter_destinations_data.h | 77 ++++++++++- src/audio_core/renderer/voice/voice_info.h | 55 ++++++++ .../am/service/library_applet_creator.cpp | 30 ++++ .../am/service/library_applet_creator.h | 6 + .../ns/application_manager_interface.cpp | 39 ++++++ .../ns/application_manager_interface.h | 6 + src/core/hle/service/ns/ns_types.h | 16 ++- ...nly_application_control_data_interface.cpp | 34 +++++ ..._only_application_control_data_interface.h | 10 +- ...read_only_application_record_interface.cpp | 17 +++ .../read_only_application_record_interface.h | 7 + .../service/pctl/parental_control_service.cpp | 1 + src/hid_core/resources/npad/npad_types.h | 14 ++ src/hid_core/resources/ring_lifo.h | 6 +- src/hid_core/resources/shared_memory_format.h | 13 +- 27 files changed, 727 insertions(+), 36 deletions(-) diff --git a/src/audio_core/common/feature_support.h b/src/audio_core/common/feature_support.h index 91d6991416..4585be45ef 100644 --- a/src/audio_core/common/feature_support.h +++ b/src/audio_core/common/feature_support.h @@ -16,7 +16,7 @@ #include namespace AudioCore { -constexpr u32 CurrentRevision = 16; +constexpr u32 CurrentRevision = 15; enum class SupportTags { CommandProcessingTimeEstimatorVersion4, @@ -47,6 +47,10 @@ enum class SupportTags { DelayChannelMappingChange, ReverbChannelMappingChange, I3dl2ReverbChannelMappingChange, + SplitterPrevVolumeReset, + SplitterBiquadFilterParameter, + SplitterDestinationV2b, + VoiceInParameterV2, // Not a real tag, just here to get the count. Size @@ -91,6 +95,10 @@ constexpr bool CheckFeatureSupported(SupportTags tag, u32 user_revision) { {SupportTags::DelayChannelMappingChange, 11}, {SupportTags::ReverbChannelMappingChange, 11}, {SupportTags::I3dl2ReverbChannelMappingChange, 11}, + {SupportTags::SplitterBiquadFilterParameter, 12}, + {SupportTags::SplitterPrevVolumeReset, 13}, + {SupportTags::SplitterDestinationV2b, 15}, + {SupportTags::VoiceInParameterV2, 15}, }}; const auto& feature = diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp index d0df1f29de..63b80c503a 100644 --- a/src/audio_core/renderer/behavior/behavior_info.cpp +++ b/src/audio_core/renderer/behavior/behavior_info.cpp @@ -193,4 +193,20 @@ bool BehaviorInfo::IsI3dl2ReverbChannelMappingChanged() const { return CheckFeatureSupported(SupportTags::I3dl2ReverbChannelMappingChange, user_revision); } +bool BehaviorInfo::IsSplitterPrevVolumeResetSupported() const { + return CheckFeatureSupported(SupportTags::SplitterPrevVolumeReset, user_revision); +} + +bool BehaviorInfo::IsSplitterDestinationV2bSupported() const { + return CheckFeatureSupported(SupportTags::SplitterDestinationV2b, user_revision); +} + +bool BehaviorInfo::IsVoiceInParameterV2Supported() const { + return CheckFeatureSupported(SupportTags::VoiceInParameterV2, user_revision); +} + +bool BehaviorInfo::IsBiquadFilterParameterForSplitterEnabled() const { + return CheckFeatureSupported(SupportTags::SplitterBiquadFilterParameter, user_revision); +} + } // namespace AudioCore::Renderer diff --git a/src/audio_core/renderer/behavior/behavior_info.h b/src/audio_core/renderer/behavior/behavior_info.h index a4958857a4..757f163c5a 100644 --- a/src/audio_core/renderer/behavior/behavior_info.h +++ b/src/audio_core/renderer/behavior/behavior_info.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -361,6 +364,38 @@ public: */ bool IsI3dl2ReverbChannelMappingChanged() const; + /** + * Check if explicit previous mix volume reset is supported for splitters. + * This allows splitters to explicitly reset their previous mix volumes instead of + * doing so implicitly on first use. + * + * @return True if supported, otherwise false. + */ + bool IsSplitterPrevVolumeResetSupported() const; + + /** + * Check if splitter destination v2b parameter format is supported (revision 15+). + * This uses the extended parameter format with biquad filter fields. + * + * @return True if supported, otherwise false. + */ + bool IsSplitterDestinationV2bSupported() const; + + /** + * Check if voice input parameter v2 format is supported (revision 15+). + * This uses the extended parameter format with float biquad filters. + * + * @return True if supported, otherwise false. + */ + bool IsVoiceInParameterV2Supported() const; + + /** + * Check if splitter destinations can carry biquad filter parameters (revision 12+). + * + * @return True if supported, otherwise false. + */ + bool IsBiquadFilterParameterForSplitterEnabled() const; + /// Host version u32 process_revision; /// User version diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp index 20f6cda3a2..39bbc91ded 100644 --- a/src/audio_core/renderer/behavior/info_updater.cpp +++ b/src/audio_core/renderer/behavior/info_updater.cpp @@ -64,8 +64,6 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, const PoolMapper pool_mapper(process_handle, memory_pools, memory_pool_count, behaviour.IsMemoryForceMappingEnabled()); const auto voice_count{voice_context.GetCount()}; - std::span in_params{ - reinterpret_cast(input), voice_count}; std::span out_params{reinterpret_cast(output), voice_count}; @@ -76,8 +74,104 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, u32 new_voice_count{0}; + // Two input formats exist: legacy (0x170) and v2 with float biquad (0x188). + const bool use_v2 = behaviour.IsVoiceInParameterV2Supported(); + const u32 in_stride = use_v2 ? 0x188u : static_cast(sizeof(VoiceInfo::InParameter)); + for (u32 i = 0; i < voice_count; i++) { - const auto& in_param{in_params[i]}; + VoiceInfo::InParameter local_in{}; + std::array float_biquads{}; + + if (!use_v2) { + const auto* in_param_ptr = reinterpret_cast( + input + i * sizeof(VoiceInfo::InParameter)); + local_in = *in_param_ptr; + } else { + struct VoiceInParameterV2 { + u32 id; + u32 node_id; + bool is_new; + bool in_use; + PlayState play_state; + SampleFormat sample_format; + u32 sample_rate; + u32 priority; + u32 sort_order; + u32 channel_count; + f32 pitch; + f32 volume; + // Two BiquadFilterParameter2 (0x18 each) -> ignored/converted + struct BiquadV2 { + bool enable; + u8 r1; + u8 r2; + u8 r3; + std::array b; + std::array a; + } biquads[2]; + u32 wave_buffer_count; + u32 wave_buffer_index; + u32 reserved1; + u64 src_data_address; + u64 src_data_size; + s32 mix_id; + u32 splitter_id; + std::array wavebuffers; + std::array channel_resource_ids; + bool clear_voice_drop; + u8 flush_wave_buffer_count; + u16 reserved2; + VoiceInfo::Flags flags; + SrcQuality src_quality; + u32 external_ctx; + u32 external_ctx_size; + u32 reserved3[2]; + }; + const auto* vin = reinterpret_cast(input + i * in_stride); + local_in.id = vin->id; + local_in.node_id = vin->node_id; + local_in.is_new = vin->is_new; + local_in.in_use = vin->in_use; + local_in.play_state = vin->play_state; + local_in.sample_format = vin->sample_format; + local_in.sample_rate = vin->sample_rate; + local_in.priority = static_cast(vin->priority); + local_in.sort_order = static_cast(vin->sort_order); + local_in.channel_count = vin->channel_count; + local_in.pitch = vin->pitch; + local_in.volume = vin->volume; + + // For REV15+, we keep float coefficients separate and only convert for compatibility + for (size_t filter_idx = 0; filter_idx < MaxBiquadFilters; filter_idx++) { + const auto& src = vin->biquads[filter_idx]; + auto& dst = local_in.biquads[filter_idx]; + dst.enabled = src.enable; + // Convert float coefficients to fixed-point Q2.14 for legacy path + dst.b[0] = static_cast(std::clamp(src.b[0] * 16384.0f, -32768.0f, 32767.0f)); + dst.b[1] = static_cast(std::clamp(src.b[1] * 16384.0f, -32768.0f, 32767.0f)); + dst.b[2] = static_cast(std::clamp(src.b[2] * 16384.0f, -32768.0f, 32767.0f)); + dst.a[0] = static_cast(std::clamp(src.a[0] * 16384.0f, -32768.0f, 32767.0f)); + dst.a[1] = static_cast(std::clamp(src.a[1] * 16384.0f, -32768.0f, 32767.0f)); + + // Also store the native float version + float_biquads[filter_idx].enabled = src.enable; + float_biquads[filter_idx].numerator = src.b; + float_biquads[filter_idx].denominator = src.a; + } + local_in.wave_buffer_count = vin->wave_buffer_count; + local_in.wave_buffer_index = static_cast(vin->wave_buffer_index); + local_in.src_data_address = static_cast(vin->src_data_address); + local_in.src_data_size = vin->src_data_size; + local_in.mix_id = static_cast(vin->mix_id); + local_in.splitter_id = vin->splitter_id; + local_in.wave_buffer_internal = vin->wavebuffers; + local_in.channel_resource_ids = vin->channel_resource_ids; + local_in.clear_voice_drop = vin->clear_voice_drop; + local_in.flush_buffer_count = vin->flush_wave_buffer_count; + local_in.flags = vin->flags; + local_in.src_quality = vin->src_quality; + } + const auto& in_param = local_in; std::array voice_states{}; if (!in_param.in_use) { @@ -101,6 +195,14 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, BehaviorInfo::ErrorInfo update_error{}; voice_info.UpdateParameters(update_error, in_param, pool_mapper, behaviour); + // For REV15+, store the native float biquad coefficients + if (use_v2) { + voice_info.use_float_biquads = true; + voice_info.biquads_float = float_biquads; + } else { + voice_info.use_float_biquads = false; + } + if (!update_error.error_code.IsSuccess()) { behaviour.AppendError(update_error); } @@ -121,7 +223,7 @@ Result InfoUpdater::UpdateVoices(VoiceContext& voice_context, new_voice_count += in_param.channel_count; } - auto consumed_input_size{voice_count * static_cast(sizeof(VoiceInfo::InParameter))}; + auto consumed_input_size{voice_count * in_stride}; auto consumed_output_size{voice_count * static_cast(sizeof(VoiceInfo::OutStatus))}; if (consumed_input_size != in_header->voices_size) { LOG_ERROR(Service_Audio, "Consumed an incorrect voices size, header size={}, consumed={}", @@ -257,18 +359,31 @@ Result InfoUpdater::UpdateMixes(MixContext& mix_context, const u32 mix_buffer_co EffectContext& effect_context, SplitterContext& splitter_context) { s32 mix_count{0}; u32 consumed_input_size{0}; + u32 input_mix_size{0}; if (behaviour.IsMixInParameterDirtyOnlyUpdateSupported()) { auto in_dirty_params{reinterpret_cast(input)}; mix_count = in_dirty_params->count; + + // Validate against expected header size to ensure structure is correct + if (mix_count < 0 || mix_count > 0x100) { + LOG_ERROR( + Service_Audio, + "Invalid mix count from dirty parameter: count={}, magic=0x{:X}, expected_size={}", + mix_count, in_dirty_params->magic, in_header->mix_size); + return Service::Audio::ResultInvalidUpdateInfo; + } + + consumed_input_size += static_cast(sizeof(MixInfo::InDirtyParameter)); input += sizeof(MixInfo::InDirtyParameter); - consumed_input_size = static_cast(sizeof(MixInfo::InDirtyParameter) + - mix_count * sizeof(MixInfo::InParameter)); } else { mix_count = mix_context.GetCount(); - consumed_input_size = static_cast(mix_count * sizeof(MixInfo::InParameter)); } + input_mix_size = static_cast(mix_count * sizeof(MixInfo::InParameter)); + consumed_input_size += input_mix_size; + + if (mix_buffer_count == 0) { return Service::Audio::ResultInvalidUpdateInfo; } diff --git a/src/audio_core/renderer/command/command_buffer.cpp b/src/audio_core/renderer/command/command_buffer.cpp index d680e0c74b..a0a574fc64 100644 --- a/src/audio_core/renderer/command/command_buffer.cpp +++ b/src/audio_core/renderer/command/command_buffer.cpp @@ -237,6 +237,13 @@ void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& vo cmd.biquad = voice_info.biquads[biquad_index]; + if (voice_info.use_float_biquads) { + cmd.biquad_float = voice_info.biquads_float[biquad_index]; + cmd.use_float_coefficients = true; + } else { + cmd.use_float_coefficients = false; + } + cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()), MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); @@ -263,6 +270,9 @@ void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBas cmd.biquad.b = parameter.b; cmd.biquad.a = parameter.a; + // Effects use legacy fixed-point format + cmd.use_float_coefficients = false; + cmd.state = memory_pool->Translate(CpuAddr(state), MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); @@ -658,6 +668,13 @@ void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, Voice cmd.output = buffer_count + channel; cmd.biquads = voice_info.biquads; + if (voice_info.use_float_biquads) { + cmd.biquads_float = voice_info.biquads_float; + cmd.use_float_coefficients = true; + } else { + cmd.use_float_coefficients = false; + } + cmd.states[0] = memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()), MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState)); diff --git a/src/audio_core/renderer/command/effect/biquad_filter.cpp b/src/audio_core/renderer/command/effect/biquad_filter.cpp index 539e0c6370..4ad3184079 100644 --- a/src/audio_core/renderer/command/effect/biquad_filter.cpp +++ b/src/audio_core/renderer/command/effect/biquad_filter.cpp @@ -51,6 +51,40 @@ void ApplyBiquadFilterFloat(std::span output, std::span input, state.s3 = Common::BitCast(s[3]); } +/** + * Biquad filter float implementation with native float coefficients. + */ +void ApplyBiquadFilterFloat2(std::span output, std::span input, + std::array& b, std::array& a, + VoiceState::BiquadFilterState& state, const u32 sample_count) { + constexpr f64 min{std::numeric_limits::min()}; + constexpr f64 max{std::numeric_limits::max()}; + + std::array b_double{static_cast(b[0]), static_cast(b[1]), + static_cast(b[2])}; + std::array a_double{static_cast(a[0]), static_cast(a[1])}; + std::array s{Common::BitCast(state.s0), Common::BitCast(state.s1), + Common::BitCast(state.s2), Common::BitCast(state.s3)}; + + for (u32 i = 0; i < sample_count; i++) { + f64 in_sample{static_cast(input[i])}; + auto sample{in_sample * b_double[0] + s[0] * b_double[1] + s[1] * b_double[2] + + s[2] * a_double[0] + s[3] * a_double[1]}; + + output[i] = static_cast(std::clamp(sample, min, max)); + + s[1] = s[0]; + s[0] = in_sample; + s[3] = s[2]; + s[2] = sample; + } + + state.s0 = Common::BitCast(s[0]); + state.s1 = Common::BitCast(s[1]); + state.s2 = Common::BitCast(s[2]); + state.s3 = Common::BitCast(s[3]); +} + /** * Biquad filter s32 implementation. * @@ -98,8 +132,14 @@ void BiquadFilterCommand::Process(const AudioRenderer::CommandListProcessor& pro processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)}; if (use_float_processing) { - ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_, - processor.sample_count); + // REV15+: Use native float coefficients if available + if (use_float_coefficients) { + ApplyBiquadFilterFloat2(output_buffer, input_buffer, biquad_float.numerator, + biquad_float.denominator, *state_, processor.sample_count); + } else { + ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_, + processor.sample_count); + } } else { ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_, processor.sample_count); diff --git a/src/audio_core/renderer/command/effect/biquad_filter.h b/src/audio_core/renderer/command/effect/biquad_filter.h index 0e903930a6..0e1893e536 100644 --- a/src/audio_core/renderer/command/effect/biquad_filter.h +++ b/src/audio_core/renderer/command/effect/biquad_filter.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -50,12 +53,16 @@ struct BiquadFilterCommand : ICommand { s16 output; /// Input parameters for biquad VoiceInfo::BiquadFilterParameter biquad; + /// Input parameters for biquad (REV15+ native float) + VoiceInfo::BiquadFilterParameter2 biquad_float; /// Biquad state, updated each call CpuAddr state; /// If true, reset the state bool needs_init; /// If true, use float processing rather than int bool use_float_processing; + /// If true, use native float coefficients (REV15+) + bool use_float_coefficients; }; /** @@ -72,4 +79,18 @@ void ApplyBiquadFilterFloat(std::span output, std::span input, std::array& b, std::array& a, VoiceState::BiquadFilterState& state, const u32 sample_count); +/** + * Biquad filter float implementation with native float coefficients (SDK REV15+). + * + * @param output - Output container for filtered samples. + * @param input - Input container for samples to be filtered. + * @param b - Feedforward coefficients (float). + * @param a - Feedback coefficients (float). + * @param state - State to track previous samples. + * @param sample_count - Number of samples to process. + */ +void ApplyBiquadFilterFloat2(std::span output, std::span input, + std::array& b, std::array& a, + VoiceState::BiquadFilterState& state, const u32 sample_count); + } // namespace AudioCore::Renderer diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp index 208bbeaf29..2dfbcf6145 100644 --- a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp +++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -33,8 +36,14 @@ void MultiTapBiquadFilterCommand::Process(const AudioRenderer::CommandListProces *state = {}; } - ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state, - processor.sample_count); + // REV15+: Use native float coefficients if available + if (use_float_coefficients) { + ApplyBiquadFilterFloat2(output_buffer, input_buffer, biquads_float[i].numerator, + biquads_float[i].denominator, *state, processor.sample_count); + } else { + ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state, + processor.sample_count); + } } } diff --git a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h index 50fce80b0c..35fa76f62d 100644 --- a/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h +++ b/src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -49,12 +52,16 @@ struct MultiTapBiquadFilterCommand : ICommand { s16 output; /// Biquad parameters std::array biquads; + /// Biquad parameters (REV15+ native float) + std::array biquads_float; /// Biquad states, updated each call std::array states; /// If each biquad needs initialisation std::array needs_init; /// Number of active biquads u8 filter_tap_count; + /// If true, use native float coefficients (REV15+) + bool use_float_coefficients; }; } // namespace AudioCore::Renderer diff --git a/src/audio_core/renderer/splitter/splitter_context.cpp b/src/audio_core/renderer/splitter/splitter_context.cpp index fb118e981e..1ba5daebe9 100644 --- a/src/audio_core/renderer/splitter/splitter_context.cpp +++ b/src/audio_core/renderer/splitter/splitter_context.cpp @@ -35,12 +35,16 @@ SplitterDestinationData& SplitterContext::GetData(const u32 index) { void SplitterContext::Setup(std::span splitter_infos_, const u32 splitter_info_count_, SplitterDestinationData* splitter_destinations_, - const u32 destination_count_, const bool splitter_bug_fixed_) { + const u32 destination_count_, const bool splitter_bug_fixed_, + const BehaviorInfo& behavior) { splitter_infos = splitter_infos_; info_count = splitter_info_count_; splitter_destinations = splitter_destinations_; destinations_count = destination_count_; splitter_bug_fixed = splitter_bug_fixed_; + splitter_prev_volume_reset_supported = behavior.IsSplitterPrevVolumeResetSupported(); + splitter_biquad_param_supported = behavior.IsBiquadFilterParameterForSplitterEnabled(); + splitter_float_coeff_supported = behavior.IsSplitterDestinationV2bSupported(); } bool SplitterContext::UsingSplitter() const { @@ -84,7 +88,7 @@ bool SplitterContext::Initialize(const BehaviorInfo& behavior, } Setup(splitter_infos, params.splitter_infos, splitter_destinations, - params.splitter_destinations, behavior.IsSplitterBugFixed()); + params.splitter_destinations, behavior.IsSplitterBugFixed(), behavior); } return true; } @@ -137,19 +141,104 @@ u32 SplitterContext::UpdateInfo(const u8* input, u32 offset, const u32 splitter_ u32 SplitterContext::UpdateData(const u8* input, u32 offset, const u32 count) { for (u32 i = 0; i < count; i++) { - auto data_header{ - reinterpret_cast(input + offset)}; + // Version selection based on feature flags: + // - REV12: integer biquad params (Version2a) + // - REV15: float coeff/biquad v2b + // - older: no biquad fields + if (!splitter_biquad_param_supported) { + const auto* data_header = + reinterpret_cast(input + offset); - if (data_header->magic != GetSplitterSendDataMagic()) { - continue; + if (data_header->magic != GetSplitterSendDataMagic()) { + continue; + } + if (data_header->id < 0 || data_header->id > destinations_count) { + continue; + } + + auto modified_params = *data_header; + if (!splitter_prev_volume_reset_supported) { + modified_params.reset_prev_volume = false; + } + splitter_destinations[data_header->id].Update(modified_params); + offset += sizeof(SplitterDestinationData::InParameter); + } else if (!splitter_float_coeff_supported) { + // Version 2a: struct contains legacy fixed-point biquad filter fields (REV12+) + const auto* data_header_v2a = + reinterpret_cast(input + + offset); + + if (data_header_v2a->magic != GetSplitterSendDataMagic()) { + continue; + } + if (data_header_v2a->id < 0 || data_header_v2a->id > destinations_count) { + continue; + } + + // Map common fields to the base format + SplitterDestinationData::InParameter mapped{}; + mapped.magic = data_header_v2a->magic; + mapped.id = data_header_v2a->id; + mapped.mix_volumes = data_header_v2a->mix_volumes; + mapped.mix_id = data_header_v2a->mix_id; + mapped.in_use = data_header_v2a->in_use; + mapped.reset_prev_volume = + splitter_prev_volume_reset_supported ? data_header_v2a->reset_prev_volume : false; + + auto& destination = splitter_destinations[data_header_v2a->id]; + destination.Update(mapped); + + // Convert legacy fixed-point biquad params into float representation + auto biquad_filters = destination.GetBiquadFilters(); + for (size_t filter_idx = 0; filter_idx < MaxBiquadFilters; filter_idx++) { + const auto& legacy = data_header_v2a->biquad_filters[filter_idx]; + auto& out = biquad_filters[filter_idx]; + out.enabled = legacy.enabled; + // s16 fixed-point scale: use Q14 like voices (b and a are s16, 1.0 ~= 1<<14) + constexpr float scale = 1.0f / static_cast(1 << 14); + out.numerator[0] = static_cast(legacy.b[0]) * scale; + out.numerator[1] = static_cast(legacy.b[1]) * scale; + out.numerator[2] = static_cast(legacy.b[2]) * scale; + out.denominator[0] = static_cast(legacy.a[0]) * scale; + out.denominator[1] = static_cast(legacy.a[1]) * scale; + } + + offset += static_cast(sizeof(SplitterDestinationData::InParameterVersion2a)); + } else { + // Version 2b: struct contains extra biquad filter fields with float coeffs + const auto* data_header_v2b = + reinterpret_cast(input + + offset); + + if (data_header_v2b->magic != GetSplitterSendDataMagic()) { + continue; + } + if (data_header_v2b->id < 0 || data_header_v2b->id > destinations_count) { + continue; + } + + // Map common fields to the old format + SplitterDestinationData::InParameter mapped{}; + mapped.magic = data_header_v2b->magic; + mapped.id = data_header_v2b->id; + mapped.mix_volumes = data_header_v2b->mix_volumes; + mapped.mix_id = data_header_v2b->mix_id; + mapped.in_use = data_header_v2b->in_use; + mapped.reset_prev_volume = + splitter_prev_volume_reset_supported ? data_header_v2b->reset_prev_volume : false; + + // Store biquad filters from V2b (REV15+) + auto& destination = splitter_destinations[data_header_v2b->id]; + destination.Update(mapped); + + // Copy biquad filter parameters + auto biquad_filters = destination.GetBiquadFilters(); + for (size_t filter_idx = 0; filter_idx < MaxBiquadFilters; filter_idx++) { + biquad_filters[filter_idx] = data_header_v2b->biquad_filters[filter_idx]; + } + + offset += static_cast(sizeof(SplitterDestinationData::InParameterVersion2b)); } - - if (data_header->id < 0 || data_header->id > destinations_count) { - continue; - } - - splitter_destinations[data_header->id].Update(*data_header); - offset += sizeof(SplitterDestinationData::InParameter); } return offset; diff --git a/src/audio_core/renderer/splitter/splitter_context.h b/src/audio_core/renderer/splitter/splitter_context.h index 1c0b846719..c71ce3d7c4 100644 --- a/src/audio_core/renderer/splitter/splitter_context.h +++ b/src/audio_core/renderer/splitter/splitter_context.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -168,10 +171,11 @@ private: * @param splitter_destinations - Workbuffer for splitter destinations. * @param destination_count - Number of destinations in the workbuffer. * @param splitter_bug_fixed - Is the splitter bug fixed? + * @param behavior - Behavior info for feature support. */ void Setup(std::span splitter_infos, u32 splitter_info_count, SplitterDestinationData* splitter_destinations, u32 destination_count, - bool splitter_bug_fixed); + bool splitter_bug_fixed, const BehaviorInfo& behavior); /// Workbuffer for splitters std::span splitter_infos{}; @@ -183,6 +187,12 @@ private: s32 destinations_count{}; /// Is the splitter bug fixed? bool splitter_bug_fixed{}; + /// Is explicit previous mix volume reset supported? + bool splitter_prev_volume_reset_supported{}; + /// Is biquad filter parameter for splitter (REV12) supported? + bool splitter_biquad_param_supported{}; + /// Is float coefficient/biquad filter v2b parameter supported? + bool splitter_float_coeff_supported{}; }; } // namespace Renderer diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp index 5ec37e48e1..028cee9853 100644 --- a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -84,4 +87,14 @@ void SplitterDestinationData::SetNext(SplitterDestinationData* next_) { next = next_; } +std::span +SplitterDestinationData::GetBiquadFilters() { + return biquad_filters; +} + +std::span +SplitterDestinationData::GetBiquadFilters() const { + return biquad_filters; +} + } // namespace AudioCore::Renderer diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.h b/src/audio_core/renderer/splitter/splitter_destinations_data.h index 90edfc667c..6bfe5e0e34 100644 --- a/src/audio_core/renderer/splitter/splitter_destinations_data.h +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -16,16 +19,72 @@ namespace AudioCore::Renderer { */ class SplitterDestinationData { public: + /** + * Biquad filter parameter with float coefficients (SDK REV15+). + * Defined here to avoid circular dependency with VoiceInfo. + */ + struct BiquadFilterParameter2 { + /* 0x00 */ bool enabled; + /* 0x01 */ u8 reserved1; + /* 0x02 */ u8 reserved2; + /* 0x03 */ u8 reserved3; + /* 0x04 */ std::array numerator; // b0, b1, b2 + /* 0x10 */ std::array denominator; // a1, a2 (a0 = 1) + }; + static_assert(sizeof(BiquadFilterParameter2) == 0x18, + "BiquadFilterParameter2 has the wrong size!"); + + /** + * Legacy biquad filter parameter with fixed-point coefficients (SDK REV12+ for splitters). + * Matches the old voice biquad format. + */ + struct BiquadFilterParameterLegacy { + /* 0x00 */ bool enabled; + /* 0x02 */ std::array b; // numerator + /* 0x08 */ std::array a; // denominator (a0 = 1) + }; + static_assert(sizeof(BiquadFilterParameterLegacy) == 0xC, + "BiquadFilterParameterLegacy has the wrong size!"); + struct InParameter { /* 0x00 */ u32 magic; // 'SNDD' /* 0x04 */ s32 id; /* 0x08 */ std::array mix_volumes; /* 0x68 */ u32 mix_id; /* 0x6C */ bool in_use; + /* 0x6D */ bool reset_prev_volume; }; static_assert(sizeof(InParameter) == 0x70, "SplitterDestinationData::InParameter has the wrong size!"); + struct InParameterVersion2a { + /* 0x00 */ u32 magic; // 'SNDD' + /* 0x04 */ s32 id; + /* 0x08 */ std::array mix_volumes; + /* 0x68 */ u32 mix_id; + /* 0x6C */ std::array + biquad_filters; + /* 0x84 */ bool in_use; + /* 0x85 */ bool reset_prev_volume; // only effective if supported + /* 0x86 */ u8 reserved[10]; + }; + static_assert(sizeof(InParameterVersion2a) == 0x90, + "SplitterDestinationData::InParameterVersion2a has the wrong size!"); + + struct InParameterVersion2b { + /* 0x00 */ u32 magic; // 'SNDD' + /* 0x04 */ s32 id; + /* 0x08 */ std::array mix_volumes; + /* 0x68 */ u32 mix_id; + /* 0x6C */ std::array + biquad_filters; + /* 0x9C */ bool in_use; + /* 0x9D */ bool reset_prev_volume; + /* 0x9E */ u8 reserved[10]; + }; + static_assert(sizeof(InParameterVersion2b) == 0xA8, + "SplitterDestinationData::InParameterVersion2b has the wrong size!"); + SplitterDestinationData(s32 id); /** @@ -78,7 +137,7 @@ public: f32 GetMixVolumePrev(u32 index) const; /** - * Get the previous mix volumes for all mix buffers in this destination. + * Get the previous mix volumes for all mix buffers. * * @return Span of previous mix buffer volumes. */ @@ -115,6 +174,20 @@ public: */ void SetNext(SplitterDestinationData* next); + /** + * Get biquad filter parameters for this destination (REV15+ or mapped from REV12). + * + * @return Span of biquad filter parameters. + */ + std::span GetBiquadFilters(); + + /** + * Get const biquad filter parameters for this destination (REV15+ or mapped from REV12). + * + * @return Const span of biquad filter parameters. + */ + std::span GetBiquadFilters() const; + private: /// Id of this destination const s32 id; @@ -124,6 +197,8 @@ private: std::array mix_volumes{0.0f}; /// Previous mix volumes std::array prev_mix_volumes{0.0f}; + /// Biquad filter parameters (REV15+ or mapped from REV12) + std::array biquad_filters{}; /// Next destination in the mix chain SplitterDestinationData* next{}; /// Is this destination in use? diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h index 14a687dcb7..595150f232 100644 --- a/src/audio_core/renderer/voice/voice_info.h +++ b/src/audio_core/renderer/voice/voice_info.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -135,6 +138,17 @@ public: static_assert(sizeof(BiquadFilterParameter) == 0xC, "VoiceInfo::BiquadFilterParameter has the wrong size!"); + struct BiquadFilterParameter2 { + /* 0x00 */ bool enabled; + /* 0x01 */ u8 reserved1; + /* 0x02 */ u8 reserved2; + /* 0x03 */ u8 reserved3; + /* 0x04 */ std::array numerator; // b0, b1, b2 + /* 0x10 */ std::array denominator; // a1, a2 (a0 = 1) + }; + static_assert(sizeof(BiquadFilterParameter2) == 0x18, + "VoiceInfo::BiquadFilterParameter2 has the wrong size!"); + struct InParameter { /* 0x000 */ u32 id; /* 0x004 */ u32 node_id; @@ -168,6 +182,43 @@ public: }; static_assert(sizeof(InParameter) == 0x170, "VoiceInfo::InParameter has the wrong size!"); + struct InParameter2 { + /* 0x000 */ u32 id; + /* 0x004 */ u32 node_id; + /* 0x008 */ bool is_new; + /* 0x009 */ bool in_use; + /* 0x00A */ PlayState play_state; + /* 0x00B */ SampleFormat sample_format; + /* 0x00C */ u32 sample_rate; + /* 0x010 */ s32 priority; + /* 0x014 */ s32 sort_order; + /* 0x018 */ u32 channel_count; + /* 0x01C */ f32 pitch; + /* 0x020 */ f32 volume; + /* 0x024 */ std::array biquads; + /* 0x054 */ u32 wave_buffer_count; + /* 0x058 */ u32 wave_buffer_index; + /* 0x05C */ u32 reserved1; + /* 0x060 */ CpuAddr src_data_address; + /* 0x068 */ u64 src_data_size; + /* 0x070 */ u32 mix_id; + /* 0x074 */ u32 splitter_id; + /* 0x078 */ std::array wave_buffer_internal; + /* 0x158 */ std::array channel_resource_ids; + /* 0x170 */ bool clear_voice_drop; + /* 0x171 */ u8 flush_buffer_count; + /* 0x172 */ u16 reserved2; + /* 0x174 */ Flags flags; + /* 0x175 */ u8 reserved3; + /* 0x176 */ SrcQuality src_quality; + /* 0x177 */ u8 reserved4; + /* 0x178 */ u32 external_context; + /* 0x17C */ u32 external_context_size; + /* 0x180 */ u32 reserved5; + /* 0x184 */ u32 reserved6; + }; + static_assert(sizeof(InParameter2) == 0x188, "VoiceInfo::InParameter2 has the wrong size!"); + struct OutStatus { /* 0x00 */ u64 played_sample_count; /* 0x08 */ u32 wave_buffers_consumed; @@ -349,6 +400,10 @@ public: f32 prev_volume{}; /// Biquad filters for generating filter commands on this voice std::array biquads{}; + /// Float biquad filters for REV15+ (native float coefficients) + std::array biquads_float{}; + /// Use float biquad coefficients (REV15+) + bool use_float_biquads{}; /// Number of active wavebuffers u32 wave_buffer_count{}; /// Current playing wavebuffer index diff --git a/src/core/hle/service/am/service/library_applet_creator.cpp b/src/core/hle/service/am/service/library_applet_creator.cpp index 54790838e0..e38729e70a 100644 --- a/src/core/hle/service/am/service/library_applet_creator.cpp +++ b/src/core/hle/service/am/service/library_applet_creator.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -175,6 +178,7 @@ ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_, std::shared_ {0, D<&ILibraryAppletCreator::CreateLibraryApplet>, "CreateLibraryApplet"}, {1, nullptr, "TerminateAllLibraryApplets"}, {2, nullptr, "AreAnyLibraryAppletsLeft"}, + {3, D<&ILibraryAppletCreator::CreateLibraryAppletEx>, "CreateLibraryAppletEx"}, {10, D<&ILibraryAppletCreator::CreateStorage>, "CreateStorage"}, {11, D<&ILibraryAppletCreator::CreateTransferMemoryStorage>, "CreateTransferMemoryStorage"}, {12, D<&ILibraryAppletCreator::CreateHandleStorage>, "CreateHandleStorage"}, @@ -210,6 +214,32 @@ Result ILibraryAppletCreator::CreateLibraryApplet( R_SUCCEED(); } +Result ILibraryAppletCreator::CreateLibraryAppletEx( + Out> out_library_applet_accessor, AppletId applet_id, + LibraryAppletMode library_applet_mode, u64 thread_id) { + LOG_DEBUG(Service_AM, "called with applet_id={} applet_mode={} thread_id={}", applet_id, + library_applet_mode, thread_id); + + std::shared_ptr library_applet; + if (ShouldCreateGuestApplet(applet_id)) { + library_applet = + CreateGuestApplet(system, m_window_system, m_applet, applet_id, library_applet_mode); + } + if (!library_applet) { + library_applet = + CreateFrontendApplet(system, m_window_system, m_applet, applet_id, library_applet_mode); + } + if (!library_applet) { + LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", applet_id); + R_THROW(ResultUnknown); + } + + // Applet is created, can now be launched. + m_applet->library_applet_launchable_event.Signal(); + *out_library_applet_accessor = library_applet; + R_SUCCEED(); +} + Result ILibraryAppletCreator::CreateStorage(Out> out_storage, s64 size) { LOG_DEBUG(Service_AM, "called, size={}", size); diff --git a/src/core/hle/service/am/service/library_applet_creator.h b/src/core/hle/service/am/service/library_applet_creator.h index a10a769828..34e7586e8f 100644 --- a/src/core/hle/service/am/service/library_applet_creator.h +++ b/src/core/hle/service/am/service/library_applet_creator.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -24,6 +27,9 @@ private: Result CreateLibraryApplet( Out> out_library_applet_accessor, AppletId applet_id, LibraryAppletMode library_applet_mode); + Result CreateLibraryAppletEx( + Out> out_library_applet_accessor, AppletId applet_id, + LibraryAppletMode library_applet_mode, u64 thread_id); Result CreateStorage(Out> out_storage, s64 size); Result CreateTransferMemoryStorage( Out> out_storage, bool is_writable, s64 size, diff --git a/src/core/hle/service/ns/application_manager_interface.cpp b/src/core/hle/service/ns/application_manager_interface.cpp index 60ecd5c2b9..461f134bfd 100644 --- a/src/core/hle/service/ns/application_manager_interface.cpp +++ b/src/core/hle/service/ns/application_manager_interface.cpp @@ -128,6 +128,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {406, nullptr, "GetApplicationControlProperty"}, {407, nullptr, "ListApplicationTitle"}, {408, nullptr, "ListApplicationIcon"}, + {419, D<&IApplicationManagerInterface::RequestDownloadApplicationControlDataInBackground>, "RequestDownloadApplicationControlDataInBackground"}, {502, nullptr, "RequestCheckGameCardRegistration"}, {503, nullptr, "RequestGameCardRegistrationGoldPoint"}, {504, nullptr, "RequestRegisterGameCard"}, @@ -210,6 +211,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {1703, nullptr, "GetApplicationViewDownloadErrorContext"}, {1704, D<&IApplicationManagerInterface::GetApplicationViewWithPromotionInfo>, "GetApplicationViewWithPromotionInfo"}, {1705, nullptr, "IsPatchAutoDeletableApplication"}, + {1706, D<&IApplicationManagerInterface::Unknown1706>, "Unknown1706"}, {1800, nullptr, "IsNotificationSetupCompleted"}, {1801, nullptr, "GetLastNotificationInfoCount"}, {1802, nullptr, "ListLastNotificationInfo"}, @@ -309,6 +311,7 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_ {4022, D<&IApplicationManagerInterface::Unknown4022>, "Unknown4022"}, {4023, D<&IApplicationManagerInterface::Unknown4023>, "Unknown4023"}, {4088, D<&IApplicationManagerInterface::Unknown4022>, "Unknown4088"}, + {4053, D<&IApplicationManagerInterface::Unknown4053>, "Unknown4053"}, {9999, nullptr, "GetApplicationCertificate"}, }; // clang-format on @@ -526,6 +529,37 @@ Result IApplicationManagerInterface::GetApplicationTerminateResult(Out o R_SUCCEED(); } +Result IApplicationManagerInterface::RequestDownloadApplicationControlDataInBackground( + u64 unk, u64 application_id) { + LOG_WARNING(Service_NS, "(STUBBED), app={:016X} unk={}", application_id, unk); + R_SUCCEED(); +} + +Result IApplicationManagerInterface::Unknown1706( + OutBuffer out_buffer_58, + InBuffer in_buffer_8) { + LOG_WARNING(Service_NS, "(STUBBED) Unknown1706 called: out_size={} in_size={}", + out_buffer_58.size(), in_buffer_8.size()); + + if (out_buffer_58.size() < 0x58 || in_buffer_8.size() < 0x8) { + R_THROW(ResultUnknown); + } + + u64 application_id = 0; + std::memcpy(&application_id, in_buffer_8.data(), sizeof(u64)); + + ApplicationView view{}; + view.application_id = application_id; + view.unk = 0x70000; + view.flags = 0x401f17; + + std::memset(out_buffer_58.data(), 0, out_buffer_58.size()); + std::memcpy(out_buffer_58.data(), &view, sizeof(ApplicationView)); + + + R_SUCCEED(); +} + Result IApplicationManagerInterface::Unknown4022( OutCopyHandle out_event) { LOG_WARNING(Service_NS, "(STUBBED) called"); @@ -539,4 +573,9 @@ Result IApplicationManagerInterface::Unknown4023(Out out_result) { R_SUCCEED(); } +Result IApplicationManagerInterface::Unknown4053() { + LOG_WARNING(Service_NS, "(STUBBED) called."); + R_SUCCEED(); +} + } // namespace Service::NS diff --git a/src/core/hle/service/ns/application_manager_interface.h b/src/core/hle/service/ns/application_manager_interface.h index 251f93ee06..e181960396 100644 --- a/src/core/hle/service/ns/application_manager_interface.h +++ b/src/core/hle/service/ns/application_manager_interface.h @@ -55,6 +55,12 @@ public: Result GetApplicationTerminateResult(Out out_result, u64 application_id); Result Unknown4022(OutCopyHandle out_event); Result Unknown4023(Out out_result); + Result Unknown4053(); + + Result RequestDownloadApplicationControlDataInBackground(u64 unk, + u64 application_id); + Result Unknown1706(OutBuffer out_buffer_58, + InBuffer in_buffer_8); private: KernelHelpers::ServiceContext service_context; diff --git a/src/core/hle/service/ns/ns_types.h b/src/core/hle/service/ns/ns_types.h index 2dd664c4e9..a4eec3a5d8 100644 --- a/src/core/hle/service/ns/ns_types.h +++ b/src/core/hle/service/ns/ns_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -81,10 +84,11 @@ static_assert(sizeof(PromotionInfo) == 0x20, "PromotionInfo has incorrect size." /// NsApplicationViewWithPromotionInfo struct ApplicationViewWithPromotionInfo { - ApplicationView view; ///< \ref NsApplicationView - PromotionInfo promotion; ///< \ref NsPromotionInfo + ApplicationView view; ///< \ref NsApplicationView + PromotionInfo promotion; ///< \ref NsPromotionInfo + std::array padding{}; ///< Extra padding for newer HOS versions }; -static_assert(sizeof(ApplicationViewWithPromotionInfo) == 0x70, +static_assert(sizeof(ApplicationViewWithPromotionInfo) == 0x78, "ApplicationViewWithPromotionInfo has incorrect size."); struct ApplicationOccupiedSizeEntity { @@ -113,4 +117,10 @@ struct Uid { }; static_assert(sizeof(Uid) == 0x10, "Uid has incorrect size."); +struct ApplicationDisplayData { + std::array application_name; + std::array developer_name; +}; +static_assert(sizeof(ApplicationDisplayData) == 0x300, "ApplicationDisplayData has incorrect size."); + } // namespace Service::NS 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 9b2ca94a4f..c72aa08870 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 @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -7,6 +10,7 @@ #include "core/file_sys/vfs/vfs.h" #include "core/hle/service/cmif_serialization.h" #include "core/hle/service/ns/language.h" +#include "core/hle/service/ns/ns_types.h" #include "core/hle/service/ns/ns_results.h" #include "core/hle/service/ns/read_only_application_control_data_interface.h" #include "core/hle/service/set/settings_server.h" @@ -23,6 +27,7 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa {2, D<&IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLanguageCode>, "ConvertApplicationLanguageToLanguageCode"}, {3, nullptr, "ConvertLanguageCodeToApplicationLanguage"}, {4, nullptr, "SelectApplicationDesiredLanguage"}, + {5, D<&IReadOnlyApplicationControlDataInterface::GetApplicationDisplayData>, "GetApplicationDisplayData"}, }; // clang-format on @@ -119,4 +124,33 @@ Result IReadOnlyApplicationControlDataInterface::ConvertApplicationLanguageToLan R_SUCCEED(); } +Result IReadOnlyApplicationControlDataInterface::GetApplicationDisplayData( + OutBuffer out_buffer, Out out_size, u64 language_code, + u64 application_id) { + LOG_INFO(Service_NS, "called with application_id={:016X}, language_code={:016X}", + application_id, language_code); + + constexpr u64 payload_size = sizeof(ApplicationDisplayData); + + if (out_buffer.size() < payload_size) { + LOG_ERROR(Service_NS, "output buffer is too small! (actual={}, expected_min={})", + out_buffer.size(), payload_size); + R_THROW(ResultUnknown); + } + + const FileSys::PatchManager pm{application_id, system.GetFileSystemController(), + system.GetContentProvider()}; + const auto control = pm.GetControlMetadata(); + + ApplicationDisplayData display_data{}; + + std::memset(display_data.application_name.data(), 0, display_data.application_name.size()); + std::memset(display_data.developer_name.data(), 0, display_data.developer_name.size()); + + std::memcpy(out_buffer.data(), &display_data, payload_size); + *out_size = payload_size; + + R_SUCCEED(); +} + } // namespace Service::NS diff --git a/src/core/hle/service/ns/read_only_application_control_data_interface.h b/src/core/hle/service/ns/read_only_application_control_data_interface.h index ac099435ab..9ac355014d 100644 --- a/src/core/hle/service/ns/read_only_application_control_data_interface.h +++ b/src/core/hle/service/ns/read_only_application_control_data_interface.h @@ -1,12 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 #pragma once #include "core/hle/service/cmif_types.h" +#include "core/hle/service/service.h" #include "core/hle/service/ns/language.h" #include "core/hle/service/ns/ns_types.h" -#include "core/hle/service/service.h" namespace Service::NS { @@ -16,7 +19,6 @@ public: explicit IReadOnlyApplicationControlDataInterface(Core::System& system_); ~IReadOnlyApplicationControlDataInterface() override; -public: Result GetApplicationControlData(OutBuffer out_buffer, Out out_actual_size, ApplicationControlSource application_control_source, @@ -25,6 +27,10 @@ public: u32 supported_languages); Result ConvertApplicationLanguageToLanguageCode(Out out_language_code, ApplicationLanguage application_language); + + Result GetApplicationDisplayData(OutBuffer out_buffer, + Out out_size, u64 language_code, + u64 application_id); }; } // namespace Service::NS diff --git a/src/core/hle/service/ns/read_only_application_record_interface.cpp b/src/core/hle/service/ns/read_only_application_record_interface.cpp index 816a1e1dc8..18f9e41d8c 100644 --- a/src/core/hle/service/ns/read_only_application_record_interface.cpp +++ b/src/core/hle/service/ns/read_only_application_record_interface.cpp @@ -1,8 +1,13 @@ +// SPDX-FileCopyrightText: Copyright 2025 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/hle/service/cmif_serialization.h" #include "core/hle/service/ns/read_only_application_record_interface.h" +#include "core/hle/service/ns/ns_types.h" +#include "core/hle/service/ns/application_manager_interface.h" namespace Service::NS { @@ -13,6 +18,8 @@ IReadOnlyApplicationRecordInterface::IReadOnlyApplicationRecordInterface(Core::S {1, nullptr, "NotifyApplicationFailure"}, {2, D<&IReadOnlyApplicationRecordInterface::IsDataCorruptedResult>, "IsDataCorruptedResult"}, + {3, D<&IReadOnlyApplicationRecordInterface::ListApplicationRecord>, + "ListApplicationRecord"}, }; // clang-format on @@ -35,4 +42,14 @@ Result IReadOnlyApplicationRecordInterface::IsDataCorruptedResult( R_SUCCEED(); } +Result IReadOnlyApplicationRecordInterface::ListApplicationRecord( + OutArray out_records, Out out_count, + s32 entry_offset) { + LOG_DEBUG(Service_NS, "delegating to IApplicationManagerInterface::ListApplicationRecord, offset={} limit={}", + entry_offset, out_records.size()); + + R_RETURN(IApplicationManagerInterface(system).ListApplicationRecord(out_records, out_count, + entry_offset)); +} + } // namespace Service::NS diff --git a/src/core/hle/service/ns/read_only_application_record_interface.h b/src/core/hle/service/ns/read_only_application_record_interface.h index d06e8f5e66..fc43c768ba 100644 --- a/src/core/hle/service/ns/read_only_application_record_interface.h +++ b/src/core/hle/service/ns/read_only_application_record_interface.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -5,6 +8,7 @@ #include "core/hle/service/cmif_types.h" #include "core/hle/service/service.h" +#include "core/hle/service/ns/ns_types.h" namespace Service::NS { @@ -17,6 +21,9 @@ public: private: Result HasApplicationRecord(Out out_has_application_record, u64 program_id); Result IsDataCorruptedResult(Out out_is_data_corrupted_result, Result result); + Result ListApplicationRecord( + OutArray out_records, Out out_count, + s32 entry_offset); }; } // namespace Service::NS diff --git a/src/core/hle/service/pctl/parental_control_service.cpp b/src/core/hle/service/pctl/parental_control_service.cpp index 82c65ac1fd..1310be64b1 100644 --- a/src/core/hle/service/pctl/parental_control_service.cpp +++ b/src/core/hle/service/pctl/parental_control_service.cpp @@ -38,6 +38,7 @@ IParentalControlService::IParentalControlService(Core::System& system_, Capabili {1016, nullptr, "ConfirmShowNewsPermission"}, {1017, D<&IParentalControlService::EndFreeCommunication>, "EndFreeCommunication"}, {1018, D<&IParentalControlService::IsFreeCommunicationAvailable>, "IsFreeCommunicationAvailable"}, + {1019, D<&IParentalControlService::ConfirmLaunchApplicationPermission>, "ConfirmLaunchApplicationPermission"}, {1031, D<&IParentalControlService::IsRestrictionEnabled>, "IsRestrictionEnabled"}, {1032, D<&IParentalControlService::GetSafetyLevel>, "GetSafetyLevel"}, {1033, nullptr, "SetSafetyLevel"}, diff --git a/src/hid_core/resources/npad/npad_types.h b/src/hid_core/resources/npad/npad_types.h index 92700d69a6..68b803c3d3 100644 --- a/src/hid_core/resources/npad/npad_types.h +++ b/src/hid_core/resources/npad/npad_types.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -159,6 +162,17 @@ struct NpadGcTriggerState { }; static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size"); +// This is nn::hid::NpadCondition (global controller condition structure) +struct NpadCondition { + u32 _00{}; + u32 is_initialized{1}; + u32 hold_type{static_cast(NpadJoyHoldType::Horizontal)}; + u32 is_valid{1}; +}; +static_assert(sizeof(NpadCondition) == 0x10, "NpadCondition is an invalid size"); + + + // This is nn::hid::NpadSystemProperties struct NPadSystemProperties { union { diff --git a/src/hid_core/resources/ring_lifo.h b/src/hid_core/resources/ring_lifo.h index 0816784e08..55ba2f0e61 100644 --- a/src/hid_core/resources/ring_lifo.h +++ b/src/hid_core/resources/ring_lifo.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -44,8 +47,7 @@ struct Lifo { buffer_count++; } buffer_tail = GetNextEntryIndex(); - const auto& previous_entry = ReadPreviousEntry(); - entries[buffer_tail].sampling_number = previous_entry.sampling_number + 1; + entries[buffer_tail].sampling_number = new_state.sampling_number << 1; entries[buffer_tail].state = new_state; } }; diff --git a/src/hid_core/resources/shared_memory_format.h b/src/hid_core/resources/shared_memory_format.h index 49755c8dc3..b9deeec302 100644 --- a/src/hid_core/resources/shared_memory_format.h +++ b/src/hid_core/resources/shared_memory_format.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -201,7 +204,9 @@ static_assert(sizeof(ConsoleSixAxisSensorSharedMemoryFormat) == 0x20, // This is nn::hid::detail::SharedMemoryFormat struct SharedMemoryFormat { - void Initialize() {} + void Initialize() { + npad_condition = NpadCondition{}; + } DebugPadSharedMemoryFormat debug_pad; TouchScreenSharedMemoryFormat touch_screen; @@ -218,7 +223,9 @@ struct SharedMemoryFormat { ConsoleSixAxisSensorSharedMemoryFormat console; INSERT_PADDING_BYTES(0x19E0); MouseSharedMemoryFormat debug_mouse; - INSERT_PADDING_BYTES(0x2000); + INSERT_PADDING_BYTES(0x200); + NpadCondition npad_condition; + INSERT_PADDING_BYTES(0x1DF0); }; static_assert(offsetof(SharedMemoryFormat, debug_pad) == 0x0, "debug_pad has wrong offset"); static_assert(offsetof(SharedMemoryFormat, touch_screen) == 0x400, "touch_screen has wrong offset"); @@ -236,6 +243,8 @@ static_assert(offsetof(SharedMemoryFormat, npad) == 0x9A00, "npad has wrong offs static_assert(offsetof(SharedMemoryFormat, gesture) == 0x3BA00, "gesture has wrong offset"); static_assert(offsetof(SharedMemoryFormat, console) == 0x3C200, "console has wrong offset"); static_assert(offsetof(SharedMemoryFormat, debug_mouse) == 0x3DC00, "debug_mouse has wrong offset"); +static_assert(offsetof(SharedMemoryFormat, npad_condition) == 0x3E200, + "npad_condition has wrong offset"); static_assert(sizeof(SharedMemoryFormat) == 0x40000, "SharedMemoryFormat is an invalid size"); } // namespace Service::HID