From a3ef2cc1838a007a2f424e88212aa6f1eb93eab3 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 Oct 2025 17:03:14 +0200 Subject: [PATCH] [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