aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorunknown <sahyno1996@gmail.com>2025-10-12 17:03:14 +0200
committercrueter <crueter@eden-emu.dev>2025-10-12 17:03:14 +0200
commita3ef2cc1838a007a2f424e88212aa6f1eb93eab3 (patch)
tree6ab37fe23b6d30a33a352deb631866720d012ecb /src
parent1e1b8ad33face3f3409aec50818d2b4fda8e6a0d (diff)
[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 <zephyron@citron-emu.org> Co-authored-by: Shinmegumi <shinmegumi@eden-emu.dev> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2719 Reviewed-by: MaranBr <maranbr@eden-emu.dev> Reviewed-by: CamilleLaVey <camillelavey99@gmail.com> Co-authored-by: unknown <sahyno1996@gmail.com> Co-committed-by: unknown <sahyno1996@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/audio_core/common/feature_support.h10
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.cpp16
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.h35
-rw-r--r--src/audio_core/renderer/behavior/info_updater.cpp129
-rw-r--r--src/audio_core/renderer/command/command_buffer.cpp17
-rw-r--r--src/audio_core/renderer/command/effect/biquad_filter.cpp44
-rw-r--r--src/audio_core/renderer/command/effect/biquad_filter.h21
-rw-r--r--src/audio_core/renderer/command/effect/multi_tap_biquad_filter.cpp13
-rw-r--r--src/audio_core/renderer/command/effect/multi_tap_biquad_filter.h7
-rw-r--r--src/audio_core/renderer/splitter/splitter_context.cpp117
-rw-r--r--src/audio_core/renderer/splitter/splitter_context.h12
-rw-r--r--src/audio_core/renderer/splitter/splitter_destinations_data.cpp13
-rw-r--r--src/audio_core/renderer/splitter/splitter_destinations_data.h77
-rw-r--r--src/audio_core/renderer/voice/voice_info.h55
-rw-r--r--src/core/hle/service/am/service/library_applet_creator.cpp30
-rw-r--r--src/core/hle/service/am/service/library_applet_creator.h6
-rw-r--r--src/core/hle/service/ns/application_manager_interface.cpp39
-rw-r--r--src/core/hle/service/ns/application_manager_interface.h6
-rw-r--r--src/core/hle/service/ns/ns_types.h16
-rw-r--r--src/core/hle/service/ns/read_only_application_control_data_interface.cpp34
-rw-r--r--src/core/hle/service/ns/read_only_application_control_data_interface.h10
-rw-r--r--src/core/hle/service/ns/read_only_application_record_interface.cpp17
-rw-r--r--src/core/hle/service/ns/read_only_application_record_interface.h7
-rw-r--r--src/core/hle/service/pctl/parental_control_service.cpp1
-rw-r--r--src/hid_core/resources/npad/npad_types.h14
-rw-r--r--src/hid_core/resources/ring_lifo.h6
-rw-r--r--src/hid_core/resources/shared_memory_format.h13
27 files changed, 728 insertions, 37 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 <ranges>
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<const VoiceInfo::InParameter> in_params{
- reinterpret_cast<const VoiceInfo::InParameter*>(input), voice_count};
std::span<VoiceInfo::OutStatus> out_params{reinterpret_cast<VoiceInfo::OutStatus*>(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<u32>(sizeof(VoiceInfo::InParameter));
+
for (u32 i = 0; i < voice_count; i++) {
- const auto& in_param{in_params[i]};
+ VoiceInfo::InParameter local_in{};
+ std::array<VoiceInfo::BiquadFilterParameter2, MaxBiquadFilters> float_biquads{};
+
+ if (!use_v2) {
+ const auto* in_param_ptr = reinterpret_cast<const VoiceInfo::InParameter*>(
+ 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<f32, 3> b;
+ std::array<f32, 2> 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<VoiceInfo::WaveBufferInternal, MaxWaveBuffers> wavebuffers;
+ std::array<u32, MaxChannels> 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<const VoiceInParameterV2*>(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<s32>(vin->priority);
+ local_in.sort_order = static_cast<s32>(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<s16>(std::clamp(src.b[0] * 16384.0f, -32768.0f, 32767.0f));
+ dst.b[1] = static_cast<s16>(std::clamp(src.b[1] * 16384.0f, -32768.0f, 32767.0f));
+ dst.b[2] = static_cast<s16>(std::clamp(src.b[2] * 16384.0f, -32768.0f, 32767.0f));
+ dst.a[0] = static_cast<s16>(std::clamp(src.a[0] * 16384.0f, -32768.0f, 32767.0f));
+ dst.a[1] = static_cast<s16>(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<u16>(vin->wave_buffer_index);
+ local_in.src_data_address = static_cast<CpuAddr>(vin->src_data_address);
+ local_in.src_data_size = vin->src_data_size;
+ local_in.mix_id = static_cast<u32>(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<VoiceState*, MaxChannels> 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<u32>(sizeof(VoiceInfo::InParameter))};
+ auto consumed_input_size{voice_count * in_stride};
auto consumed_output_size{voice_count * static_cast<u32>(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<const MixInfo::InDirtyParameter*>(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<u32>(sizeof(MixInfo::InDirtyParameter));
input += sizeof(MixInfo::InDirtyParameter);
- consumed_input_size = static_cast<u32>(sizeof(MixInfo::InDirtyParameter) +
- mix_count * sizeof(MixInfo::InParameter));
} else {
mix_count = mix_context.GetCount();
- consumed_input_size = static_cast<u32>(mix_count * sizeof(MixInfo::InParameter));
}
+ input_mix_size = static_cast<u32>(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
@@ -52,6 +52,40 @@ void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
}
/**
+ * Biquad filter float implementation with native float coefficients.
+ */
+void ApplyBiquadFilterFloat2(std::span<s32> output, std::span<const s32> input,
+ std::array<f32, 3>& b, std::array<f32, 2>& a,
+ VoiceState::BiquadFilterState& state, const u32 sample_count) {
+ constexpr f64 min{std::numeric_limits<s32>::min()};
+ constexpr f64 max{std::numeric_limits<s32>::max()};
+
+ std::array<f64, 3> b_double{static_cast<f64>(b[0]), static_cast<f64>(b[1]),
+ static_cast<f64>(b[2])};
+ std::array<f64, 2> a_double{static_cast<f64>(a[0]), static_cast<f64>(a[1])};
+ std::array<f64, 4> s{Common::BitCast<f64>(state.s0), Common::BitCast<f64>(state.s1),
+ Common::BitCast<f64>(state.s2), Common::BitCast<f64>(state.s3)};
+
+ for (u32 i = 0; i < sample_count; i++) {
+ f64 in_sample{static_cast<f64>(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<s32>(std::clamp(sample, min, max));
+
+ s[1] = s[0];
+ s[0] = in_sample;
+ s[3] = s[2];
+ s[2] = sample;
+ }
+
+ state.s0 = Common::BitCast<s64>(s[0]);
+ state.s1 = Common::BitCast<s64>(s[1]);
+ state.s2 = Common::BitCast<s64>(s[2]);
+ state.s3 = Common::BitCast<s64>(s[3]);
+}
+
+/**
* Biquad filter s32 implementation.
*
* @param output - Output container for filtered samples.
@@ -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<s32> output, std::span<const s32> input,
std::array<s16, 3>& b, std::array<s16, 2>& 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<s32> output, std::span<const s32> input,
+ std::array<f32, 3>& b, std::array<f32, 2>& 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<VoiceInfo::BiquadFilterParameter, MaxBiquadFilters> biquads;
+ /// Biquad parameters (REV15+ native float)
+ std::array<VoiceInfo::BiquadFilterParameter2, MaxBiquadFilters> biquads_float;
/// Biquad states, updated each call
std::array<CpuAddr, MaxBiquadFilters> states;
/// If each biquad needs initialisation
std::array<bool, MaxBiquadFilters> 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<SplitterInfo> 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<const SplitterDestinationData::InParameter*>(input + offset)};
-
- if (data_header->magic != GetSplitterSendDataMagic()) {
- continue;
- }
-
- if (data_header->id < 0 || data_header->id > destinations_count) {
- continue;
+ // 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<const SplitterDestinationData::InParameter*>(input + offset);
+
+ 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<const SplitterDestinationData::InParameterVersion2a*>(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<float>(1 << 14);
+ out.numerator[0] = static_cast<float>(legacy.b[0]) * scale;
+ out.numerator[1] = static_cast<float>(legacy.b[1]) * scale;
+ out.numerator[2] = static_cast<float>(legacy.b[2]) * scale;
+ out.denominator[0] = static_cast<float>(legacy.a[0]) * scale;
+ out.denominator[1] = static_cast<float>(legacy.a[1]) * scale;
+ }
+
+ offset += static_cast<u32>(sizeof(SplitterDestinationData::InParameterVersion2a));
+ } else {
+ // Version 2b: struct contains extra biquad filter fields with float coeffs
+ const auto* data_header_v2b =
+ reinterpret_cast<const SplitterDestinationData::InParameterVersion2b*>(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<u32>(sizeof(SplitterDestinationData::InParameterVersion2b));
}
-
- 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<SplitterInfo> 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<SplitterInfo> 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::BiquadFilterParameter2>
+SplitterDestinationData::GetBiquadFilters() {
+ return biquad_filters;
+}
+
+std::span<const SplitterDestinationData::BiquadFilterParameter2>
+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<f32, 3> numerator; // b0, b1, b2
+ /* 0x10 */ std::array<f32, 2> 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<s16, 3> b; // numerator
+ /* 0x08 */ std::array<s16, 2> 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<f32, MaxMixBuffers> 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<f32, MaxMixBuffers> mix_volumes;
+ /* 0x68 */ u32 mix_id;
+ /* 0x6C */ std::array<SplitterDestinationData::BiquadFilterParameterLegacy, MaxBiquadFilters>
+ 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<f32, MaxMixBuffers> mix_volumes;
+ /* 0x68 */ u32 mix_id;
+ /* 0x6C */ std::array<SplitterDestinationData::BiquadFilterParameter2, MaxBiquadFilters>
+ 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<BiquadFilterParameter2> GetBiquadFilters();
+
+ /**
+ * Get const biquad filter parameters for this destination (REV15+ or mapped from REV12).
+ *
+ * @return Const span of biquad filter parameters.
+ */
+ std::span<const BiquadFilterParameter2> GetBiquadFilters() const;
+
private:
/// Id of this destination
const s32 id;
@@ -124,6 +197,8 @@ private:
std::array<f32, MaxMixBuffers> mix_volumes{0.0f};
/// Previous mix volumes
std::array<f32, MaxMixBuffers> prev_mix_volumes{0.0f};
+ /// Biquad filter parameters (REV15+ or mapped from REV12)
+ std::array<BiquadFilterParameter2, MaxBiquadFilters> 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<f32, 3> numerator; // b0, b1, b2
+ /* 0x10 */ std::array<f32, 2> 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<BiquadFilterParameter2, MaxBiquadFilters> 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<WaveBufferInternal, MaxWaveBuffers> wave_buffer_internal;
+ /* 0x158 */ std::array<s32, MaxChannels> 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<BiquadFilterParameter, MaxBiquadFilters> biquads{};
+ /// Float biquad filters for REV15+ (native float coefficients)
+ std::array<BiquadFilterParameter2, MaxBiquadFilters> 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<SharedPointer<ILibraryAppletAccessor>> 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<ILibraryAppletAccessor> 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<SharedPointer<IStorage>> 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<SharedPointer<ILibraryAppletAccessor>> out_library_applet_accessor, AppletId applet_id,
LibraryAppletMode library_applet_mode);
+ Result CreateLibraryAppletEx(
+ Out<SharedPointer<ILibraryAppletAccessor>> out_library_applet_accessor, AppletId applet_id,
+ LibraryAppletMode library_applet_mode, u64 thread_id);
Result CreateStorage(Out<SharedPointer<IStorage>> out_storage, s64 size);
Result CreateTransferMemoryStorage(
Out<SharedPointer<IStorage>> 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<Result> 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<BufferAttr_HipcAutoSelect> out_buffer_58,
+ InBuffer<BufferAttr_HipcMapAlias> 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<Kernel::KReadableEvent> out_event) {
LOG_WARNING(Service_NS, "(STUBBED) called");
@@ -539,4 +573,9 @@ Result IApplicationManagerInterface::Unknown4023(Out<u64> 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<Result> out_result, u64 application_id);
Result Unknown4022(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result Unknown4023(Out<u64> out_result);
+ Result Unknown4053();
+
+ Result RequestDownloadApplicationControlDataInBackground(u64 unk,
+ u64 application_id);
+ Result Unknown1706(OutBuffer<BufferAttr_HipcAutoSelect> out_buffer_58,
+ InBuffer<BufferAttr_HipcMapAlias> 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<u8, 0x8> 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<char, 0x200> application_name;
+ std::array<char, 0x100> 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<BufferAttr_HipcMapAlias> out_buffer, Out<u64> 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<BufferAttr_HipcMapAlias> out_buffer,
Out<u32> out_actual_size,
ApplicationControlSource application_control_source,
@@ -25,6 +27,10 @@ public:
u32 supported_languages);
Result ConvertApplicationLanguageToLanguageCode(Out<u64> out_language_code,
ApplicationLanguage application_language);
+
+ Result GetApplicationDisplayData(OutBuffer<BufferAttr_HipcMapAlias> out_buffer,
+ Out<u64> 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<ApplicationRecord, BufferAttr_HipcMapAlias> out_records, Out<s32> 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<bool> out_has_application_record, u64 program_id);
Result IsDataCorruptedResult(Out<bool> out_is_data_corrupted_result, Result result);
+ Result ListApplicationRecord(
+ OutArray<ApplicationRecord, BufferAttr_HipcMapAlias> out_records, Out<s32> 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<u32>(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