diff options
55 files changed, 3835 insertions, 672 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b0891adf..2bef9d6ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -333,6 +333,7 @@ find_package(LLVM 17 MODULE COMPONENTS Demangle) find_package(lz4 REQUIRED) find_package(nlohmann_json 3.8 REQUIRED) find_package(Opus 1.3 MODULE) +find_package(RenderDoc MODULE) find_package(VulkanMemoryAllocator CONFIG) find_package(ZLIB 1.2 REQUIRED) find_package(zstd 1.5 REQUIRED) diff --git a/CMakeModules/FindRenderDoc.cmake b/CMakeModules/FindRenderDoc.cmake new file mode 100644 index 000000000..2678b936b --- /dev/null +++ b/CMakeModules/FindRenderDoc.cmake @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2023 Alexandre Bouvier <contact@amb.tf> +# +# SPDX-License-Identifier: GPL-3.0-or-later + +find_path(RenderDoc_INCLUDE_DIR renderdoc_app.h) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(RenderDoc + REQUIRED_VARS RenderDoc_INCLUDE_DIR +) + +if (RenderDoc_FOUND AND NOT TARGET RenderDoc::API) + add_library(RenderDoc::API INTERFACE IMPORTED) + set_target_properties(RenderDoc::API PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${RenderDoc_INCLUDE_DIR}" + ) +endif() + +mark_as_advanced(RenderDoc_INCLUDE_DIR) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index a4c2ffc10..9eebc7d65 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -174,8 +174,11 @@ target_include_directories(stb PUBLIC ./stb) add_library(bc_decoder bc_decoder/bc_decoder.cpp) target_include_directories(bc_decoder PUBLIC ./bc_decoder) -add_library(renderdoc INTERFACE) -target_include_directories(renderdoc SYSTEM INTERFACE ./renderdoc) +if (NOT TARGET RenderDoc::API) + add_library(renderdoc INTERFACE) + target_include_directories(renderdoc SYSTEM INTERFACE ./renderdoc) + add_library(RenderDoc::API ALIAS renderdoc) +endif() if (ANDROID) if (ARCHITECTURE_arm64) diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index fe79a701c..431f899b3 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -77,13 +77,30 @@ android { buildConfigField("String", "BRANCH", "\"${getBranch()}\"") } + val keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE") + if (keystoreFile != null) { + signingConfigs { + create("release") { + storeFile = file(keystoreFile) + storePassword = System.getenv("ANDROID_KEYSTORE_PASS") + keyAlias = System.getenv("ANDROID_KEY_ALIAS") + keyPassword = System.getenv("ANDROID_KEYSTORE_PASS") + } + } + } + // Define build types, which are orthogonal to product flavors. buildTypes { // Signed by release key, allowing for upload to Play Store. release { + signingConfig = if (keystoreFile != null) { + signingConfigs.getByName("release") + } else { + signingConfigs.getByName("debug") + } + resValue("string", "app_name_suffixed", "yuzu") - signingConfig = signingConfigs.getByName("debug") isMinifyEnabled = true isDebuggable = false proguardFiles( diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 67dfe0290..400988c5f 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -10,6 +10,13 @@ add_library(audio_core STATIC adsp/apps/audio_renderer/command_buffer.h adsp/apps/audio_renderer/command_list_processor.cpp adsp/apps/audio_renderer/command_list_processor.h + adsp/apps/opus/opus_decoder.cpp + adsp/apps/opus/opus_decoder.h + adsp/apps/opus/opus_decode_object.cpp + adsp/apps/opus/opus_decode_object.h + adsp/apps/opus/opus_multistream_decode_object.cpp + adsp/apps/opus/opus_multistream_decode_object.h + adsp/apps/opus/shared_memory.h audio_core.cpp audio_core.h audio_event.h @@ -35,6 +42,13 @@ add_library(audio_core STATIC in/audio_in.h in/audio_in_system.cpp in/audio_in_system.h + opus/hardware_opus.cpp + opus/hardware_opus.h + opus/decoder_manager.cpp + opus/decoder_manager.h + opus/decoder.cpp + opus/decoder.h + opus/parameters.h out/audio_out.cpp out/audio_out.h out/audio_out_system.cpp @@ -214,7 +228,7 @@ else() ) endif() -target_link_libraries(audio_core PUBLIC common core) +target_link_libraries(audio_core PUBLIC common core Opus::opus) if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) target_link_libraries(audio_core PRIVATE dynarmic::dynarmic) endif() diff --git a/src/audio_core/adsp/adsp.cpp b/src/audio_core/adsp/adsp.cpp index 0580990f5..6c53c98fd 100644 --- a/src/audio_core/adsp/adsp.cpp +++ b/src/audio_core/adsp/adsp.cpp @@ -7,12 +7,21 @@ namespace AudioCore::ADSP { ADSP::ADSP(Core::System& system, Sink::Sink& sink) { - audio_renderer = - std::make_unique<AudioRenderer::AudioRenderer>(system, system.ApplicationMemory(), sink); + audio_renderer = std::make_unique<AudioRenderer::AudioRenderer>(system, sink); + opus_decoder = std::make_unique<OpusDecoder::OpusDecoder>(system); + opus_decoder->Send(Direction::DSP, OpusDecoder::Message::Start); + if (opus_decoder->Receive(Direction::Host) != OpusDecoder::Message::StartOK) { + LOG_ERROR(Service_Audio, "OpusDeocder failed to initialize."); + return; + } } AudioRenderer::AudioRenderer& ADSP::AudioRenderer() { return *audio_renderer.get(); } +OpusDecoder::OpusDecoder& ADSP::OpusDecoder() { + return *opus_decoder.get(); +} + } // namespace AudioCore::ADSP diff --git a/src/audio_core/adsp/adsp.h b/src/audio_core/adsp/adsp.h index bd5bcc63b..a0c24a16a 100644 --- a/src/audio_core/adsp/adsp.h +++ b/src/audio_core/adsp/adsp.h @@ -4,6 +4,7 @@ #pragma once #include "audio_core/adsp/apps/audio_renderer/audio_renderer.h" +#include "audio_core/adsp/apps/opus/opus_decoder.h" #include "common/common_types.h" namespace Core { @@ -40,10 +41,12 @@ public: ~ADSP() = default; AudioRenderer::AudioRenderer& AudioRenderer(); + OpusDecoder::OpusDecoder& OpusDecoder(); private: /// AudioRenderer app std::unique_ptr<AudioRenderer::AudioRenderer> audio_renderer{}; + std::unique_ptr<OpusDecoder::OpusDecoder> opus_decoder{}; }; } // namespace ADSP diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp index 2e549bc6f..972d5e45b 100644 --- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp +++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp @@ -14,13 +14,12 @@ #include "core/core.h" #include "core/core_timing.h" -MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97)); +MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP_AudioRenderer", MP_RGB(60, 19, 97)); namespace AudioCore::ADSP::AudioRenderer { -AudioRenderer::AudioRenderer(Core::System& system_, Core::Memory::Memory& memory_, - Sink::Sink& sink_) - : system{system_}, memory{memory_}, sink{sink_} {} +AudioRenderer::AudioRenderer(Core::System& system_, Sink::Sink& sink_) + : system{system_}, sink{sink_} {} AudioRenderer::~AudioRenderer() { Stop(); @@ -33,8 +32,8 @@ void AudioRenderer::Start() { main_thread = std::jthread([this](std::stop_token stop_token) { Main(stop_token); }); - mailbox.Send(Direction::DSP, {Message::InitializeOK, {}}); - if (mailbox.Receive(Direction::Host).msg != Message::InitializeOK) { + mailbox.Send(Direction::DSP, Message::InitializeOK); + if (mailbox.Receive(Direction::Host) != Message::InitializeOK) { LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " "message response from ADSP!"); return; @@ -47,8 +46,8 @@ void AudioRenderer::Stop() { return; } - mailbox.Send(Direction::DSP, {Message::Shutdown, {}}); - if (mailbox.Receive(Direction::Host).msg != Message::Shutdown) { + mailbox.Send(Direction::DSP, Message::Shutdown); + if (mailbox.Receive(Direction::Host) != Message::Shutdown) { LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown " "message response from ADSP!"); } @@ -67,25 +66,25 @@ void AudioRenderer::Stop() { void AudioRenderer::Signal() { signalled_tick = system.CoreTiming().GetGlobalTimeNs().count(); - Send(Direction::DSP, {Message::Render, {}}); + Send(Direction::DSP, Message::Render); } void AudioRenderer::Wait() { - auto received = Receive(Direction::Host); - if (received.msg != Message::RenderResponse) { + auto msg = Receive(Direction::Host); + if (msg != Message::RenderResponse) { LOG_ERROR(Service_Audio, "Did not receive the expected render response from the AudioRenderer! Expected " "{}, got {}", - Message::RenderResponse, received.msg); + Message::RenderResponse, msg); } } -void AudioRenderer::Send(Direction dir, MailboxMessage message) { +void AudioRenderer::Send(Direction dir, u32 message) { mailbox.Send(dir, std::move(message)); } -MailboxMessage AudioRenderer::Receive(Direction dir, bool block) { - return mailbox.Receive(dir, block); +u32 AudioRenderer::Receive(Direction dir) { + return mailbox.Receive(dir); } void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit, @@ -120,7 +119,7 @@ void AudioRenderer::CreateSinkStreams() { } void AudioRenderer::Main(std::stop_token stop_token) { - static constexpr char name[]{"AudioRenderer"}; + static constexpr char name[]{"DSP_AudioRenderer_Main"}; MicroProfileOnThreadCreate(name); Common::SetCurrentThreadName(name); Common::SetCurrentThreadPriority(Common::ThreadPriority::High); @@ -128,28 +127,28 @@ void AudioRenderer::Main(std::stop_token stop_token) { // TODO: Create buffer map/unmap thread + mailbox // TODO: Create gMix devices, initialize them here - if (mailbox.Receive(Direction::DSP).msg != Message::InitializeOK) { + if (mailbox.Receive(Direction::DSP) != Message::InitializeOK) { LOG_ERROR(Service_Audio, "ADSP Audio Renderer -- Failed to receive initialize message from host!"); return; } - mailbox.Send(Direction::Host, {Message::InitializeOK, {}}); + mailbox.Send(Direction::Host, Message::InitializeOK); // 0.12 seconds (2,304,000 / 19,200,000) constexpr u64 max_process_time{2'304'000ULL}; while (!stop_token.stop_requested()) { - auto received{mailbox.Receive(Direction::DSP)}; - switch (received.msg) { + auto msg{mailbox.Receive(Direction::DSP)}; + switch (msg) { case Message::Shutdown: - mailbox.Send(Direction::Host, {Message::Shutdown, {}}); + mailbox.Send(Direction::Host, Message::Shutdown); return; case Message::Render: { if (system.IsShuttingDown()) [[unlikely]] { std::this_thread::sleep_for(std::chrono::milliseconds(5)); - mailbox.Send(Direction::Host, {Message::RenderResponse, {}}); + mailbox.Send(Direction::Host, Message::RenderResponse); continue; } std::array<bool, MaxRendererSessions> buffers_reset{}; @@ -205,13 +204,12 @@ void AudioRenderer::Main(std::stop_token stop_token) { } } - mailbox.Send(Direction::Host, {Message::RenderResponse, {}}); + mailbox.Send(Direction::Host, Message::RenderResponse); } break; default: LOG_WARNING(Service_Audio, - "ADSP AudioRenderer received an invalid message, msg={:02X}!", - received.msg); + "ADSP AudioRenderer received an invalid message, msg={:02X}!", msg); break; } } diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h index 3f5b7dca2..85874d88a 100644 --- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h +++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h @@ -17,13 +17,6 @@ namespace Core { class System; -namespace Timing { -struct EventType; -} -namespace Memory { -class Memory; -} -class System; } // namespace Core namespace AudioCore { @@ -34,19 +27,19 @@ class Sink; namespace ADSP::AudioRenderer { enum Message : u32 { - Invalid = 0x00, - MapUnmap_Map = 0x01, - MapUnmap_MapResponse = 0x02, - MapUnmap_Unmap = 0x03, - MapUnmap_UnmapResponse = 0x04, - MapUnmap_InvalidateCache = 0x05, - MapUnmap_InvalidateCacheResponse = 0x06, - MapUnmap_Shutdown = 0x07, - MapUnmap_ShutdownResponse = 0x08, - InitializeOK = 0x16, - RenderResponse = 0x20, - Render = 0x2A, - Shutdown = 0x34, + Invalid = 0, + MapUnmap_Map = 1, + MapUnmap_MapResponse = 2, + MapUnmap_Unmap = 3, + MapUnmap_UnmapResponse = 4, + MapUnmap_InvalidateCache = 5, + MapUnmap_InvalidateCacheResponse = 6, + MapUnmap_Shutdown = 7, + MapUnmap_ShutdownResponse = 8, + InitializeOK = 22, + RenderResponse = 32, + Render = 42, + Shutdown = 52, }; /** @@ -54,7 +47,7 @@ enum Message : u32 { */ class AudioRenderer { public: - explicit AudioRenderer(Core::System& system, Core::Memory::Memory& memory, Sink::Sink& sink); + explicit AudioRenderer(Core::System& system, Sink::Sink& sink); ~AudioRenderer(); /** @@ -72,8 +65,8 @@ public: void Signal(); void Wait(); - void Send(Direction dir, MailboxMessage message); - MailboxMessage Receive(Direction dir, bool block = true); + void Send(Direction dir, u32 message); + u32 Receive(Direction dir); void SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit, u64 applet_resource_user_id, bool reset) noexcept; @@ -94,9 +87,7 @@ private: /// Core system Core::System& system; - /// Memory - Core::Memory::Memory& memory; - /// The output sink the AudioRenderer will use + /// The output sink the AudioRenderer will send samples to Sink::Sink& sink; /// The active mailbox Mailbox mailbox; @@ -104,11 +95,13 @@ private: std::jthread main_thread{}; /// The current state std::atomic<bool> running{}; + /// Shared memory of input command buffers, set by host, read by DSP std::array<CommandBuffer, MaxRendererSessions> command_buffers{}; /// The command lists to process std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{}; /// The streams which will receive the processed samples std::array<Sink::SinkStream*, MaxRendererSessions> streams{}; + /// CPU Tick when the DSP was signalled to process, uses time rather than tick u64 signalled_tick{0}; }; diff --git a/src/audio_core/adsp/apps/opus/opus_decode_object.cpp b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp new file mode 100644 index 000000000..2c16d3769 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/adsp/apps/opus/opus_decode_object.h"
+#include "common/assert.h"
+
+namespace AudioCore::ADSP::OpusDecoder {
+namespace {
+bool IsValidChannelCount(u32 channel_count) {
+ return channel_count == 1 || channel_count == 2;
+}
+} // namespace
+
+u32 OpusDecodeObject::GetWorkBufferSize(u32 channel_count) {
+ if (!IsValidChannelCount(channel_count)) {
+ return 0;
+ }
+ return static_cast<u32>(sizeof(OpusDecodeObject)) + opus_decoder_get_size(channel_count);
+}
+
+OpusDecodeObject& OpusDecodeObject::Initialize(u64 buffer, u64 buffer2) {
+ auto* new_decoder = reinterpret_cast<OpusDecodeObject*>(buffer);
+ auto* comparison = reinterpret_cast<OpusDecodeObject*>(buffer2);
+
+ if (new_decoder->magic == DecodeObjectMagic) {
+ if (!new_decoder->initialized ||
+ (new_decoder->initialized && new_decoder->self == comparison)) {
+ new_decoder->state_valid = true;
+ }
+ } else {
+ new_decoder->initialized = false;
+ new_decoder->state_valid = true;
+ }
+ return *new_decoder;
+}
+
+s32 OpusDecodeObject::InitializeDecoder(u32 sample_rate, u32 channel_count) {
+ if (!state_valid) {
+ return OPUS_INVALID_STATE;
+ }
+
+ if (initialized) {
+ return OPUS_OK;
+ }
+
+ // Unfortunately libopus does not expose the OpusDecoder struct publicly, so we can't include
+ // it in this class. Nintendo does not allocate memory, which is why we have a workbuffer
+ // provided.
+ // We could use _create and have libopus allocate it for us, but then we have to separately
+ // track which decoder is being used between this and multistream in order to call the correct
+ // destroy from the host side.
+ // This is a bit cringe, but is safe as these objects are only ever initialized inside the given
+ // workbuffer, and GetWorkBufferSize will guarantee there's enough space to follow.
+ decoder = (LibOpusDecoder*)(this + 1);
+ s32 ret = opus_decoder_init(decoder, sample_rate, channel_count);
+ if (ret == OPUS_OK) {
+ magic = DecodeObjectMagic;
+ initialized = true;
+ state_valid = true;
+ self = this;
+ final_range = 0;
+ }
+ return ret;
+}
+
+s32 OpusDecodeObject::Shutdown() {
+ if (!state_valid) {
+ return OPUS_INVALID_STATE;
+ }
+
+ if (initialized) {
+ magic = 0x0;
+ initialized = false;
+ state_valid = false;
+ self = nullptr;
+ final_range = 0;
+ decoder = nullptr;
+ }
+ return OPUS_OK;
+}
+
+s32 OpusDecodeObject::ResetDecoder() {
+ return opus_decoder_ctl(decoder, OPUS_RESET_STATE);
+}
+
+s32 OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size,
+ u64 input_data, u64 input_data_size) {
+ ASSERT(initialized);
+ out_sample_count = 0;
+
+ if (!state_valid) {
+ return OPUS_INVALID_STATE;
+ }
+
+ auto ret_code_or_samples = opus_decode(
+ decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
+ reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
+
+ if (ret_code_or_samples < OPUS_OK) {
+ return ret_code_or_samples;
+ }
+
+ out_sample_count = ret_code_or_samples;
+ return opus_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
+}
+
+} // namespace AudioCore::ADSP::OpusDecoder
diff --git a/src/audio_core/adsp/apps/opus/opus_decode_object.h b/src/audio_core/adsp/apps/opus/opus_decode_object.h new file mode 100644 index 000000000..6425f987c --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_decode_object.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <opus.h> + +#include "common/common_types.h" + +namespace AudioCore::ADSP::OpusDecoder { +using LibOpusDecoder = ::OpusDecoder; +static constexpr u32 DecodeObjectMagic = 0xDEADBEEF; + +class OpusDecodeObject { +public: + static u32 GetWorkBufferSize(u32 channel_count); + static OpusDecodeObject& Initialize(u64 buffer, u64 buffer2); + + s32 InitializeDecoder(u32 sample_rate, u32 channel_count); + s32 Shutdown(); + s32 ResetDecoder(); + s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, + u64 input_data_size); + u32 GetFinalRange() const noexcept { + return final_range; + } + +private: + u32 magic; + bool initialized; + bool state_valid; + OpusDecodeObject* self; + u32 final_range; + LibOpusDecoder* decoder; +}; +static_assert(std::is_trivially_constructible_v<OpusDecodeObject>); + +} // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/audio_core/adsp/apps/opus/opus_decoder.cpp b/src/audio_core/adsp/apps/opus/opus_decoder.cpp new file mode 100644 index 000000000..2084de128 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_decoder.cpp @@ -0,0 +1,269 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <array> +#include <chrono> + +#include "audio_core/adsp/apps/opus/opus_decode_object.h" +#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h" +#include "audio_core/adsp/apps/opus/shared_memory.h" +#include "audio_core/audio_core.h" +#include "audio_core/common/common.h" +#include "common/logging/log.h" +#include "common/microprofile.h" +#include "common/thread.h" +#include "core/core.h" +#include "core/core_timing.h" + +MICROPROFILE_DEFINE(OpusDecoder, "Audio", "DSP_OpusDecoder", MP_RGB(60, 19, 97)); + +namespace AudioCore::ADSP::OpusDecoder { + +namespace { +constexpr size_t OpusStreamCountMax = 255; + +bool IsValidChannelCount(u32 channel_count) { + return channel_count == 1 || channel_count == 2; +} + +bool IsValidMultiStreamChannelCount(u32 channel_count) { + return channel_count <= OpusStreamCountMax; +} + +bool IsValidMultiStreamStreamCounts(s32 total_stream_count, s32 sterero_stream_count) { + return IsValidMultiStreamChannelCount(total_stream_count) && total_stream_count > 0 && + sterero_stream_count > 0 && sterero_stream_count <= total_stream_count; +} +} // namespace + +OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} { + init_thread = std::jthread([this](std::stop_token stop_token) { Init(stop_token); }); +} + +OpusDecoder::~OpusDecoder() { + if (!running) { + init_thread.request_stop(); + return; + } + + // Shutdown the thread + Send(Direction::DSP, Message::Shutdown); + auto msg = Receive(Direction::Host); + ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}", + Message::ShutdownOK, msg); + main_thread.request_stop(); + main_thread.join(); + running = false; +} + +void OpusDecoder::Send(Direction dir, u32 message) { + mailbox.Send(dir, std::move(message)); +} + +u32 OpusDecoder::Receive(Direction dir, std::stop_token stop_token) { + return mailbox.Receive(dir, stop_token); +} + +void OpusDecoder::Init(std::stop_token stop_token) { + Common::SetCurrentThreadName("DSP_OpusDecoder_Init"); + + if (Receive(Direction::DSP, stop_token) != Message::Start) { + LOG_ERROR(Service_Audio, + "DSP OpusDecoder failed to receive Start message. Opus initialization failed."); + return; + } + main_thread = std::jthread([this](std::stop_token st) { Main(st); }); + running = true; + Send(Direction::Host, Message::StartOK); +} + +void OpusDecoder::Main(std::stop_token stop_token) { + Common::SetCurrentThreadName("DSP_OpusDecoder_Main"); + + while (!stop_token.stop_requested()) { + auto msg = Receive(Direction::DSP, stop_token); + switch (msg) { + case Shutdown: + Send(Direction::Host, Message::ShutdownOK); + return; + + case GetWorkBufferSize: { + auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]); + + ASSERT(IsValidChannelCount(channel_count)); + + shared_memory->dsp_return_data[0] = OpusDecodeObject::GetWorkBufferSize(channel_count); + Send(Direction::Host, Message::GetWorkBufferSizeOK); + } break; + + case InitializeDecodeObject: { + auto buffer = shared_memory->host_send_data[0]; + auto buffer_size = shared_memory->host_send_data[1]; + auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]); + auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]); + + ASSERT(sample_rate >= 0); + ASSERT(IsValidChannelCount(channel_count)); + ASSERT(buffer_size >= OpusDecodeObject::GetWorkBufferSize(channel_count)); + + auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer); + shared_memory->dsp_return_data[0] = + decoder_object.InitializeDecoder(sample_rate, channel_count); + + Send(Direction::Host, Message::InitializeDecodeObjectOK); + } break; + + case ShutdownDecodeObject: { + auto buffer = shared_memory->host_send_data[0]; + [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; + + auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer); + shared_memory->dsp_return_data[0] = decoder_object.Shutdown(); + + Send(Direction::Host, Message::ShutdownDecodeObjectOK); + } break; + + case DecodeInterleaved: { + auto start_time = system.CoreTiming().GetGlobalTimeUs(); + + auto buffer = shared_memory->host_send_data[0]; + auto input_data = shared_memory->host_send_data[1]; + auto input_data_size = shared_memory->host_send_data[2]; + auto output_data = shared_memory->host_send_data[3]; + auto output_data_size = shared_memory->host_send_data[4]; + auto final_range = static_cast<u32>(shared_memory->host_send_data[5]); + auto reset_requested = shared_memory->host_send_data[6]; + + u32 decoded_samples{0}; + + auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer); + s32 error_code{OPUS_OK}; + if (reset_requested) { + error_code = decoder_object.ResetDecoder(); + } + + if (error_code == OPUS_OK) { + error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size, + input_data, input_data_size); + } + + if (error_code == OPUS_OK) { + if (final_range && decoder_object.GetFinalRange() != final_range) { + error_code = OPUS_INVALID_PACKET; + } + } + + auto end_time = system.CoreTiming().GetGlobalTimeUs(); + shared_memory->dsp_return_data[0] = error_code; + shared_memory->dsp_return_data[1] = decoded_samples; + shared_memory->dsp_return_data[2] = (end_time - start_time).count(); + + Send(Direction::Host, Message::DecodeInterleavedOK); + } break; + + case MapMemory: { + [[maybe_unused]] auto buffer = shared_memory->host_send_data[0]; + [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; + Send(Direction::Host, Message::MapMemoryOK); + } break; + + case UnmapMemory: { + [[maybe_unused]] auto buffer = shared_memory->host_send_data[0]; + [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; + Send(Direction::Host, Message::UnmapMemoryOK); + } break; + + case GetWorkBufferSizeForMultiStream: { + auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]); + auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]); + + ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count)); + + shared_memory->dsp_return_data[0] = OpusMultiStreamDecodeObject::GetWorkBufferSize( + total_stream_count, stereo_stream_count); + Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK); + } break; + + case InitializeMultiStreamDecodeObject: { + auto buffer = shared_memory->host_send_data[0]; + auto buffer_size = shared_memory->host_send_data[1]; + auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]); + auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]); + auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[4]); + auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[5]); + // Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel + // mappings, but [6] is never set, and there is not enough room in the argument data for + // more than 40 channels, when 255 are possible. + // It also means the mapping values are undefined, though likely always 0, + // and the mappings given by the game are ignored. The mappings are copied to this + // dedicated buffer host side, so let's do as intended. + auto mappings = shared_memory->channel_mapping.data(); + + ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count)); + ASSERT(sample_rate >= 0); + ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize( + total_stream_count, stereo_stream_count)); + + auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer); + shared_memory->dsp_return_data[0] = decoder_object.InitializeDecoder( + sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings); + + Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK); + } break; + + case ShutdownMultiStreamDecodeObject: { + auto buffer = shared_memory->host_send_data[0]; + [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; + + auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer); + shared_memory->dsp_return_data[0] = decoder_object.Shutdown(); + + Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK); + } break; + + case DecodeInterleavedForMultiStream: { + auto start_time = system.CoreTiming().GetGlobalTimeUs(); + + auto buffer = shared_memory->host_send_data[0]; + auto input_data = shared_memory->host_send_data[1]; + auto input_data_size = shared_memory->host_send_data[2]; + auto output_data = shared_memory->host_send_data[3]; + auto output_data_size = shared_memory->host_send_data[4]; + auto final_range = static_cast<u32>(shared_memory->host_send_data[5]); + auto reset_requested = shared_memory->host_send_data[6]; + + u32 decoded_samples{0}; + + auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer); + s32 error_code{OPUS_OK}; + if (reset_requested) { + error_code = decoder_object.ResetDecoder(); + } + + if (error_code == OPUS_OK) { + error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size, + input_data, input_data_size); + } + + if (error_code == OPUS_OK) { + if (final_range && decoder_object.GetFinalRange() != final_range) { + error_code = OPUS_INVALID_PACKET; + } + } + + auto end_time = system.CoreTiming().GetGlobalTimeUs(); + shared_memory->dsp_return_data[0] = error_code; + shared_memory->dsp_return_data[1] = decoded_samples; + shared_memory->dsp_return_data[2] = (end_time - start_time).count(); + + Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK); + } break; + + default: + LOG_ERROR(Service_Audio, "Invalid OpusDecoder command {}", msg); + continue; + } + } +} + +} // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/audio_core/adsp/apps/opus/opus_decoder.h b/src/audio_core/adsp/apps/opus/opus_decoder.h new file mode 100644 index 000000000..fcb89bb40 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_decoder.h @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <memory> +#include <thread> + +#include "audio_core/adsp/apps/opus/shared_memory.h" +#include "audio_core/adsp/mailbox.h" +#include "common/common_types.h" + +namespace Core { +class System; +} // namespace Core + +namespace AudioCore::ADSP::OpusDecoder { + +enum Message : u32 { + Invalid = 0, + Start = 1, + Shutdown = 2, + StartOK = 11, + ShutdownOK = 12, + GetWorkBufferSize = 21, + InitializeDecodeObject = 22, + ShutdownDecodeObject = 23, + DecodeInterleaved = 24, + MapMemory = 25, + UnmapMemory = 26, + GetWorkBufferSizeForMultiStream = 27, + InitializeMultiStreamDecodeObject = 28, + ShutdownMultiStreamDecodeObject = 29, + DecodeInterleavedForMultiStream = 30, + + GetWorkBufferSizeOK = 41, + InitializeDecodeObjectOK = 42, + ShutdownDecodeObjectOK = 43, + DecodeInterleavedOK = 44, + MapMemoryOK = 45, + UnmapMemoryOK = 46, + GetWorkBufferSizeForMultiStreamOK = 47, + InitializeMultiStreamDecodeObjectOK = 48, + ShutdownMultiStreamDecodeObjectOK = 49, + DecodeInterleavedForMultiStreamOK = 50, +}; + +/** + * The AudioRenderer application running on the ADSP. + */ +class OpusDecoder { +public: + explicit OpusDecoder(Core::System& system); + ~OpusDecoder(); + + bool IsRunning() const noexcept { + return running; + } + + void Send(Direction dir, u32 message); + u32 Receive(Direction dir, std::stop_token stop_token = {}); + + void SetSharedMemory(SharedMemory& shared_memory_) { + shared_memory = &shared_memory_; + } + +private: + /** + * Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread. + */ + void Init(std::stop_token stop_token); + /** + * Main OpusDecoder thread, responsible for processing the incoming Opus packets. + */ + void Main(std::stop_token stop_token); + + /// Core system + Core::System& system; + /// Mailbox to communicate messages with the host, drives the main thread + Mailbox mailbox; + /// Init thread + std::jthread init_thread{}; + /// Main thread + std::jthread main_thread{}; + /// The current state + bool running{}; + /// Structure shared with the host, input data set by the host before sending a mailbox message, + /// and the responses are written back by the OpusDecoder. + SharedMemory* shared_memory{}; +}; + +} // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp new file mode 100644 index 000000000..f6d362e68 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
+#include "common/assert.h"
+
+namespace AudioCore::ADSP::OpusDecoder {
+
+namespace {
+bool IsValidChannelCount(u32 channel_count) {
+ return channel_count == 1 || channel_count == 2;
+}
+
+bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
+ return total_stream_count > 0 && stereo_stream_count > 0 &&
+ stereo_stream_count <= total_stream_count && IsValidChannelCount(total_stream_count);
+}
+} // namespace
+
+u32 OpusMultiStreamDecodeObject::GetWorkBufferSize(u32 total_stream_count,
+ u32 stereo_stream_count) {
+ if (IsValidStreamCounts(total_stream_count, stereo_stream_count)) {
+ return static_cast<u32>(sizeof(OpusMultiStreamDecodeObject)) +
+ opus_multistream_decoder_get_size(total_stream_count, stereo_stream_count);
+ }
+ return 0;
+}
+
+OpusMultiStreamDecodeObject& OpusMultiStreamDecodeObject::Initialize(u64 buffer, u64 buffer2) {
+ auto* new_decoder = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer);
+ auto* comparison = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer2);
+
+ if (new_decoder->magic == DecodeMultiStreamObjectMagic) {
+ if (!new_decoder->initialized ||
+ (new_decoder->initialized && new_decoder->self == comparison)) {
+ new_decoder->state_valid = true;
+ }
+ } else {
+ new_decoder->initialized = false;
+ new_decoder->state_valid = true;
+ }
+ return *new_decoder;
+}
+
+s32 OpusMultiStreamDecodeObject::InitializeDecoder(u32 sample_rate, u32 total_stream_count,
+ u32 channel_count, u32 stereo_stream_count,
+ u8* mappings) {
+ if (!state_valid) {
+ return OPUS_INVALID_STATE;
+ }
+
+ if (initialized) {
+ return OPUS_OK;
+ }
+
+ // See OpusDecodeObject::InitializeDecoder for an explanation of this
+ decoder = (LibOpusMSDecoder*)(this + 1);
+ s32 ret = opus_multistream_decoder_init(decoder, sample_rate, channel_count, total_stream_count,
+ stereo_stream_count, mappings);
+ if (ret == OPUS_OK) {
+ magic = DecodeMultiStreamObjectMagic;
+ initialized = true;
+ state_valid = true;
+ self = this;
+ final_range = 0;
+ }
+ return ret;
+}
+
+s32 OpusMultiStreamDecodeObject::Shutdown() {
+ if (!state_valid) {
+ return OPUS_INVALID_STATE;
+ }
+
+ if (initialized) {
+ magic = 0x0;
+ initialized = false;
+ state_valid = false;
+ self = nullptr;
+ final_range = 0;
+ decoder = nullptr;
+ }
+ return OPUS_OK;
+}
+
+s32 OpusMultiStreamDecodeObject::ResetDecoder() {
+ return opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE);
+}
+
+s32 OpusMultiStreamDecodeObject::Decode(u32& out_sample_count, u64 output_data,
+ u64 output_data_size, u64 input_data, u64 input_data_size) {
+ ASSERT(initialized);
+ out_sample_count = 0;
+
+ if (!state_valid) {
+ return OPUS_INVALID_STATE;
+ }
+
+ auto ret_code_or_samples = opus_multistream_decode(
+ decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
+ reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
+
+ if (ret_code_or_samples < OPUS_OK) {
+ return ret_code_or_samples;
+ }
+
+ out_sample_count = ret_code_or_samples;
+ return opus_multistream_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
+}
+
+} // namespace AudioCore::ADSP::OpusDecoder
diff --git a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h new file mode 100644 index 000000000..93558ded5 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <opus_multistream.h> + +#include "common/common_types.h" + +namespace AudioCore::ADSP::OpusDecoder { +using LibOpusMSDecoder = ::OpusMSDecoder; +static constexpr u32 DecodeMultiStreamObjectMagic = 0xDEADBEEF; + +class OpusMultiStreamDecodeObject { +public: + static u32 GetWorkBufferSize(u32 total_stream_count, u32 stereo_stream_count); + static OpusMultiStreamDecodeObject& Initialize(u64 buffer, u64 buffer2); + + s32 InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count, + u32 stereo_stream_count, u8* mappings); + s32 Shutdown(); + s32 ResetDecoder(); + s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, + u64 input_data_size); + u32 GetFinalRange() const noexcept { + return final_range; + } + +private: + u32 magic; + bool initialized; + bool state_valid; + OpusMultiStreamDecodeObject* self; + u32 final_range; + LibOpusMSDecoder* decoder; +}; +static_assert(std::is_trivially_constructible_v<OpusMultiStreamDecodeObject>); + +} // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/audio_core/adsp/apps/opus/shared_memory.h b/src/audio_core/adsp/apps/opus/shared_memory.h new file mode 100644 index 000000000..c696731ed --- /dev/null +++ b/src/audio_core/adsp/apps/opus/shared_memory.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace AudioCore::ADSP::OpusDecoder { + +struct SharedMemory { + std::array<u8, 0x100> channel_mapping{}; + std::array<u64, 16> host_send_data{}; + std::array<u64, 16> dsp_return_data{}; +}; + +} // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/audio_core/adsp/mailbox.h b/src/audio_core/adsp/mailbox.h index c31b73717..1dd40ebfa 100644 --- a/src/audio_core/adsp/mailbox.h +++ b/src/audio_core/adsp/mailbox.h @@ -3,6 +3,8 @@ #pragma once +#include <span> + #include "common/bounded_threadsafe_queue.h" #include "common/common_types.h" @@ -19,11 +21,6 @@ enum class Direction : u32 { DSP, }; -struct MailboxMessage { - u32 msg; - std::span<u8> data; -}; - class Mailbox { public: void Initialize(AppMailboxId id_) { @@ -35,25 +32,19 @@ public: return id; } - void Send(Direction dir, MailboxMessage&& message) { + void Send(Direction dir, u32 message) { auto& queue = dir == Direction::Host ? host_queue : adsp_queue; - queue.EmplaceWait(std::move(message)); + queue.EmplaceWait(message); } - MailboxMessage Receive(Direction dir, bool block = true) { + u32 Receive(Direction dir, std::stop_token stop_token = {}) { auto& queue = dir == Direction::Host ? host_queue : adsp_queue; - MailboxMessage t; - if (block) { - queue.PopWait(t); - } else { - queue.TryPop(t); - } - return t; + return queue.PopWait(stop_token); } void Reset() { id = AppMailboxId::Invalid; - MailboxMessage t; + u32 t{}; while (host_queue.TryPop(t)) { } while (adsp_queue.TryPop(t)) { @@ -62,8 +53,8 @@ public: private: AppMailboxId id{0}; - Common::SPSCQueue<MailboxMessage> host_queue; - Common::SPSCQueue<MailboxMessage> adsp_queue; + Common::SPSCQueue<u32> host_queue; + Common::SPSCQueue<u32> adsp_queue; }; } // namespace AudioCore::ADSP diff --git a/src/audio_core/opus/decoder.cpp b/src/audio_core/opus/decoder.cpp new file mode 100644 index 000000000..5b23fce14 --- /dev/null +++ b/src/audio_core/opus/decoder.cpp @@ -0,0 +1,179 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/opus/decoder.h"
+#include "audio_core/opus/hardware_opus.h"
+#include "audio_core/opus/parameters.h"
+#include "common/alignment.h"
+#include "common/swap.h"
+#include "core/core.h"
+
+namespace AudioCore::OpusDecoder {
+using namespace Service::Audio;
+namespace {
+OpusPacketHeader ReverseHeader(OpusPacketHeader header) {
+ OpusPacketHeader out;
+ out.size = Common::swap32(header.size);
+ out.final_range = Common::swap32(header.final_range);
+ return out;
+}
+} // namespace
+
+OpusDecoder::OpusDecoder(Core::System& system_, HardwareOpus& hardware_opus_)
+ : system{system_}, hardware_opus{hardware_opus_} {}
+
+OpusDecoder::~OpusDecoder() {
+ if (decode_object_initialized) {
+ hardware_opus.ShutdownDecodeObject(shared_buffer.get(), shared_buffer_size);
+ }
+}
+
+Result OpusDecoder::Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory,
+ u64 transfer_memory_size) {
+ auto frame_size{params.use_large_frame_size ? 5760 : 1920};
+ shared_buffer_size = transfer_memory_size;
+ shared_buffer = std::make_unique<u8[]>(shared_buffer_size);
+ shared_memory_mapped = true;
+
+ buffer_size =
+ Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16);
+
+ out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size};
+ size_t in_data_size{0x600u};
+ in_data = {out_data.data() - in_data_size, in_data_size};
+
+ ON_RESULT_FAILURE {
+ if (shared_memory_mapped) {
+ shared_memory_mapped = false;
+ ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size)));
+ }
+ };
+
+ R_TRY(hardware_opus.InitializeDecodeObject(params.sample_rate, params.channel_count,
+ shared_buffer.get(), shared_buffer_size));
+
+ sample_rate = params.sample_rate;
+ channel_count = params.channel_count;
+ use_large_frame_size = params.use_large_frame_size;
+ decode_object_initialized = true;
+ R_SUCCEED();
+}
+
+Result OpusDecoder::Initialize(OpusMultiStreamParametersEx& params,
+ Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) {
+ auto frame_size{params.use_large_frame_size ? 5760 : 1920};
+ shared_buffer_size = transfer_memory_size;
+ shared_buffer = std::make_unique<u8[]>(shared_buffer_size);
+ shared_memory_mapped = true;
+
+ buffer_size =
+ Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16);
+
+ out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size};
+ size_t in_data_size{Common::AlignUp(1500ull * params.total_stream_count, 64u)};
+ in_data = {out_data.data() - in_data_size, in_data_size};
+
+ ON_RESULT_FAILURE {
+ if (shared_memory_mapped) {
+ shared_memory_mapped = false;
+ ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size)));
+ }
+ };
+
+ R_TRY(hardware_opus.InitializeMultiStreamDecodeObject(
+ params.sample_rate, params.channel_count, params.total_stream_count,
+ params.stereo_stream_count, params.mappings.data(), shared_buffer.get(),
+ shared_buffer_size));
+
+ sample_rate = params.sample_rate;
+ channel_count = params.channel_count;
+ total_stream_count = params.total_stream_count;
+ stereo_stream_count = params.stereo_stream_count;
+ use_large_frame_size = params.use_large_frame_size;
+ decode_object_initialized = true;
+ R_SUCCEED();
+}
+
+Result OpusDecoder::DecodeInterleaved(u32* out_data_size, u64* out_time_taken,
+ u32* out_sample_count, std::span<const u8> input_data,
+ std::span<u8> output_data, bool reset) {
+ u32 out_samples;
+ u64 time_taken{};
+
+ R_UNLESS(input_data.size_bytes() > sizeof(OpusPacketHeader), ResultInputDataTooSmall);
+
+ auto* header_p{reinterpret_cast<const OpusPacketHeader*>(input_data.data())};
+ OpusPacketHeader header{ReverseHeader(*header_p)};
+
+ R_UNLESS(in_data.size_bytes() >= header.size &&
+ header.size + sizeof(OpusPacketHeader) <= input_data.size_bytes(),
+ ResultBufferTooSmall);
+
+ if (!shared_memory_mapped) {
+ R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size));
+ shared_memory_mapped = true;
+ }
+
+ std::memcpy(in_data.data(), input_data.data() + sizeof(OpusPacketHeader), header.size);
+
+ R_TRY(hardware_opus.DecodeInterleaved(out_samples, out_data.data(), out_data.size_bytes(),
+ channel_count, in_data.data(), header.size,
+ shared_buffer.get(), time_taken, reset));
+
+ std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16));
+
+ *out_data_size = header.size + sizeof(OpusPacketHeader);
+ *out_sample_count = out_samples;
+ if (out_time_taken) {
+ *out_time_taken = time_taken / 1000;
+ }
+ R_SUCCEED();
+}
+
+Result OpusDecoder::SetContext([[maybe_unused]] std::span<const u8> context) {
+ R_SUCCEED_IF(shared_memory_mapped);
+ shared_memory_mapped = true;
+ R_RETURN(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size));
+}
+
+Result OpusDecoder::DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken,
+ u32* out_sample_count,
+ std::span<const u8> input_data,
+ std::span<u8> output_data, bool reset) {
+ u32 out_samples;
+ u64 time_taken{};
+
+ R_UNLESS(input_data.size_bytes() > sizeof(OpusPacketHeader), ResultInputDataTooSmall);
+
+ auto* header_p{reinterpret_cast<const OpusPacketHeader*>(input_data.data())};
+ OpusPacketHeader header{ReverseHeader(*header_p)};
+
+ LOG_ERROR(Service_Audio, "header size 0x{:X} input data size 0x{:X} in_data size 0x{:X}",
+ header.size, input_data.size_bytes(), in_data.size_bytes());
+
+ R_UNLESS(in_data.size_bytes() >= header.size &&
+ header.size + sizeof(OpusPacketHeader) <= input_data.size_bytes(),
+ ResultBufferTooSmall);
+
+ if (!shared_memory_mapped) {
+ R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size));
+ shared_memory_mapped = true;
+ }
+
+ std::memcpy(in_data.data(), input_data.data() + sizeof(OpusPacketHeader), header.size);
+
+ R_TRY(hardware_opus.DecodeInterleavedForMultiStream(
+ out_samples, out_data.data(), out_data.size_bytes(), channel_count, in_data.data(),
+ header.size, shared_buffer.get(), time_taken, reset));
+
+ std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16));
+
+ *out_data_size = header.size + sizeof(OpusPacketHeader);
+ *out_sample_count = out_samples;
+ if (out_time_taken) {
+ *out_time_taken = time_taken / 1000;
+ }
+ R_SUCCEED();
+}
+
+} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/decoder.h b/src/audio_core/opus/decoder.h new file mode 100644 index 000000000..d08d8a4a4 --- /dev/null +++ b/src/audio_core/opus/decoder.h @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "audio_core/opus/parameters.h"
+#include "common/common_types.h"
+#include "core/hle/kernel/k_transfer_memory.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace Core {
+class System;
+}
+
+namespace AudioCore::OpusDecoder {
+class HardwareOpus;
+
+class OpusDecoder {
+public:
+ explicit OpusDecoder(Core::System& system, HardwareOpus& hardware_opus_);
+ ~OpusDecoder();
+
+ Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory,
+ u64 transfer_memory_size);
+ Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory,
+ u64 transfer_memory_size);
+ Result DecodeInterleaved(u32* out_data_size, u64* out_time_taken, u32* out_sample_count,
+ std::span<const u8> input_data, std::span<u8> output_data, bool reset);
+ Result SetContext([[maybe_unused]] std::span<const u8> context);
+ Result DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken,
+ u32* out_sample_count, std::span<const u8> input_data,
+ std::span<u8> output_data, bool reset);
+
+private:
+ Core::System& system;
+ HardwareOpus& hardware_opus;
+ std::unique_ptr<u8[]> shared_buffer{};
+ u64 shared_buffer_size;
+ std::span<u8> in_data{};
+ std::span<u8> out_data{};
+ u64 buffer_size{};
+ s32 sample_rate{};
+ s32 channel_count{};
+ bool use_large_frame_size{false};
+ s32 total_stream_count{};
+ s32 stereo_stream_count{};
+ bool shared_memory_mapped{false};
+ bool decode_object_initialized{false};
+};
+
+} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/decoder_manager.cpp b/src/audio_core/opus/decoder_manager.cpp new file mode 100644 index 000000000..4a5382973 --- /dev/null +++ b/src/audio_core/opus/decoder_manager.cpp @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "audio_core/adsp/apps/opus/opus_decoder.h"
+#include "audio_core/opus/decoder_manager.h"
+#include "common/alignment.h"
+#include "core/core.h"
+
+namespace AudioCore::OpusDecoder {
+using namespace Service::Audio;
+
+namespace {
+bool IsValidChannelCount(u32 channel_count) {
+ return channel_count == 1 || channel_count == 2;
+}
+
+bool IsValidMultiStreamChannelCount(u32 channel_count) {
+ return channel_count > 0 && channel_count <= OpusStreamCountMax;
+}
+
+bool IsValidSampleRate(u32 sample_rate) {
+ return sample_rate == 8'000 || sample_rate == 12'000 || sample_rate == 16'000 ||
+ sample_rate == 24'000 || sample_rate == 48'000;
+}
+
+bool IsValidStreamCount(u32 channel_count, u32 total_stream_count, u32 stereo_stream_count) {
+ return total_stream_count > 0 && stereo_stream_count > 0 &&
+ stereo_stream_count <= total_stream_count &&
+ total_stream_count + stereo_stream_count <= channel_count;
+}
+
+} // namespace
+
+OpusDecoderManager::OpusDecoderManager(Core::System& system_)
+ : system{system_}, hardware_opus{system} {
+ for (u32 i = 0; i < MaxChannels; i++) {
+ required_workbuffer_sizes[i] = hardware_opus.GetWorkBufferSize(1 + i);
+ }
+}
+
+Result OpusDecoderManager::GetWorkBufferSize(OpusParameters& params, u64& out_size) {
+ OpusParametersEx ex{
+ .sample_rate = params.sample_rate,
+ .channel_count = params.channel_count,
+ .use_large_frame_size = false,
+ };
+ R_RETURN(GetWorkBufferSizeExEx(ex, out_size));
+}
+
+Result OpusDecoderManager::GetWorkBufferSizeEx(OpusParametersEx& params, u64& out_size) {
+ R_RETURN(GetWorkBufferSizeExEx(params, out_size));
+}
+
+Result OpusDecoderManager::GetWorkBufferSizeExEx(OpusParametersEx& params, u64& out_size) {
+ R_UNLESS(IsValidChannelCount(params.channel_count), ResultInvalidOpusChannelCount);
+ R_UNLESS(IsValidSampleRate(params.sample_rate), ResultInvalidOpusSampleRate);
+
+ auto work_buffer_size{required_workbuffer_sizes[params.channel_count - 1]};
+ auto frame_size{params.use_large_frame_size ? 5760 : 1920};
+ work_buffer_size +=
+ Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 64);
+ out_size = work_buffer_size + 0x600;
+ R_SUCCEED();
+}
+
+Result OpusDecoderManager::GetWorkBufferSizeForMultiStream(OpusMultiStreamParameters& params,
+ u64& out_size) {
+ OpusMultiStreamParametersEx ex{
+ .sample_rate = params.sample_rate,
+ .channel_count = params.channel_count,
+ .total_stream_count = params.total_stream_count,
+ .stereo_stream_count = params.stereo_stream_count,
+ .use_large_frame_size = false,
+ .mappings = {},
+ };
+ R_RETURN(GetWorkBufferSizeForMultiStreamExEx(ex, out_size));
+}
+
+Result OpusDecoderManager::GetWorkBufferSizeForMultiStreamEx(OpusMultiStreamParametersEx& params,
+ u64& out_size) {
+ R_RETURN(GetWorkBufferSizeForMultiStreamExEx(params, out_size));
+}
+
+Result OpusDecoderManager::GetWorkBufferSizeForMultiStreamExEx(OpusMultiStreamParametersEx& params,
+ u64& out_size) {
+ R_UNLESS(IsValidMultiStreamChannelCount(params.channel_count), ResultInvalidOpusChannelCount);
+ R_UNLESS(IsValidSampleRate(params.sample_rate), ResultInvalidOpusSampleRate);
+ R_UNLESS(IsValidStreamCount(params.channel_count, params.total_stream_count,
+ params.stereo_stream_count),
+ ResultInvalidOpusSampleRate);
+
+ auto work_buffer_size{hardware_opus.GetWorkBufferSizeForMultiStream(
+ params.total_stream_count, params.stereo_stream_count)};
+ auto frame_size{params.use_large_frame_size ? 5760 : 1920};
+ work_buffer_size += Common::AlignUp(1500 * params.total_stream_count, 64);
+ work_buffer_size +=
+ Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 64);
+ out_size = work_buffer_size;
+ R_SUCCEED();
+}
+
+} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/decoder_manager.h b/src/audio_core/opus/decoder_manager.h new file mode 100644 index 000000000..466e1967b --- /dev/null +++ b/src/audio_core/opus/decoder_manager.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "audio_core/opus/hardware_opus.h"
+#include "audio_core/opus/parameters.h"
+#include "common/common_types.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace Core {
+class System;
+}
+
+namespace AudioCore::OpusDecoder {
+
+class OpusDecoderManager {
+public:
+ OpusDecoderManager(Core::System& system);
+
+ HardwareOpus& GetHardwareOpus() {
+ return hardware_opus;
+ }
+
+ Result GetWorkBufferSize(OpusParameters& params, u64& out_size);
+ Result GetWorkBufferSizeEx(OpusParametersEx& params, u64& out_size);
+ Result GetWorkBufferSizeExEx(OpusParametersEx& params, u64& out_size);
+ Result GetWorkBufferSizeForMultiStream(OpusMultiStreamParameters& params, u64& out_size);
+ Result GetWorkBufferSizeForMultiStreamEx(OpusMultiStreamParametersEx& params, u64& out_size);
+ Result GetWorkBufferSizeForMultiStreamExEx(OpusMultiStreamParametersEx& params, u64& out_size);
+
+private:
+ Core::System& system;
+ HardwareOpus hardware_opus;
+ std::array<u64, MaxChannels> required_workbuffer_sizes{};
+};
+
+} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/hardware_opus.cpp b/src/audio_core/opus/hardware_opus.cpp new file mode 100644 index 000000000..d6544dcb0 --- /dev/null +++ b/src/audio_core/opus/hardware_opus.cpp @@ -0,0 +1,241 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+
+#include "audio_core/audio_core.h"
+#include "audio_core/opus/hardware_opus.h"
+#include "core/core.h"
+
+namespace AudioCore::OpusDecoder {
+namespace {
+using namespace Service::Audio;
+
+static constexpr Result ResultCodeFromLibOpusErrorCode(u64 error_code) {
+ s32 error{static_cast<s32>(error_code)};
+ ASSERT(error <= OPUS_OK);
+ switch (error) {
+ case OPUS_ALLOC_FAIL:
+ R_THROW(ResultLibOpusAllocFail);
+ case OPUS_INVALID_STATE:
+ R_THROW(ResultLibOpusInvalidState);
+ case OPUS_UNIMPLEMENTED:
+ R_THROW(ResultLibOpusUnimplemented);
+ case OPUS_INVALID_PACKET:
+ R_THROW(ResultLibOpusInvalidPacket);
+ case OPUS_INTERNAL_ERROR:
+ R_THROW(ResultLibOpusInternalError);
+ case OPUS_BUFFER_TOO_SMALL:
+ R_THROW(ResultBufferTooSmall);
+ case OPUS_BAD_ARG:
+ R_THROW(ResultLibOpusBadArg);
+ case OPUS_OK:
+ R_RETURN(ResultSuccess);
+ }
+ UNREACHABLE();
+}
+
+} // namespace
+
+HardwareOpus::HardwareOpus(Core::System& system_)
+ : system{system_}, opus_decoder{system.AudioCore().ADSP().OpusDecoder()} {
+ opus_decoder.SetSharedMemory(shared_memory);
+}
+
+u64 HardwareOpus::GetWorkBufferSize(u32 channel) {
+ if (!opus_decoder.IsRunning()) {
+ return 0;
+ }
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = channel;
+ opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::GetWorkBufferSize);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ if (msg != ADSP::OpusDecoder::Message::GetWorkBufferSizeOK) {
+ LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
+ ADSP::OpusDecoder::Message::GetWorkBufferSizeOK, msg);
+ return 0;
+ }
+ return shared_memory.dsp_return_data[0];
+}
+
+u64 HardwareOpus::GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = total_stream_count;
+ shared_memory.host_send_data[1] = stereo_stream_count;
+ opus_decoder.Send(ADSP::Direction::DSP,
+ ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStream);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ if (msg != ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStreamOK) {
+ LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
+ ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStreamOK, msg);
+ return 0;
+ }
+ return shared_memory.dsp_return_data[0];
+}
+
+Result HardwareOpus::InitializeDecodeObject(u32 sample_rate, u32 channel_count, void* buffer,
+ u64 buffer_size) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = (u64)buffer;
+ shared_memory.host_send_data[1] = buffer_size;
+ shared_memory.host_send_data[2] = sample_rate;
+ shared_memory.host_send_data[3] = channel_count;
+
+ opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::InitializeDecodeObject);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ if (msg != ADSP::OpusDecoder::Message::InitializeDecodeObjectOK) {
+ LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
+ ADSP::OpusDecoder::Message::InitializeDecodeObjectOK, msg);
+ R_THROW(ResultInvalidOpusDSPReturnCode);
+ }
+
+ R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
+}
+
+Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
+ u32 total_stream_count,
+ u32 stereo_stream_count, void* mappings,
+ void* buffer, u64 buffer_size) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = (u64)buffer;
+ shared_memory.host_send_data[1] = buffer_size;
+ shared_memory.host_send_data[2] = sample_rate;
+ shared_memory.host_send_data[3] = channel_count;
+ shared_memory.host_send_data[4] = total_stream_count;
+ shared_memory.host_send_data[5] = stereo_stream_count;
+
+ ASSERT(channel_count <= MaxChannels);
+ std::memcpy(shared_memory.channel_mapping.data(), mappings, channel_count * sizeof(u8));
+
+ opus_decoder.Send(ADSP::Direction::DSP,
+ ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObject);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ if (msg != ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObjectOK) {
+ LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
+ ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObjectOK, msg);
+ R_THROW(ResultInvalidOpusDSPReturnCode);
+ }
+
+ R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
+}
+
+Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = (u64)buffer;
+ shared_memory.host_send_data[1] = buffer_size;
+
+ opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::ShutdownDecodeObject);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ ASSERT_MSG(msg == ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK,
+ "Expected Opus shutdown code {}, got {}",
+ ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg);
+
+ R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
+}
+
+Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = (u64)buffer;
+ shared_memory.host_send_data[1] = buffer_size;
+
+ opus_decoder.Send(ADSP::Direction::DSP,
+ ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObject);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ ASSERT_MSG(msg == ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK,
+ "Expected Opus shutdown code {}, got {}",
+ ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg);
+
+ R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
+}
+
+Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
+ u64 output_data_size, u32 channel_count, void* input_data,
+ u64 input_data_size, void* buffer, u64& out_time_taken,
+ bool reset) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = (u64)buffer;
+ shared_memory.host_send_data[1] = (u64)input_data;
+ shared_memory.host_send_data[2] = input_data_size;
+ shared_memory.host_send_data[3] = (u64)output_data;
+ shared_memory.host_send_data[4] = output_data_size;
+ shared_memory.host_send_data[5] = 0;
+ shared_memory.host_send_data[6] = reset;
+
+ opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::DecodeInterleaved);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedOK) {
+ LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
+ ADSP::OpusDecoder::Message::DecodeInterleavedOK, msg);
+ R_THROW(ResultInvalidOpusDSPReturnCode);
+ }
+
+ auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
+ if (error_code == OPUS_OK) {
+ out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
+ out_time_taken = 1000 * shared_memory.dsp_return_data[2];
+ }
+ R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
+}
+
+Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
+ u64 output_data_size, u32 channel_count,
+ void* input_data, u64 input_data_size,
+ void* buffer, u64& out_time_taken,
+ bool reset) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = (u64)buffer;
+ shared_memory.host_send_data[1] = (u64)input_data;
+ shared_memory.host_send_data[2] = input_data_size;
+ shared_memory.host_send_data[3] = (u64)output_data;
+ shared_memory.host_send_data[4] = output_data_size;
+ shared_memory.host_send_data[5] = 0;
+ shared_memory.host_send_data[6] = reset;
+
+ opus_decoder.Send(ADSP::Direction::DSP,
+ ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK) {
+ LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
+ ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg);
+ R_THROW(ResultInvalidOpusDSPReturnCode);
+ }
+
+ auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
+ if (error_code == OPUS_OK) {
+ out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
+ out_time_taken = 1000 * shared_memory.dsp_return_data[2];
+ }
+ R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
+}
+
+Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = (u64)buffer;
+ shared_memory.host_send_data[1] = buffer_size;
+
+ opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::MapMemory);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ if (msg != ADSP::OpusDecoder::Message::MapMemoryOK) {
+ LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
+ ADSP::OpusDecoder::Message::MapMemoryOK, msg);
+ R_THROW(ResultInvalidOpusDSPReturnCode);
+ }
+ R_SUCCEED();
+}
+
+Result HardwareOpus::UnmapMemory(void* buffer, u64 buffer_size) {
+ std::scoped_lock l{mutex};
+ shared_memory.host_send_data[0] = (u64)buffer;
+ shared_memory.host_send_data[1] = buffer_size;
+
+ opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::UnmapMemory);
+ auto msg = opus_decoder.Receive(ADSP::Direction::Host);
+ if (msg != ADSP::OpusDecoder::Message::UnmapMemoryOK) {
+ LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
+ ADSP::OpusDecoder::Message::UnmapMemoryOK, msg);
+ R_THROW(ResultInvalidOpusDSPReturnCode);
+ }
+ R_SUCCEED();
+}
+
+} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/hardware_opus.h b/src/audio_core/opus/hardware_opus.h new file mode 100644 index 000000000..7013a6b40 --- /dev/null +++ b/src/audio_core/opus/hardware_opus.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <mutex>
+#include <opus.h>
+
+#include "audio_core/adsp/apps/opus/opus_decoder.h"
+#include "audio_core/adsp/apps/opus/shared_memory.h"
+#include "audio_core/adsp/mailbox.h"
+#include "core/hle/service/audio/errors.h"
+
+namespace AudioCore::OpusDecoder {
+class HardwareOpus {
+public:
+ HardwareOpus(Core::System& system);
+
+ u64 GetWorkBufferSize(u32 channel);
+ u64 GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count);
+
+ Result InitializeDecodeObject(u32 sample_rate, u32 channel_count, void* buffer,
+ u64 buffer_size);
+ Result InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
+ u32 totaL_stream_count, u32 stereo_stream_count,
+ void* mappings, void* buffer, u64 buffer_size);
+ Result ShutdownDecodeObject(void* buffer, u64 buffer_size);
+ Result ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size);
+ Result DecodeInterleaved(u32& out_sample_count, void* output_data, u64 output_data_size,
+ u32 channel_count, void* input_data, u64 input_data_size, void* buffer,
+ u64& out_time_taken, bool reset);
+ Result DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
+ u64 output_data_size, u32 channel_count,
+ void* input_data, u64 input_data_size, void* buffer,
+ u64& out_time_taken, bool reset);
+ Result MapMemory(void* buffer, u64 buffer_size);
+ Result UnmapMemory(void* buffer, u64 buffer_size);
+
+private:
+ Core::System& system;
+ std::mutex mutex;
+ ADSP::OpusDecoder::OpusDecoder& opus_decoder;
+ ADSP::OpusDecoder::SharedMemory shared_memory;
+};
+} // namespace AudioCore::OpusDecoder
diff --git a/src/audio_core/opus/parameters.h b/src/audio_core/opus/parameters.h new file mode 100644 index 000000000..4c54b2825 --- /dev/null +++ b/src/audio_core/opus/parameters.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace AudioCore::OpusDecoder { +constexpr size_t OpusStreamCountMax = 255; +constexpr size_t MaxChannels = 2; + +struct OpusParameters { + /* 0x00 */ u32 sample_rate; + /* 0x04 */ u32 channel_count; +}; // size = 0x8 +static_assert(sizeof(OpusParameters) == 0x8, "OpusParameters has the wrong size!"); + +struct OpusParametersEx { + /* 0x00 */ u32 sample_rate; + /* 0x04 */ u32 channel_count; + /* 0x08 */ bool use_large_frame_size; + /* 0x09 */ INSERT_PADDING_BYTES(7); +}; // size = 0x10 +static_assert(sizeof(OpusParametersEx) == 0x10, "OpusParametersEx has the wrong size!"); + +struct OpusMultiStreamParameters { + /* 0x00 */ u32 sample_rate; + /* 0x04 */ u32 channel_count; + /* 0x08 */ u32 total_stream_count; + /* 0x0C */ u32 stereo_stream_count; + /* 0x10 */ std::array<u8, OpusStreamCountMax + 1> mappings; +}; // size = 0x110 +static_assert(sizeof(OpusMultiStreamParameters) == 0x110, + "OpusMultiStreamParameters has the wrong size!"); + +struct OpusMultiStreamParametersEx { + /* 0x00 */ u32 sample_rate; + /* 0x04 */ u32 channel_count; + /* 0x08 */ u32 total_stream_count; + /* 0x0C */ u32 stereo_stream_count; + /* 0x10 */ bool use_large_frame_size; + /* 0x11 */ INSERT_PADDING_BYTES(7); + /* 0x18 */ std::array<u8, OpusStreamCountMax + 1> mappings; +}; // size = 0x118 +static_assert(sizeof(OpusMultiStreamParametersEx) == 0x118, + "OpusMultiStreamParametersEx has the wrong size!"); + +struct OpusPacketHeader { + /* 0x00 */ u32 size; + /* 0x04 */ u32 final_range; +}; // size = 0x8 +static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusPacketHeader has the wrong size!"); +} // namespace AudioCore::OpusDecoder diff --git a/src/audio_core/renderer/command/command_processing_time_estimator.cpp b/src/audio_core/renderer/command/command_processing_time_estimator.cpp index a48a016b1..0f7aff1b4 100644 --- a/src/audio_core/renderer/command/command_processing_time_estimator.cpp +++ b/src/audio_core/renderer/command/command_processing_time_estimator.cpp @@ -27,12 +27,12 @@ u32 CommandProcessingTimeEstimatorVersion1::Estimate( u32 CommandProcessingTimeEstimatorVersion1::Estimate( const AdpcmDataSourceVersion1Command& command) const { - return static_cast<u32>(command.pitch * 0.25f * 1.2f); + return static_cast<u32>(command.pitch * 0.46f * 1.2f); } u32 CommandProcessingTimeEstimatorVersion1::Estimate( const AdpcmDataSourceVersion2Command& command) const { - return static_cast<u32>(command.pitch * 0.25f * 1.2f); + return static_cast<u32>(command.pitch * 0.46f * 1.2f); } u32 CommandProcessingTimeEstimatorVersion1::Estimate( diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp index d29754634..31f92087c 100644 --- a/src/audio_core/renderer/system.cpp +++ b/src/audio_core/renderer/system.cpp @@ -684,11 +684,11 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer, sink_context, splitter_context, perf_manager}; voice_context.SortInfo(); + command_generator.GenerateVoiceCommands(); const auto start_estimated_time{drop_voice_param * static_cast<f32>(command_buffer.estimated_process_time)}; - command_generator.GenerateVoiceCommands(); command_generator.GenerateSubMixCommands(); command_generator.GenerateFinalMixCommands(); command_generator.GenerateSinkCommands(); @@ -708,11 +708,13 @@ u64 System::GenerateCommand(std::span<u8> in_command_buffer, const auto end_estimated_time{drop_voice_param * static_cast<f32>(command_buffer.estimated_process_time)}; + + const auto dsp_time_limit{((time_limit_percent / 100.0f) * 2'880'000.0f) * + (static_cast<f32>(render_time_limit_percent) / 100.0f)}; + const auto estimated_time{start_estimated_time - end_estimated_time}; - const auto time_limit{static_cast<u32>( - estimated_time + (((time_limit_percent / 100.0f) * 2'880'000.0) * - (static_cast<f32>(render_time_limit_percent) / 100.0f)))}; + const auto time_limit{static_cast<u32>(std::max(dsp_time_limit + estimated_time, 0.0f))}; num_voices_dropped = DropVoices(command_buffer, static_cast<u32>(start_estimated_time), time_limit); } diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 34877b461..416203c59 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -26,12 +26,11 @@ add_library(common STATIC assert.h atomic_helpers.h atomic_ops.h - detached_tasks.cpp - detached_tasks.h bit_cast.h bit_field.h bit_set.h bit_util.h + bounded_threadsafe_queue.h cityhash.cpp cityhash.h common_funcs.h @@ -41,6 +40,8 @@ add_library(common STATIC container_hash.h demangle.cpp demangle.h + detached_tasks.cpp + detached_tasks.h div_ceil.h dynamic_library.cpp dynamic_library.h diff --git a/src/common/bounded_threadsafe_queue.h b/src/common/bounded_threadsafe_queue.h index bd87aa09b..b36fc1de9 100644 --- a/src/common/bounded_threadsafe_queue.h +++ b/src/common/bounded_threadsafe_queue.h @@ -45,13 +45,13 @@ public: } T PopWait() { - T t; + T t{}; Pop<PopMode::Wait>(t); return t; } T PopWait(std::stop_token stop_token) { - T t; + T t{}; Pop<PopMode::WaitWithStopToken>(t, stop_token); return t; } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 30d2f7df6..d0f76e57e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -596,6 +596,10 @@ add_library(core STATIC hle/service/mii/types/ver3_store_data.h hle/service/mii/mii.cpp hle/service/mii/mii.h + hle/service/mii/mii_database.cpp + hle/service/mii/mii_database.h + hle/service/mii/mii_database_manager.cpp + hle/service/mii/mii_database_manager.h hle/service/mii/mii_manager.cpp hle/service/mii/mii_manager.h hle/service/mii/mii_result.h @@ -890,7 +894,7 @@ endif() create_target_directory_groups(core) target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb) -target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus renderdoc) +target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls RenderDoc::API) if (MINGW) target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) endif() diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index e33b00d89..04da93d5c 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -752,7 +752,9 @@ bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { for (u8 i = 0; i < 0x10; i++) { const auto meta_dir = dir->CreateDirectoryRelative("yuzu_meta"); const auto filename = GetCNMTName(TitleType::Update, title_id + i); - removed_data |= meta_dir->DeleteFile(filename); + if (meta_dir->GetFile(filename)) { + removed_data |= meta_dir->DeleteFile(filename); + } } return removed_data; diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 92a1439eb..dd0b27f47 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -62,7 +62,7 @@ enum class ErrorModule : u32 { XCD = 108, TMP451 = 109, NIFM = 110, - Hwopus = 111, + HwOpus = 111, LSM6DS3 = 112, Bluetooth = 113, VI = 114, diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 38c2138e8..7075ab800 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -22,6 +22,8 @@ namespace Service::AOC { +constexpr Result ResultNoPurchasedProductInfoAvailable{ErrorModule::NIMShop, 400}; + static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) { return FileSys::GetBaseTitleID(title_id) == base; } @@ -54,8 +56,8 @@ public: {0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"}, {1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"}, {2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"}, - {3, nullptr, "PopPurchasedProductInfo"}, - {4, nullptr, "PopPurchasedProductInfoWithUid"}, + {3, &IPurchaseEventManager::PopPurchasedProductInfo, "PopPurchasedProductInfo"}, + {4, &IPurchaseEventManager::PopPurchasedProductInfoWithUid, "PopPurchasedProductInfoWithUid"}, }; // clang-format on @@ -101,6 +103,20 @@ private: rb.PushCopyObjects(purchased_event->GetReadableEvent()); } + void PopPurchasedProductInfo(HLERequestContext& ctx) { + LOG_DEBUG(Service_AOC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultNoPurchasedProductInfoAvailable); + } + + void PopPurchasedProductInfoWithUid(HLERequestContext& ctx) { + LOG_DEBUG(Service_AOC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultNoPurchasedProductInfoAvailable); + } + KernelHelpers::ServiceContext service_context; Kernel::KEvent* purchased_event; diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h index 3d3d3d97a..c41345f7e 100644 --- a/src/core/hle/service/audio/errors.h +++ b/src/core/hle/service/audio/errors.h @@ -20,4 +20,16 @@ constexpr Result ResultNotSupported{ErrorModule::Audio, 513}; constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536}; constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537}; +constexpr Result ResultLibOpusAllocFail{ErrorModule::HwOpus, 7}; +constexpr Result ResultInputDataTooSmall{ErrorModule::HwOpus, 8}; +constexpr Result ResultLibOpusInvalidState{ErrorModule::HwOpus, 6}; +constexpr Result ResultLibOpusUnimplemented{ErrorModule::HwOpus, 5}; +constexpr Result ResultLibOpusInvalidPacket{ErrorModule::HwOpus, 17}; +constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4}; +constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3}; +constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2}; +constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259}; +constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001}; +constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002}; + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 1557e6088..6a7bf9416 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -1,420 +1,506 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <chrono> -#include <cstring> #include <memory> #include <vector> -#include <opus.h> -#include <opus_multistream.h> - +#include "audio_core/opus/decoder.h" +#include "audio_core/opus/parameters.h" #include "common/assert.h" #include "common/logging/log.h" #include "common/scratch_buffer.h" +#include "core/core.h" #include "core/hle/service/audio/hwopus.h" #include "core/hle/service/ipc_helpers.h" namespace Service::Audio { -namespace { -struct OpusDeleter { - void operator()(OpusMSDecoder* ptr) const { - opus_multistream_decoder_destroy(ptr); +using namespace AudioCore::OpusDecoder; + +class IHardwareOpusDecoder final : public ServiceFramework<IHardwareOpusDecoder> { +public: + explicit IHardwareOpusDecoder(Core::System& system_, HardwareOpus& hardware_opus) + : ServiceFramework{system_, "IHardwareOpusDecoder"}, + impl{std::make_unique<AudioCore::OpusDecoder::OpusDecoder>(system_, hardware_opus)} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IHardwareOpusDecoder::DecodeInterleavedOld, "DecodeInterleavedOld"}, + {1, &IHardwareOpusDecoder::SetContext, "SetContext"}, + {2, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamOld, "DecodeInterleavedForMultiStreamOld"}, + {3, &IHardwareOpusDecoder::SetContextForMultiStream, "SetContextForMultiStream"}, + {4, &IHardwareOpusDecoder::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, + {5, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfOld, "DecodeInterleavedForMultiStreamWithPerfOld"}, + {6, &IHardwareOpusDecoder::DecodeInterleavedWithPerfAndResetOld, "DecodeInterleavedWithPerfAndResetOld"}, + {7, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfAndResetOld, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"}, + {8, &IHardwareOpusDecoder::DecodeInterleaved, "DecodeInterleaved"}, + {9, &IHardwareOpusDecoder::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"}, + }; + // clang-format on + + RegisterHandlers(functions); } -}; -using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>; + Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, + u64 transfer_memory_size) { + return impl->Initialize(params, transfer_memory, transfer_memory_size); + } -struct OpusPacketHeader { - // Packet size in bytes. - u32_be size; - // Indicates the final range of the codec's entropy coder. - u32_be final_range; -}; -static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size"); + Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, + u64 transfer_memory_size) { + return impl->Initialize(params, transfer_memory, transfer_memory_size); + } -class OpusDecoderState { -public: - /// Describes extra behavior that may be asked of the decoding context. - enum class ExtraBehavior { - /// No extra behavior. - None, +private: + void DecodeInterleavedOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; - /// Resets the decoder context back to a freshly initialized state. - ResetContext, - }; + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); - enum class PerfTime { - Disabled, - Enabled, - }; + u32 size{}; + u32 sample_count{}; + auto result = + impl->DecodeInterleaved(&size, nullptr, &sample_count, input_data, output_data, false); - explicit OpusDecoderState(OpusDecoderPtr decoder_, u32 sample_rate_, u32 channel_count_) - : decoder{std::move(decoder_)}, sample_rate{sample_rate_}, channel_count{channel_count_} {} - - // Decodes interleaved Opus packets. Optionally allows reporting time taken to - // perform the decoding, as well as any relevant extra behavior. - void DecodeInterleaved(HLERequestContext& ctx, PerfTime perf_time, - ExtraBehavior extra_behavior) { - if (perf_time == PerfTime::Disabled) { - DecodeInterleavedHelper(ctx, nullptr, extra_behavior); - } else { - u64 performance = 0; - DecodeInterleavedHelper(ctx, &performance, extra_behavior); - } + LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); } -private: - void DecodeInterleavedHelper(HLERequestContext& ctx, u64* performance, - ExtraBehavior extra_behavior) { - u32 consumed = 0; - u32 sample_count = 0; - samples.resize_destructive(ctx.GetWriteBufferNumElements<opus_int16>()); - - if (extra_behavior == ExtraBehavior::ResetContext) { - ResetDecoderContext(); - } - - if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) { - LOG_ERROR(Audio, "Failed to decode opus data"); - IPC::ResponseBuilder rb{ctx, 2}; - // TODO(ogniK): Use correct error code - rb.Push(ResultUnknown); - return; - } - - const u32 param_size = performance != nullptr ? 6 : 4; - IPC::ResponseBuilder rb{ctx, param_size}; - rb.Push(ResultSuccess); - rb.Push<u32>(consumed); - rb.Push<u32>(sample_count); - if (performance) { - rb.Push<u64>(*performance); - } - ctx.WriteBuffer(samples); + void SetContext(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + LOG_DEBUG(Service_Audio, "called"); + + auto input_data{ctx.ReadBuffer(0)}; + auto result = impl->SetContext(input_data); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void DecodeInterleavedForMultiStreamOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + auto result = impl->DecodeInterleavedForMultiStream(&size, nullptr, &sample_count, + input_data, output_data, false); + + LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); } - bool DecodeOpusData(u32& consumed, u32& sample_count, std::span<const u8> input, - std::span<opus_int16> output, u64* out_performance_time) const { - const auto start_time = std::chrono::steady_clock::now(); - const std::size_t raw_output_sz = output.size() * sizeof(opus_int16); - if (sizeof(OpusPacketHeader) > input.size()) { - LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}", - sizeof(OpusPacketHeader), input.size()); - return false; - } - - OpusPacketHeader hdr{}; - std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader)); - if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) { - LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}", - sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size()); - return false; - } - - const auto frame = input.data() + sizeof(OpusPacketHeader); - const auto decoded_sample_count = opus_packet_get_nb_samples( - frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)), - static_cast<opus_int32>(sample_rate)); - if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) { - LOG_ERROR( - Audio, - "Decoded data does not fit into the output data, decoded_sz={}, raw_output_sz={}", - decoded_sample_count * channel_count * sizeof(u16), raw_output_sz); - return false; - } - - const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)); - const auto out_sample_count = - opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0); - if (out_sample_count < 0) { - LOG_ERROR(Audio, - "Incorrect sample count received from opus_decode, " - "output_sample_count={}, frame_size={}, data_sz_from_hdr={}", - out_sample_count, frame_size, static_cast<u32>(hdr.size)); - return false; - } - - const auto end_time = std::chrono::steady_clock::now() - start_time; - sample_count = out_sample_count; - consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size); - if (out_performance_time != nullptr) { - *out_performance_time = - std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count(); - } - - return true; + void SetContextForMultiStream(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + LOG_DEBUG(Service_Audio, "called"); + + auto input_data{ctx.ReadBuffer(0)}; + auto result = impl->SetContext(input_data); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); } - void ResetDecoderContext() { - ASSERT(decoder != nullptr); + void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, + output_data, false); + + LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size, + sample_count, time_taken); - opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE); + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } - OpusDecoderPtr decoder; - u32 sample_rate; - u32 channel_count; - Common::ScratchBuffer<opus_int16> samples; -}; + void DecodeInterleavedForMultiStreamWithPerfOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; -class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { -public: - explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state_) - : ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{ - std::move(decoder_state_)} { - // clang-format off - static const FunctionInfo functions[] = { - {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"}, - {1, nullptr, "SetContext"}, - {2, nullptr, "DecodeInterleavedForMultiStreamOld"}, - {3, nullptr, "SetContextForMultiStream"}, - {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, - {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"}, - {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleavedWithPerfAndResetOld"}, - {7, nullptr, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"}, - {8, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"}, - {9, &IHardwareOpusDecoderManager::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"}, - }; - // clang-format on + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); - RegisterHandlers(functions); + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, + input_data, output_data, false); + + LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size, + sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } -private: - void DecodeInterleavedOld(HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); + void DecodeInterleavedWithPerfAndResetOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto reset{rp.Pop<bool>()}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, + output_data, reset); + + LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", + reset, size, sample_count, time_taken); + + ctx.WriteBuffer(output_data); - decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled, - OpusDecoderState::ExtraBehavior::None); + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } - void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); + void DecodeInterleavedForMultiStreamWithPerfAndResetOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto reset{rp.Pop<bool>()}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, + input_data, output_data, reset); - decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, - OpusDecoderState::ExtraBehavior::None); + LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", + reset, size, sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } void DecodeInterleaved(HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); - IPC::RequestParser rp{ctx}; - const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext - : OpusDecoderState::ExtraBehavior::None; - decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior); + auto reset{rp.Pop<bool>()}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, + output_data, reset); + + LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", + reset, size, sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } void DecodeInterleavedForMultiStream(HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); - IPC::RequestParser rp{ctx}; - const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext - : OpusDecoderState::ExtraBehavior::None; - decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior); + auto reset{rp.Pop<bool>()}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, + input_data, output_data, reset); + + LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", + reset, size, sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } - OpusDecoderState decoder_state; + std::unique_ptr<AudioCore::OpusDecoder::OpusDecoder> impl; + Common::ScratchBuffer<u8> output_data; }; -std::size_t WorkerBufferSize(u32 channel_count) { - ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); - constexpr int num_streams = 1; - const int num_stereo_streams = channel_count == 2 ? 1 : 0; - return opus_multistream_decoder_get_size(num_streams, num_stereo_streams); -} +void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; -// Creates the mapping table that maps the input channels to the particular -// output channels. In the stereo case, we map the left and right input channels -// to the left and right output channels respectively. -// -// However, in the monophonic case, we only map the one available channel -// to the sole output channel. We specify 255 for the would-be right channel -// as this is a special value defined by Opus to indicate to the decoder to -// ignore that channel. -std::array<u8, 2> CreateMappingTable(u32 channel_count) { - if (channel_count == 2) { - return {{0, 1}}; - } + auto params = rp.PopRaw<OpusParameters>(); + auto transfer_memory_size{rp.Pop<u32>()}; + auto transfer_memory_handle{ctx.GetCopyHandle(0)}; + auto transfer_memory{ + system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( + transfer_memory_handle)}; + + LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}", + params.sample_rate, params.channel_count, transfer_memory_size); - return {{0, 255}}; + auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; + + OpusParametersEx ex{ + .sample_rate = params.sample_rate, + .channel_count = params.channel_count, + .use_large_frame_size = false, + }; + auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result); + rb.PushIpcInterface(decoder); } -} // Anonymous namespace void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto sample_rate = rp.Pop<u32>(); - const auto channel_count = rp.Pop<u32>(); + auto params = rp.PopRaw<OpusParameters>(); - LOG_DEBUG(Audio, "called with sample_rate={}, channel_count={}", sample_rate, channel_count); + u64 size{}; + auto result = impl.GetWorkBufferSize(params, size); - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); - ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); + LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} -- returned size 0x{:X}", + params.sample_rate, params.channel_count, size); - const u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count)); - LOG_DEBUG(Audio, "worker_buffer_sz={}", worker_buffer_sz); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push<u32>(worker_buffer_sz); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); } -void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { - GetWorkBufferSize(ctx); +void HwOpus::OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParameters params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParameters)); + + auto transfer_memory_size{rp.Pop<u32>()}; + auto transfer_memory_handle{ctx.GetCopyHandle(0)}; + auto transfer_memory{ + system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( + transfer_memory_handle)}; + + LOG_DEBUG(Service_Audio, + "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " + "transfer_memory_size 0x{:X}", + params.sample_rate, params.channel_count, params.total_stream_count, + params.stereo_stream_count, transfer_memory_size); + + auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; + + OpusMultiStreamParametersEx ex{ + .sample_rate = params.sample_rate, + .channel_count = params.channel_count, + .total_stream_count = params.total_stream_count, + .stereo_stream_count = params.stereo_stream_count, + .use_large_frame_size = false, + .mappings{}, + }; + std::memcpy(ex.mappings.data(), params.mappings.data(), sizeof(params.mappings)); + auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result); + rb.PushIpcInterface(decoder); } -void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) { - GetWorkBufferSizeEx(ctx); +void HwOpus::GetWorkBufferSizeForMultiStream(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParameters params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParameters)); + + u64 size{}; + auto result = impl.GetWorkBufferSizeForMultiStream(params, size); + + LOG_DEBUG(Service_Audio, "size 0x{:X}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); } -void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { - OpusMultiStreamParametersEx param; - std::memcpy(¶m, ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); +void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; - const auto sample_rate = param.sample_rate; - const auto channel_count = param.channel_count; - const auto number_streams = param.number_streams; - const auto number_stereo_streams = param.number_stereo_streams; + auto params = rp.PopRaw<OpusParametersEx>(); + auto transfer_memory_size{rp.Pop<u32>()}; + auto transfer_memory_handle{ctx.GetCopyHandle(0)}; + auto transfer_memory{ + system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( + transfer_memory_handle)}; - LOG_DEBUG( - Audio, - "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}", - sample_rate, channel_count, number_streams, number_stereo_streams); + LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}", + params.sample_rate, params.channel_count, transfer_memory_size); - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); + auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; - const u32 worker_buffer_sz = - static_cast<u32>(opus_multistream_decoder_get_size(number_streams, number_stereo_streams)); + auto result = + decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push<u32>(worker_buffer_sz); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result); + rb.PushIpcInterface(decoder); } -void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { +void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto sample_rate = rp.Pop<u32>(); - const auto channel_count = rp.Pop<u32>(); - const auto buffer_sz = rp.Pop<u32>(); - - LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate, - channel_count, buffer_sz); - - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); - ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); - - const std::size_t worker_sz = WorkerBufferSize(channel_count); - ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large"); - - const int num_stereo_streams = channel_count == 2 ? 1 : 0; - const auto mapping_table = CreateMappingTable(channel_count); - - int error = 0; - OpusDecoderPtr decoder{ - opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, - num_stereo_streams, mapping_table.data(), &error)}; - if (error != OPUS_OK || decoder == nullptr) { - LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); - IPC::ResponseBuilder rb{ctx, 2}; - // TODO(ogniK): Use correct error code - rb.Push(ResultUnknown); - return; - } + auto params = rp.PopRaw<OpusParametersEx>(); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface<IHardwareOpusDecoderManager>( - system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); + u64 size{}; + auto result = impl.GetWorkBufferSizeEx(params, size); + + LOG_DEBUG(Service_Audio, "size 0x{:X}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); } -void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { +void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto sample_rate = rp.Pop<u32>(); - const auto channel_count = rp.Pop<u32>(); - LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParametersEx params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); + + auto transfer_memory_size{rp.Pop<u32>()}; + auto transfer_memory_handle{ctx.GetCopyHandle(0)}; + auto transfer_memory{ + system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( + transfer_memory_handle)}; - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); - ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); + LOG_DEBUG(Service_Audio, + "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " + "use_large_frame_size {}" + "transfer_memory_size 0x{:X}", + params.sample_rate, params.channel_count, params.total_stream_count, + params.stereo_stream_count, params.use_large_frame_size, transfer_memory_size); - const int num_stereo_streams = channel_count == 2 ? 1 : 0; - const auto mapping_table = CreateMappingTable(channel_count); + auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; - int error = 0; - OpusDecoderPtr decoder{ - opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, - num_stereo_streams, mapping_table.data(), &error)}; - if (error != OPUS_OK || decoder == nullptr) { - LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); - IPC::ResponseBuilder rb{ctx, 2}; - // TODO(ogniK): Use correct error code - rb.Push(ResultUnknown); - return; - } + auto result = + decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface<IHardwareOpusDecoderManager>( - system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); + rb.Push(result); + rb.PushIpcInterface(decoder); } -void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) { +void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input{ctx.ReadBuffer()}; OpusMultiStreamParametersEx params; - std::memcpy(¶ms, ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); - - const auto& sample_rate = params.sample_rate; - const auto& channel_count = params.channel_count; - - LOG_INFO( - Audio, - "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}", - sample_rate, channel_count, params.number_streams, params.number_stereo_streams); - - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); - - int error = 0; - OpusDecoderPtr decoder{opus_multistream_decoder_create( - sample_rate, static_cast<int>(channel_count), params.number_streams, - params.number_stereo_streams, params.channel_mappings.data(), &error)}; - if (error != OPUS_OK || decoder == nullptr) { - LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); - IPC::ResponseBuilder rb{ctx, 2}; - // TODO(ogniK): Use correct error code - rb.Push(ResultUnknown); - return; - } + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface<IHardwareOpusDecoderManager>( - system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); + u64 size{}; + auto result = impl.GetWorkBufferSizeForMultiStreamEx(params, size); + + LOG_DEBUG(Service_Audio, + "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " + "use_large_frame_size {} -- returned size 0x{:X}", + params.sample_rate, params.channel_count, params.total_stream_count, + params.stereo_stream_count, params.use_large_frame_size, size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); +} + +void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto params = rp.PopRaw<OpusParametersEx>(); + + u64 size{}; + auto result = impl.GetWorkBufferSizeExEx(params, size); + + LOG_DEBUG(Service_Audio, "size 0x{:X}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); +} + +void HwOpus::GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParametersEx params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); + + u64 size{}; + auto result = impl.GetWorkBufferSizeForMultiStreamExEx(params, size); + + LOG_DEBUG(Service_Audio, "size 0x{:X}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); } -HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} { +HwOpus::HwOpus(Core::System& system_) + : ServiceFramework{system_, "hwopus"}, system{system_}, impl{system} { static const FunctionInfo functions[] = { {0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"}, {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, - {2, nullptr, "OpenOpusDecoderForMultiStream"}, - {3, nullptr, "GetWorkBufferSizeForMultiStream"}, + {2, &HwOpus::OpenHardwareOpusDecoderForMultiStream, "OpenOpusDecoderForMultiStream"}, + {3, &HwOpus::GetWorkBufferSizeForMultiStream, "GetWorkBufferSizeForMultiStream"}, {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"}, {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"}, {6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx, "OpenHardwareOpusDecoderForMultiStreamEx"}, {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"}, {8, &HwOpus::GetWorkBufferSizeExEx, "GetWorkBufferSizeExEx"}, - {9, nullptr, "GetWorkBufferSizeForMultiStreamExEx"}, + {9, &HwOpus::GetWorkBufferSizeForMultiStreamExEx, "GetWorkBufferSizeForMultiStreamExEx"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h index 90867bf74..d3960065e 100644 --- a/src/core/hle/service/audio/hwopus.h +++ b/src/core/hle/service/audio/hwopus.h @@ -3,6 +3,7 @@ #pragma once +#include "audio_core/opus/decoder_manager.h" #include "core/hle/service/service.h" namespace Core { @@ -11,18 +12,6 @@ class System; namespace Service::Audio { -struct OpusMultiStreamParametersEx { - u32 sample_rate; - u32 channel_count; - u32 number_streams; - u32 number_stereo_streams; - u32 use_large_frame_size; - u32 padding; - std::array<u8, 0x100> channel_mappings; -}; -static_assert(sizeof(OpusMultiStreamParametersEx) == 0x118, - "OpusMultiStreamParametersEx has incorrect size"); - class HwOpus final : public ServiceFramework<HwOpus> { public: explicit HwOpus(Core::System& system_); @@ -30,12 +19,18 @@ public: private: void OpenHardwareOpusDecoder(HLERequestContext& ctx); - void OpenHardwareOpusDecoderEx(HLERequestContext& ctx); - void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx); void GetWorkBufferSize(HLERequestContext& ctx); + void OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx); + void GetWorkBufferSizeForMultiStream(HLERequestContext& ctx); + void OpenHardwareOpusDecoderEx(HLERequestContext& ctx); void GetWorkBufferSizeEx(HLERequestContext& ctx); - void GetWorkBufferSizeExEx(HLERequestContext& ctx); + void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx); void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx); + void GetWorkBufferSizeExEx(HLERequestContext& ctx); + void GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx); + + Core::System& system; + AudioCore::OpusDecoder::OpusDecoderManager impl; }; } // namespace Service::Audio diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp index 3b83c5ed7..8de806cfb 100644 --- a/src/core/hle/service/mii/mii.cpp +++ b/src/core/hle/service/mii/mii.cpp @@ -8,6 +8,9 @@ #include "core/hle/service/mii/mii.h" #include "core/hle/service/mii/mii_manager.h" #include "core/hle/service/mii/mii_result.h" +#include "core/hle/service/mii/types/char_info.h" +#include "core/hle/service/mii/types/store_data.h" +#include "core/hle/service/mii/types/ver3_store_data.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/service.h" @@ -27,29 +30,31 @@ public: {5, &IDatabaseService::UpdateLatest, "UpdateLatest"}, {6, &IDatabaseService::BuildRandom, "BuildRandom"}, {7, &IDatabaseService::BuildDefault, "BuildDefault"}, - {8, nullptr, "Get2"}, - {9, nullptr, "Get3"}, - {10, nullptr, "UpdateLatest1"}, - {11, nullptr, "FindIndex"}, - {12, nullptr, "Move"}, - {13, nullptr, "AddOrReplace"}, - {14, nullptr, "Delete"}, - {15, nullptr, "DestroyFile"}, - {16, nullptr, "DeleteFile"}, - {17, nullptr, "Format"}, + {8, &IDatabaseService::Get2, "Get2"}, + {9, &IDatabaseService::Get3, "Get3"}, + {10, &IDatabaseService::UpdateLatest1, "UpdateLatest1"}, + {11, &IDatabaseService::FindIndex, "FindIndex"}, + {12, &IDatabaseService::Move, "Move"}, + {13, &IDatabaseService::AddOrReplace, "AddOrReplace"}, + {14, &IDatabaseService::Delete, "Delete"}, + {15, &IDatabaseService::DestroyFile, "DestroyFile"}, + {16, &IDatabaseService::DeleteFile, "DeleteFile"}, + {17, &IDatabaseService::Format, "Format"}, {18, nullptr, "Import"}, {19, nullptr, "Export"}, - {20, nullptr, "IsBrokenDatabaseWithClearFlag"}, + {20, &IDatabaseService::IsBrokenDatabaseWithClearFlag, "IsBrokenDatabaseWithClearFlag"}, {21, &IDatabaseService::GetIndex, "GetIndex"}, {22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"}, {23, &IDatabaseService::Convert, "Convert"}, - {24, nullptr, "ConvertCoreDataToCharInfo"}, - {25, nullptr, "ConvertCharInfoToCoreData"}, - {26, nullptr, "Append"}, + {24, &IDatabaseService::ConvertCoreDataToCharInfo, "ConvertCoreDataToCharInfo"}, + {25, &IDatabaseService::ConvertCharInfoToCoreData, "ConvertCharInfoToCoreData"}, + {26, &IDatabaseService::Append, "Append"}, }; // clang-format on RegisterHandlers(functions); + + manager.Initialize(metadata); } private: @@ -80,10 +85,10 @@ private: IPC::RequestParser rp{ctx}; const auto source_flag{rp.PopRaw<SourceFlag>()}; - LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); - const u32 mii_count = manager.GetCount(metadata, source_flag); + LOG_DEBUG(Service_Mii, "called with source_flag={}, mii_count={}", source_flag, mii_count); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); rb.Push(mii_count); @@ -94,16 +99,17 @@ private: const auto source_flag{rp.PopRaw<SourceFlag>()}; const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()}; - LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size); - u32 mii_count{}; std::vector<CharInfoElement> char_info_elements(output_size); - Result result = manager.Get(metadata, char_info_elements, mii_count, source_flag); + const auto result = manager.Get(metadata, char_info_elements, mii_count, source_flag); if (mii_count != 0) { ctx.WriteBuffer(char_info_elements); } + LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag, + output_size, mii_count); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(result); rb.Push(mii_count); @@ -114,16 +120,17 @@ private: const auto source_flag{rp.PopRaw<SourceFlag>()}; const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()}; - LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size); - u32 mii_count{}; std::vector<CharInfo> char_info(output_size); - Result result = manager.Get(metadata, char_info, mii_count, source_flag); + const auto result = manager.Get(metadata, char_info, mii_count, source_flag); if (mii_count != 0) { ctx.WriteBuffer(char_info); } + LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag, + output_size, mii_count); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(result); rb.Push(mii_count); @@ -134,7 +141,7 @@ private: const auto char_info{rp.PopRaw<CharInfo>()}; const auto source_flag{rp.PopRaw<SourceFlag>()}; - LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); + LOG_INFO(Service_Mii, "called with source_flag={}", source_flag); CharInfo new_char_info{}; const auto result = manager.UpdateLatest(metadata, new_char_info, char_info, source_flag); @@ -146,7 +153,7 @@ private: IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; rb.Push(ResultSuccess); - rb.PushRaw<CharInfo>(new_char_info); + rb.PushRaw(new_char_info); } void BuildRandom(HLERequestContext& ctx) { @@ -180,14 +187,14 @@ private: IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; rb.Push(ResultSuccess); - rb.PushRaw<CharInfo>(char_info); + rb.PushRaw(char_info); } void BuildDefault(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto index{rp.Pop<u32>()}; - LOG_INFO(Service_Mii, "called with index={}", index); + LOG_DEBUG(Service_Mii, "called with index={}", index); if (index > 5) { IPC::ResponseBuilder rb{ctx, 2}; @@ -200,7 +207,239 @@ private: IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; rb.Push(ResultSuccess); - rb.PushRaw<CharInfo>(char_info); + rb.PushRaw(char_info); + } + + void Get2(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto source_flag{rp.PopRaw<SourceFlag>()}; + const auto output_size{ctx.GetWriteBufferNumElements<StoreDataElement>()}; + + u32 mii_count{}; + std::vector<StoreDataElement> store_data_elements(output_size); + const auto result = manager.Get(metadata, store_data_elements, mii_count, source_flag); + + if (mii_count != 0) { + ctx.WriteBuffer(store_data_elements); + } + + LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag, + output_size, mii_count); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result); + rb.Push(mii_count); + } + + void Get3(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto source_flag{rp.PopRaw<SourceFlag>()}; + const auto output_size{ctx.GetWriteBufferNumElements<StoreData>()}; + + u32 mii_count{}; + std::vector<StoreData> store_data(output_size); + const auto result = manager.Get(metadata, store_data, mii_count, source_flag); + + if (mii_count != 0) { + ctx.WriteBuffer(store_data); + } + + LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag, + output_size, mii_count); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result); + rb.Push(mii_count); + } + + void UpdateLatest1(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto store_data{rp.PopRaw<StoreData>()}; + const auto source_flag{rp.PopRaw<SourceFlag>()}; + + LOG_INFO(Service_Mii, "called with source_flag={}", source_flag); + + Result result = ResultSuccess; + if (!is_system) { + result = ResultPermissionDenied; + } + + StoreData new_store_data{}; + if (result.IsSuccess()) { + result = manager.UpdateLatest(metadata, new_store_data, store_data, source_flag); + } + + if (result.IsFailure()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, 2 + sizeof(StoreData) / sizeof(u32)}; + rb.Push(ResultSuccess); + rb.PushRaw<StoreData>(new_store_data); + } + + void FindIndex(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto create_id{rp.PopRaw<Common::UUID>()}; + const auto is_special{rp.PopRaw<bool>()}; + + LOG_INFO(Service_Mii, "called with create_id={}, is_special={}", + create_id.FormattedString(), is_special); + + const s32 index = manager.FindIndex(create_id, is_special); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(index); + } + + void Move(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto create_id{rp.PopRaw<Common::UUID>()}; + const auto new_index{rp.PopRaw<s32>()}; + + LOG_INFO(Service_Mii, "called with create_id={}, new_index={}", create_id.FormattedString(), + new_index); + + Result result = ResultSuccess; + if (!is_system) { + result = ResultPermissionDenied; + } + + if (result.IsSuccess()) { + const u32 count = manager.GetCount(metadata, SourceFlag::Database); + if (new_index < 0 || new_index >= static_cast<s32>(count)) { + result = ResultInvalidArgument; + } + } + + if (result.IsSuccess()) { + result = manager.Move(metadata, new_index, create_id); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void AddOrReplace(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto store_data{rp.PopRaw<StoreData>()}; + + LOG_INFO(Service_Mii, "called"); + + Result result = ResultSuccess; + + if (!is_system) { + result = ResultPermissionDenied; + } + + if (result.IsSuccess()) { + result = manager.AddOrReplace(metadata, store_data); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void Delete(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto create_id{rp.PopRaw<Common::UUID>()}; + + LOG_INFO(Service_Mii, "called, create_id={}", create_id.FormattedString()); + + Result result = ResultSuccess; + + if (!is_system) { + result = ResultPermissionDenied; + } + + if (result.IsSuccess()) { + result = manager.Delete(metadata, create_id); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void DestroyFile(HLERequestContext& ctx) { + // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled"); + const bool is_db_test_mode_enabled = false; + + LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled); + + Result result = ResultSuccess; + + if (!is_db_test_mode_enabled) { + result = ResultTestModeOnly; + } + + if (result.IsSuccess()) { + result = manager.DestroyFile(metadata); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void DeleteFile(HLERequestContext& ctx) { + // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled"); + const bool is_db_test_mode_enabled = false; + + LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled); + + Result result = ResultSuccess; + + if (!is_db_test_mode_enabled) { + result = ResultTestModeOnly; + } + + if (result.IsSuccess()) { + result = manager.DeleteFile(); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void Format(HLERequestContext& ctx) { + // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled"); + const bool is_db_test_mode_enabled = false; + + LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled); + + Result result = ResultSuccess; + + if (!is_db_test_mode_enabled) { + result = ResultTestModeOnly; + } + + if (result.IsSuccess()) { + result = manager.Format(metadata); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void IsBrokenDatabaseWithClearFlag(HLERequestContext& ctx) { + LOG_DEBUG(Service_Mii, "called"); + + bool is_broken_with_clear_flag = false; + Result result = ResultSuccess; + + if (!is_system) { + result = ResultPermissionDenied; + } + + if (result.IsSuccess()) { + is_broken_with_clear_flag = manager.IsBrokenWithClearFlag(metadata); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result); + rb.Push<u8>(is_broken_with_clear_flag); } void GetIndex(HLERequestContext& ctx) { @@ -236,13 +475,53 @@ private: LOG_INFO(Service_Mii, "called"); CharInfo char_info{}; - manager.ConvertV3ToCharInfo(char_info, mii_v3); + const auto result = manager.ConvertV3ToCharInfo(char_info, mii_v3); IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; - rb.Push(ResultSuccess); + rb.Push(result); + rb.PushRaw<CharInfo>(char_info); + } + + void ConvertCoreDataToCharInfo(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto core_data{rp.PopRaw<CoreData>()}; + + LOG_INFO(Service_Mii, "called"); + + CharInfo char_info{}; + const auto result = manager.ConvertCoreDataToCharInfo(char_info, core_data); + + IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; + rb.Push(result); rb.PushRaw<CharInfo>(char_info); } + void ConvertCharInfoToCoreData(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto char_info{rp.PopRaw<CharInfo>()}; + + LOG_INFO(Service_Mii, "called"); + + CoreData core_data{}; + const auto result = manager.ConvertCharInfoToCoreData(core_data, char_info); + + IPC::ResponseBuilder rb{ctx, 2 + sizeof(CoreData) / sizeof(u32)}; + rb.Push(result); + rb.PushRaw<CoreData>(core_data); + } + + void Append(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto char_info{rp.PopRaw<CharInfo>()}; + + LOG_INFO(Service_Mii, "called"); + + const auto result = manager.Append(metadata, char_info); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + MiiManager manager{}; DatabaseSessionMetadata metadata{}; bool is_system{}; @@ -278,9 +557,9 @@ public: explicit MiiImg(Core::System& system_) : ServiceFramework{system_, "miiimg"} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "Initialize"}, + {0, &MiiImg::Initialize, "Initialize"}, {10, nullptr, "Reload"}, - {11, nullptr, "GetCount"}, + {11, &MiiImg::GetCount, "GetCount"}, {12, nullptr, "IsEmpty"}, {13, nullptr, "IsFull"}, {14, nullptr, "GetAttribute"}, @@ -297,6 +576,22 @@ public: RegisterHandlers(functions); } + +private: + void Initialize(HLERequestContext& ctx) { + LOG_INFO(Service_Mii, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void GetCount(HLERequestContext& ctx) { + LOG_DEBUG(Service_Mii, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(0); + } }; void LoopProcess(Core::System& system) { diff --git a/src/core/hle/service/mii/mii_database.cpp b/src/core/hle/service/mii/mii_database.cpp new file mode 100644 index 000000000..3803e58e2 --- /dev/null +++ b/src/core/hle/service/mii/mii_database.cpp @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/mii/mii_database.h" +#include "core/hle/service/mii/mii_result.h" +#include "core/hle/service/mii/mii_util.h" + +namespace Service::Mii { + +u8 NintendoFigurineDatabase::GetDatabaseLength() const { + return database_length; +} + +bool NintendoFigurineDatabase::IsFull() const { + return database_length >= MaxDatabaseLength; +} + +StoreData NintendoFigurineDatabase::Get(std::size_t index) const { + StoreData store_data = miis.at(index); + + // This hack is to make external database dumps compatible + store_data.SetDeviceChecksum(); + + return store_data; +} + +u32 NintendoFigurineDatabase::GetCount(const DatabaseSessionMetadata& metadata) const { + if (magic == MiiMagic) { + return GetDatabaseLength(); + } + + u32 mii_count{}; + for (std::size_t index = 0; index < mii_count; ++index) { + const auto& store_data = Get(index); + if (!store_data.IsSpecial()) { + mii_count++; + } + } + + return mii_count; +} + +bool NintendoFigurineDatabase::GetIndexByCreatorId(u32& out_index, + const Common::UUID& create_id) const { + for (std::size_t index = 0; index < database_length; ++index) { + if (miis[index].GetCreateId() == create_id) { + out_index = static_cast<u32>(index); + return true; + } + } + + return false; +} + +Result NintendoFigurineDatabase::Move(u32 current_index, u32 new_index) { + if (current_index == new_index) { + return ResultNotUpdated; + } + + const StoreData store_data = miis[current_index]; + + if (new_index > current_index) { + // Shift left + const u32 index_diff = new_index - current_index; + for (std::size_t i = 0; i < index_diff; i++) { + miis[current_index + i] = miis[current_index + i + 1]; + } + } else { + // Shift right + const u32 index_diff = current_index - new_index; + for (std::size_t i = 0; i < index_diff; i++) { + miis[current_index - i] = miis[current_index - i - 1]; + } + } + + miis[new_index] = store_data; + crc = GenerateDatabaseCrc(); + return ResultSuccess; +} + +void NintendoFigurineDatabase::Replace(u32 index, const StoreData& store_data) { + miis[index] = store_data; + crc = GenerateDatabaseCrc(); +} + +void NintendoFigurineDatabase::Add(const StoreData& store_data) { + miis[database_length] = store_data; + database_length++; + crc = GenerateDatabaseCrc(); +} + +void NintendoFigurineDatabase::Delete(u32 index) { + // Shift left + const s32 new_database_size = database_length - 1; + if (static_cast<s32>(index) < new_database_size) { + for (std::size_t i = index; i < static_cast<std::size_t>(new_database_size); i++) { + miis[i] = miis[i + 1]; + } + } + + database_length = static_cast<u8>(new_database_size); + crc = GenerateDatabaseCrc(); +} + +void NintendoFigurineDatabase::CleanDatabase() { + miis = {}; + version = 1; + magic = DatabaseMagic; + database_length = 0; + crc = GenerateDatabaseCrc(); +} + +void NintendoFigurineDatabase::CorruptCrc() { + crc = GenerateDatabaseCrc(); + crc = ~crc; +} + +Result NintendoFigurineDatabase::CheckIntegrity() { + if (magic != DatabaseMagic) { + return ResultInvalidDatabaseSignature; + } + + if (version != 1) { + return ResultInvalidDatabaseVersion; + } + + if (crc != GenerateDatabaseCrc()) { + return ResultInvalidDatabaseChecksum; + } + + if (database_length >= MaxDatabaseLength) { + return ResultInvalidDatabaseLength; + } + + return ResultSuccess; +} + +u16 NintendoFigurineDatabase::GenerateDatabaseCrc() { + return MiiUtil::CalculateCrc16(&magic, sizeof(NintendoFigurineDatabase) - sizeof(crc)); +} + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_database.h b/src/core/hle/service/mii/mii_database.h new file mode 100644 index 000000000..3bd240f93 --- /dev/null +++ b/src/core/hle/service/mii/mii_database.h @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/result.h" +#include "core/hle/service/mii/types/store_data.h" + +namespace Service::Mii { + +constexpr std::size_t MaxDatabaseLength{100}; +constexpr u32 MiiMagic{0xa523b78f}; +constexpr u32 DatabaseMagic{0x4244464e}; // NFDB + +class NintendoFigurineDatabase { +public: + /// Returns the total mii count. + u8 GetDatabaseLength() const; + + /// Returns true if database is full. + bool IsFull() const; + + /// Returns the mii of the specified index. + StoreData Get(std::size_t index) const; + + /// Returns the total mii count. Ignoring special mii. + u32 GetCount(const DatabaseSessionMetadata& metadata) const; + + /// Returns the index of a mii. If the mii isn't found returns false. + bool GetIndexByCreatorId(u32& out_index, const Common::UUID& create_id) const; + + /// Moves the location of a specific mii. + Result Move(u32 current_index, u32 new_index); + + /// Replaces mii with new data. + void Replace(u32 index, const StoreData& store_data); + + /// Adds a new mii to the end of the database. + void Add(const StoreData& store_data); + + /// Removes mii from database and shifts left the remainding data. + void Delete(u32 index); + + /// Deletes all contents with a fresh database + void CleanDatabase(); + + /// Intentionally sets a bad checksum + void CorruptCrc(); + + /// Returns success if database is valid otherwise returns the corresponding error code. + Result CheckIntegrity(); + +private: + /// Returns the checksum of the database + u16 GenerateDatabaseCrc(); + + u32 magic{}; // 'NFDB' + std::array<StoreData, MaxDatabaseLength> miis{}; + u8 version{}; + u8 database_length{}; + u16 crc{}; +}; +static_assert(sizeof(NintendoFigurineDatabase) == 0x1A98, + "NintendoFigurineDatabase has incorrect size."); + +}; // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_database_manager.cpp b/src/core/hle/service/mii/mii_database_manager.cpp new file mode 100644 index 000000000..c39898594 --- /dev/null +++ b/src/core/hle/service/mii/mii_database_manager.cpp @@ -0,0 +1,420 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" +#include "common/string_util.h" + +#include "core/hle/service/mii/mii_database_manager.h" +#include "core/hle/service/mii/mii_result.h" +#include "core/hle/service/mii/mii_util.h" +#include "core/hle/service/mii/types/char_info.h" +#include "core/hle/service/mii/types/store_data.h" + +namespace Service::Mii { +const char* DbFileName = "MiiDatabase.dat"; + +DatabaseManager::DatabaseManager() {} + +Result DatabaseManager::MountSaveData() { + if (!is_save_data_mounted) { + system_save_dir = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000030"; + if (is_test_db) { + system_save_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / + "system/save/8000000000000031"; + } + + // mount point should be "mii:" + + if (!Common::FS::CreateDirs(system_save_dir)) { + return ResultUnknown; + } + } + + is_save_data_mounted = true; + return ResultSuccess; +} + +Result DatabaseManager::Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken) { + is_database_broken = false; + if (!is_save_data_mounted) { + return ResultInvalidArgument; + } + + database.CleanDatabase(); + update_counter++; + metadata.update_counter = update_counter; + + const Common::FS::IOFile db_file{system_save_dir / DbFileName, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + + if (!db_file.IsOpen()) { + return SaveDatabase(); + } + + if (Common::FS::GetSize(system_save_dir / DbFileName) != sizeof(NintendoFigurineDatabase)) { + is_database_broken = true; + } + + if (db_file.Read(database) != 1) { + is_database_broken = true; + } + + if (is_database_broken) { + // Dragons happen here for simplicity just clean the database + LOG_ERROR(Service_Mii, "Mii database is corrupted"); + database.CleanDatabase(); + return ResultUnknown; + } + + const auto result = database.CheckIntegrity(); + + if (result.IsError()) { + LOG_ERROR(Service_Mii, "Mii database is corrupted 0x{:0x}", result.raw); + database.CleanDatabase(); + return ResultSuccess; + } + + LOG_INFO(Service_Mii, "Successfully loaded mii database. size={}", + database.GetDatabaseLength()); + return ResultSuccess; +} + +bool DatabaseManager::IsFullDatabase() const { + return database.GetDatabaseLength() == MaxDatabaseLength; +} + +bool DatabaseManager::IsModified() const { + return is_moddified; +} + +u64 DatabaseManager::GetUpdateCounter() const { + return update_counter; +} + +u32 DatabaseManager::GetCount(const DatabaseSessionMetadata& metadata) const { + const u32 database_size = database.GetDatabaseLength(); + if (metadata.magic == MiiMagic) { + return database_size; + } + + // Special mii can't be used. Skip those. + + u32 mii_count{}; + for (std::size_t index = 0; index < database_size; ++index) { + const auto& store_data = database.Get(index); + if (store_data.IsSpecial()) { + continue; + } + mii_count++; + } + + return mii_count; +} + +void DatabaseManager::Get(StoreData& out_store_data, std::size_t index, + const DatabaseSessionMetadata& metadata) const { + if (metadata.magic == MiiMagic) { + out_store_data = database.Get(index); + return; + } + + // The index refeers to the mii index without special mii. + // Search on the database until we find it + + u32 virtual_index = 0; + const u32 database_size = database.GetDatabaseLength(); + for (std::size_t i = 0; i < database_size; ++i) { + const auto& store_data = database.Get(i); + if (store_data.IsSpecial()) { + continue; + } + if (virtual_index == index) { + out_store_data = store_data; + return; + } + virtual_index++; + } + + // This function doesn't fail. It returns the first mii instead + out_store_data = database.Get(0); +} + +Result DatabaseManager::FindIndex(s32& out_index, const Common::UUID& create_id, + bool is_special) const { + u32 index{}; + const bool is_found = database.GetIndexByCreatorId(index, create_id); + + if (!is_found) { + return ResultNotFound; + } + + if (is_special) { + out_index = index; + return ResultSuccess; + } + + if (database.Get(index).IsSpecial()) { + return ResultNotFound; + } + + out_index = 0; + + if (index < 1) { + return ResultSuccess; + } + + for (std::size_t i = 0; i <= index; ++i) { + if (database.Get(i).IsSpecial()) { + continue; + } + out_index++; + } + return ResultSuccess; +} + +Result DatabaseManager::FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index, + const Common::UUID& create_id) const { + u32 index{}; + const bool is_found = database.GetIndexByCreatorId(index, create_id); + + if (!is_found) { + return ResultNotFound; + } + + if (metadata.magic == MiiMagic) { + out_index = index; + return ResultSuccess; + } + + if (database.Get(index).IsSpecial()) { + return ResultNotFound; + } + + out_index = 0; + + if (index < 1) { + return ResultSuccess; + } + + // The index refeers to the mii index without special mii. + // Search on the database until we find it + + for (std::size_t i = 0; i <= index; ++i) { + const auto& store_data = database.Get(i); + if (store_data.IsSpecial()) { + continue; + } + out_index++; + } + return ResultSuccess; +} + +Result DatabaseManager::FindMoveIndex(u32& out_index, u32 new_index, + const Common::UUID& create_id) const { + const auto database_size = database.GetDatabaseLength(); + + if (database_size >= 1) { + u32 virtual_index{}; + for (std::size_t i = 0; i < database_size; ++i) { + const StoreData& store_data = database.Get(i); + if (store_data.IsSpecial()) { + continue; + } + if (virtual_index == new_index) { + const bool is_found = database.GetIndexByCreatorId(out_index, create_id); + if (!is_found) { + return ResultNotFound; + } + if (store_data.IsSpecial()) { + return ResultInvalidOperation; + } + return ResultSuccess; + } + virtual_index++; + } + } + + const bool is_found = database.GetIndexByCreatorId(out_index, create_id); + if (!is_found) { + return ResultNotFound; + } + const StoreData& store_data = database.Get(out_index); + if (store_data.IsSpecial()) { + return ResultInvalidOperation; + } + return ResultSuccess; +} + +Result DatabaseManager::Move(DatabaseSessionMetadata& metadata, u32 new_index, + const Common::UUID& create_id) { + u32 current_index{}; + if (metadata.magic == MiiMagic) { + const bool is_found = database.GetIndexByCreatorId(current_index, create_id); + if (!is_found) { + return ResultNotFound; + } + } else { + const auto result = FindMoveIndex(current_index, new_index, create_id); + if (result.IsError()) { + return result; + } + } + + const auto result = database.Move(current_index, new_index); + if (result.IsFailure()) { + return result; + } + + is_moddified = true; + update_counter++; + metadata.update_counter = update_counter; + return ResultSuccess; +} + +Result DatabaseManager::AddOrReplace(DatabaseSessionMetadata& metadata, + const StoreData& store_data) { + if (store_data.IsValid() != ValidationResult::NoErrors) { + return ResultInvalidStoreData; + } + if (metadata.magic != MiiMagic && store_data.IsSpecial()) { + return ResultInvalidOperation; + } + + u32 index{}; + const bool is_found = database.GetIndexByCreatorId(index, store_data.GetCreateId()); + if (is_found) { + const StoreData& old_store_data = database.Get(index); + + if (store_data.IsSpecial() != old_store_data.IsSpecial()) { + return ResultInvalidOperation; + } + + database.Replace(index, store_data); + } else { + if (database.IsFull()) { + return ResultDatabaseFull; + } + + database.Add(store_data); + } + + is_moddified = true; + update_counter++; + metadata.update_counter = update_counter; + return ResultSuccess; +} + +Result DatabaseManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) { + u32 index{}; + const bool is_found = database.GetIndexByCreatorId(index, create_id); + if (!is_found) { + return ResultNotFound; + } + + if (metadata.magic != MiiMagic) { + const auto& store_data = database.Get(index); + if (store_data.IsSpecial()) { + return ResultInvalidOperation; + } + } + + database.Delete(index); + + is_moddified = true; + update_counter++; + metadata.update_counter = update_counter; + return ResultSuccess; +} + +Result DatabaseManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) { + if (char_info.Verify() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo2; + } + if (char_info.GetType() == 1) { + return ResultInvalidCharInfoType; + } + + u32 index{}; + StoreData store_data{}; + + // Loop until the mii we created is not on the database + do { + store_data.BuildWithCharInfo(char_info); + } while (database.GetIndexByCreatorId(index, store_data.GetCreateId())); + + const Result result = store_data.Restore(); + + if (result.IsSuccess() || result == ResultNotUpdated) { + return AddOrReplace(metadata, store_data); + } + + return result; +} + +Result DatabaseManager::DestroyFile(DatabaseSessionMetadata& metadata) { + database.CorruptCrc(); + + is_moddified = true; + update_counter++; + metadata.update_counter = update_counter; + + const auto result = SaveDatabase(); + database.CleanDatabase(); + + return result; +} + +Result DatabaseManager::DeleteFile() { + const bool result = Common::FS::RemoveFile(system_save_dir / DbFileName); + // TODO: Return proper FS error here + return result ? ResultSuccess : ResultUnknown; +} + +void DatabaseManager::Format(DatabaseSessionMetadata& metadata) { + database.CleanDatabase(); + is_moddified = true; + update_counter++; + metadata.update_counter = update_counter; +} + +Result DatabaseManager::SaveDatabase() { + // TODO: Replace unknown error codes with proper FS error codes when available + + if (!Common::FS::Exists(system_save_dir / DbFileName)) { + if (!Common::FS::NewFile(system_save_dir / DbFileName)) { + LOG_ERROR(Service_Mii, "Failed to create mii database"); + return ResultUnknown; + } + } + + const auto file_size = Common::FS::GetSize(system_save_dir / DbFileName); + if (file_size != 0 && file_size != sizeof(NintendoFigurineDatabase)) { + if (!Common::FS::RemoveFile(system_save_dir / DbFileName)) { + LOG_ERROR(Service_Mii, "Failed to delete mii database"); + return ResultUnknown; + } + if (!Common::FS::NewFile(system_save_dir / DbFileName)) { + LOG_ERROR(Service_Mii, "Failed to create mii database"); + return ResultUnknown; + } + } + + const Common::FS::IOFile db_file{system_save_dir / DbFileName, + Common::FS::FileAccessMode::ReadWrite, + Common::FS::FileType::BinaryFile}; + + if (db_file.Write(database) != 1) { + LOG_ERROR(Service_Mii, "Failed to save mii database"); + return ResultUnknown; + } + + is_moddified = false; + return ResultSuccess; +} + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_database_manager.h b/src/core/hle/service/mii/mii_database_manager.h new file mode 100644 index 000000000..52c32be82 --- /dev/null +++ b/src/core/hle/service/mii/mii_database_manager.h @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/fs/fs.h" +#include "core/hle/result.h" +#include "core/hle/service/mii/mii_database.h" + +namespace Service::Mii { +class CharInfo; +class StoreData; + +class DatabaseManager { +public: + DatabaseManager(); + Result MountSaveData(); + Result Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken); + + bool IsFullDatabase() const; + bool IsModified() const; + u64 GetUpdateCounter() const; + + void Get(StoreData& out_store_data, std::size_t index, + const DatabaseSessionMetadata& metadata) const; + u32 GetCount(const DatabaseSessionMetadata& metadata) const; + + Result FindIndex(s32& out_index, const Common::UUID& create_id, bool is_special) const; + Result FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index, + const Common::UUID& create_id) const; + Result FindMoveIndex(u32& out_index, u32 new_index, const Common::UUID& create_id) const; + + Result Move(DatabaseSessionMetadata& metadata, u32 current_index, + const Common::UUID& create_id); + Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& out_store_data); + Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id); + Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info); + + Result DestroyFile(DatabaseSessionMetadata& metadata); + Result DeleteFile(); + void Format(DatabaseSessionMetadata& metadata); + + Result SaveDatabase(); + +private: + // This is the global value of + // nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled"); + bool is_test_db{}; + + bool is_moddified{}; + bool is_save_data_mounted{}; + u64 update_counter{}; + NintendoFigurineDatabase database{}; + + std::filesystem::path system_save_dir{}; +}; + +}; // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index 292d63777..a5a2a9232 100644 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp @@ -1,38 +1,63 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <cstring> -#include <random> - -#include "common/assert.h" #include "common/logging/log.h" -#include "common/string_util.h" - -#include "core/hle/service/acc/profile_manager.h" +#include "core/hle/service/mii/mii_database_manager.h" #include "core/hle/service/mii/mii_manager.h" #include "core/hle/service/mii/mii_result.h" #include "core/hle/service/mii/mii_util.h" +#include "core/hle/service/mii/types/char_info.h" #include "core/hle/service/mii/types/core_data.h" #include "core/hle/service/mii/types/raw_data.h" +#include "core/hle/service/mii/types/store_data.h" +#include "core/hle/service/mii/types/ver3_store_data.h" namespace Service::Mii { constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; MiiManager::MiiManager() {} +Result MiiManager::Initialize(DatabaseSessionMetadata& metadata) { + database_manager.MountSaveData(); + database_manager.Initialize(metadata, is_broken_with_clear_flag); + return ResultSuccess; +} + +void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const { + StoreData store_data{}; + store_data.BuildDefault(index); + out_char_info.SetFromStoreData(store_data); +} + +void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const { + StoreData store_data{}; + store_data.BuildBase(gender); + out_char_info.SetFromStoreData(store_data); +} + +void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const { + StoreData store_data{}; + store_data.BuildRandom(age, gender, race); + out_char_info.SetFromStoreData(store_data); +} + +bool MiiManager::IsFullDatabase() const { + return database_manager.IsFullDatabase(); +} + +void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const { + metadata.interface_version = version; +} + bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { if ((source_flag & SourceFlag::Database) == SourceFlag::None) { return false; } - const auto metadata_update_counter = metadata.update_counter; - metadata.update_counter = update_counter; - return metadata_update_counter != update_counter; -} - -bool MiiManager::IsFullDatabase() const { - // TODO(bunnei): We don't implement the Mii database, so it cannot be full - return false; + const u64 metadata_update_counter = metadata.update_counter; + const u64 database_update_counter = database_manager.GetUpdateCounter(); + metadata.update_counter = database_update_counter; + return metadata_update_counter != database_update_counter; } u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { @@ -41,72 +66,343 @@ u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag sou mii_count += DefaultMiiCount; } if ((source_flag & SourceFlag::Database) != SourceFlag::None) { - // TODO(bunnei): We don't implement the Mii database, but when we do, update this + mii_count += database_manager.GetCount(metadata); } return mii_count; } -Result MiiManager::UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info, - const CharInfo& char_info, SourceFlag source_flag) { - if ((source_flag & SourceFlag::Database) == SourceFlag::None) { +Result MiiManager::Move(DatabaseSessionMetadata& metadata, u32 index, + const Common::UUID& create_id) { + const auto result = database_manager.Move(metadata, index, create_id); + + if (result.IsFailure()) { + return result; + } + + if (!database_manager.IsModified()) { + return ResultNotUpdated; + } + + return database_manager.SaveDatabase(); +} + +Result MiiManager::AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data) { + const auto result = database_manager.AddOrReplace(metadata, store_data); + + if (result.IsFailure()) { + return result; + } + + if (!database_manager.IsModified()) { + return ResultNotUpdated; + } + + return database_manager.SaveDatabase(); +} + +Result MiiManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) { + const auto result = database_manager.Delete(metadata, create_id); + + if (result.IsFailure()) { + return result; + } + + if (!database_manager.IsModified()) { + return ResultNotUpdated; + } + + return database_manager.SaveDatabase(); +} + +s32 MiiManager::FindIndex(const Common::UUID& create_id, bool is_special) const { + s32 index{}; + const auto result = database_manager.FindIndex(index, create_id, is_special); + if (result.IsError()) { + index = -1; + } + return index; +} + +Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, + s32& out_index) const { + if (char_info.Verify() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo; + } + + s32 index{}; + Result result = {}; + // FindIndex(index); + + if (result.IsError()) { + return ResultNotFound; + } + + if (index == -1) { return ResultNotFound; } - // TODO(bunnei): We don't implement the Mii database, so we can't have an entry - return ResultNotFound; + out_index = index; + return ResultSuccess; } -void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const { - StoreData store_data{}; - store_data.BuildDefault(index); - out_char_info.SetFromStoreData(store_data); +Result MiiManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) { + const auto result = database_manager.Append(metadata, char_info); + + if (result.IsError()) { + return ResultNotFound; + } + + if (!database_manager.IsModified()) { + return ResultNotUpdated; + } + + return database_manager.SaveDatabase(); } -void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const { +bool MiiManager::IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata) { + const bool is_broken = is_broken_with_clear_flag; + if (is_broken_with_clear_flag) { + is_broken_with_clear_flag = false; + database_manager.Format(metadata); + database_manager.SaveDatabase(); + } + return is_broken; +} + +Result MiiManager::DestroyFile(DatabaseSessionMetadata& metadata) { + is_broken_with_clear_flag = true; + return database_manager.DestroyFile(metadata); +} + +Result MiiManager::DeleteFile() { + return database_manager.DeleteFile(); +} + +Result MiiManager::Format(DatabaseSessionMetadata& metadata) { + database_manager.Format(metadata); + + if (!database_manager.IsModified()) { + return ResultNotUpdated; + } + return database_manager.SaveDatabase(); +} + +Result MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const { + if (!mii_v3.IsValid()) { + return ResultInvalidCharInfo; + } + StoreData store_data{}; - store_data.BuildBase(gender); + mii_v3.BuildToStoreData(store_data); + const auto name = store_data.GetNickname(); + if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) { + store_data.SetInvalidName(); + } + out_char_info.SetFromStoreData(store_data); + return ResultSuccess; } -void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const { +Result MiiManager::ConvertCoreDataToCharInfo(CharInfo& out_char_info, + const CoreData& core_data) const { + if (core_data.IsValid() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo; + } + StoreData store_data{}; - store_data.BuildRandom(age, gender, race); + store_data.BuildWithCoreData(core_data); + const auto name = store_data.GetNickname(); + if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) { + store_data.SetInvalidName(); + } + out_char_info.SetFromStoreData(store_data); + return ResultSuccess; +} + +Result MiiManager::ConvertCharInfoToCoreData(CoreData& out_core_data, + const CharInfo& char_info) const { + if (char_info.Verify() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo; + } + + out_core_data.BuildFromCharInfo(char_info); + const auto name = out_core_data.GetNickname(); + if (!MiiUtil::IsFontRegionValid(out_core_data.GetFontRegion(), name.data)) { + out_core_data.SetNickname(out_core_data.GetInvalidNickname()); + } + + return ResultSuccess; } -void MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const { +Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info, + const CharInfo& char_info, SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return ResultNotFound; + } + + if (metadata.IsInterfaceVersionSupported(1)) { + if (char_info.Verify() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo; + } + } + + u32 index{}; + Result result = database_manager.FindIndex(metadata, index, char_info.GetCreateId()); + + if (result.IsError()) { + return result; + } + StoreData store_data{}; - mii_v3.BuildToStoreData(store_data); + database_manager.Get(store_data, index, metadata); + + if (store_data.GetType() != char_info.GetType()) { + return ResultNotFound; + } + out_char_info.SetFromStoreData(store_data); + + if (char_info == out_char_info) { + return ResultNotUpdated; + } + + return ResultSuccess; +} + +Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data, + const StoreData& store_data, SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return ResultNotFound; + } + + if (metadata.IsInterfaceVersionSupported(1)) { + if (store_data.IsValid() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo; + } + } + + u32 index{}; + Result result = database_manager.FindIndex(metadata, index, store_data.GetCreateId()); + + if (result.IsError()) { + return result; + } + + database_manager.Get(out_store_data, index, metadata); + + if (out_store_data.GetType() != store_data.GetType()) { + return ResultNotFound; + } + + if (store_data == out_store_data) { + return ResultNotUpdated; + } + + return ResultSuccess; } Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements, u32& out_count, - SourceFlag source_flag) { + SourceFlag source_flag) const { if ((source_flag & SourceFlag::Database) == SourceFlag::None) { return BuildDefault(out_elements, out_count, source_flag); } - // TODO(bunnei): We don't implement the Mii database, so we can't have an entry + const auto mii_count = database_manager.GetCount(metadata); + + for (std::size_t index = 0; index < mii_count; ++index) { + if (out_elements.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } + + StoreData store_data{}; + database_manager.Get(store_data, index, metadata); + + out_elements[out_count].source = Source::Database; + out_elements[out_count].char_info.SetFromStoreData(store_data); + out_count++; + } // Include default Mii at the end of the list return BuildDefault(out_elements, out_count, source_flag); } Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, - u32& out_count, SourceFlag source_flag) { + u32& out_count, SourceFlag source_flag) const { if ((source_flag & SourceFlag::Database) == SourceFlag::None) { return BuildDefault(out_char_info, out_count, source_flag); } - // TODO(bunnei): We don't implement the Mii database, so we can't have an entry + const auto mii_count = database_manager.GetCount(metadata); + + for (std::size_t index = 0; index < mii_count; ++index) { + if (out_char_info.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } + + StoreData store_data{}; + database_manager.Get(store_data, index, metadata); + + out_char_info[out_count].SetFromStoreData(store_data); + out_count++; + } // Include default Mii at the end of the list return BuildDefault(out_char_info, out_count, source_flag); } +Result MiiManager::Get(const DatabaseSessionMetadata& metadata, + std::span<StoreDataElement> out_elements, u32& out_count, + SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return BuildDefault(out_elements, out_count, source_flag); + } + + const auto mii_count = database_manager.GetCount(metadata); + + for (std::size_t index = 0; index < mii_count; ++index) { + if (out_elements.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } + + StoreData store_data{}; + database_manager.Get(store_data, index, metadata); + + out_elements[out_count].store_data = store_data; + out_elements[out_count].source = Source::Database; + out_count++; + } + + // Include default Mii at the end of the list + return BuildDefault(out_elements, out_count, source_flag); +} + +Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data, + u32& out_count, SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return BuildDefault(out_store_data, out_count, source_flag); + } + + const auto mii_count = database_manager.GetCount(metadata); + + for (std::size_t index = 0; index < mii_count; ++index) { + if (out_store_data.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } + + StoreData store_data{}; + database_manager.Get(store_data, index, metadata); + + out_store_data[out_count] = store_data; + out_count++; + } + + // Include default Mii at the end of the list + return BuildDefault(out_store_data, out_count, source_flag); +} Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, - SourceFlag source_flag) { + SourceFlag source_flag) const { if ((source_flag & SourceFlag::Default) == SourceFlag::None) { return ResultSuccess; } @@ -129,7 +425,7 @@ Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& ou } Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, - SourceFlag source_flag) { + SourceFlag source_flag) const { if ((source_flag & SourceFlag::Default) == SourceFlag::None) { return ResultSuccess; } @@ -150,23 +446,41 @@ Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_coun return ResultSuccess; } -Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, - s32& out_index) { - - if (char_info.Verify() != ValidationResult::NoErrors) { - return ResultInvalidCharInfo; +Result MiiManager::BuildDefault(std::span<StoreDataElement> out_elements, u32& out_count, + SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Default) == SourceFlag::None) { + return ResultSuccess; } - constexpr u32 INVALID_INDEX{0xFFFFFFFF}; + for (std::size_t index = 0; index < DefaultMiiCount; ++index) { + if (out_elements.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } - out_index = INVALID_INDEX; + out_elements[out_count].store_data.BuildDefault(static_cast<u32>(index)); + out_elements[out_count].source = Source::Default; + out_count++; + } - // TODO(bunnei): We don't implement the Mii database, so we can't have an index - return ResultNotFound; + return ResultSuccess; } -void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) { - metadata.interface_version = version; +Result MiiManager::BuildDefault(std::span<StoreData> out_char_info, u32& out_count, + SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Default) == SourceFlag::None) { + return ResultSuccess; + } + + for (std::size_t index = 0; index < DefaultMiiCount; ++index) { + if (out_char_info.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } + + out_char_info[out_count].BuildDefault(static_cast<u32>(index)); + out_count++; + } + + return ResultSuccess; } } // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h index a2e7a6d73..48d8e8bb7 100644 --- a/src/core/hle/service/mii/mii_manager.h +++ b/src/core/hle/service/mii/mii_manager.h @@ -3,47 +3,85 @@ #pragma once -#include <vector> +#include <span> #include "core/hle/result.h" +#include "core/hle/service/mii/mii_database_manager.h" #include "core/hle/service/mii/mii_types.h" -#include "core/hle/service/mii/types/char_info.h" -#include "core/hle/service/mii/types/store_data.h" -#include "core/hle/service/mii/types/ver3_store_data.h" namespace Service::Mii { +class CharInfo; +class CoreData; +class StoreData; +class Ver3StoreData; -// The Mii manager is responsible for loading and storing the Miis to the database in NAND along -// with providing an easy interface for HLE emulation of the mii service. +struct CharInfoElement; +struct StoreDataElement; + +// The Mii manager is responsible for handling mii operations along with providing an easy interface +// for HLE emulation of the mii service. class MiiManager { public: MiiManager(); + Result Initialize(DatabaseSessionMetadata& metadata); - bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; + // Auto generated mii + void BuildDefault(CharInfo& out_char_info, u32 index) const; + void BuildBase(CharInfo& out_char_info, Gender gender) const; + void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const; + // Database operations bool IsFullDatabase() const; + void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const; + bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; - Result UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info, - const CharInfo& char_info, SourceFlag source_flag); + Result Move(DatabaseSessionMetadata& metadata, u32 index, const Common::UUID& create_id); + Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data); + Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id); + s32 FindIndex(const Common::UUID& create_id, bool is_special) const; + Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, + s32& out_index) const; + Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info); + + // Test database operations + bool IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata); + Result DestroyFile(DatabaseSessionMetadata& metadata); + Result DeleteFile(); + Result Format(DatabaseSessionMetadata& metadata); + + // Mii conversions + Result ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const; + Result ConvertCoreDataToCharInfo(CharInfo& out_char_info, const CoreData& core_data) const; + Result ConvertCharInfoToCoreData(CoreData& out_core_data, const CharInfo& char_info) const; + Result UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info, + const CharInfo& char_info, SourceFlag source_flag) const; + Result UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data, + const StoreData& store_data, SourceFlag source_flag) const; + + // Overloaded getters Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements, - u32& out_count, SourceFlag source_flag); + u32& out_count, SourceFlag source_flag) const; Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, - u32& out_count, SourceFlag source_flag); - void BuildDefault(CharInfo& out_char_info, u32 index) const; - void BuildBase(CharInfo& out_char_info, Gender gender) const; - void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const; - void ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const; - std::vector<CharInfoElement> GetDefault(SourceFlag source_flag); - Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, - s32& out_index); - void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version); + u32& out_count, SourceFlag source_flag) const; + Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreDataElement> out_elements, + u32& out_count, SourceFlag source_flag) const; + Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data, + u32& out_count, SourceFlag source_flag) const; private: Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, - SourceFlag source_flag); - Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, SourceFlag source_flag); + SourceFlag source_flag) const; + Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, + SourceFlag source_flag) const; + Result BuildDefault(std::span<StoreDataElement> out_char_info, u32& out_count, + SourceFlag source_flag) const; + Result BuildDefault(std::span<StoreData> out_char_info, u32& out_count, + SourceFlag source_flag) const; + + DatabaseManager database_manager{}; - u64 update_counter{}; + // This should be a global value + bool is_broken_with_clear_flag{}; }; }; // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_result.h b/src/core/hle/service/mii/mii_result.h index 021cb76da..e2c36e556 100644 --- a/src/core/hle/service/mii/mii_result.h +++ b/src/core/hle/service/mii/mii_result.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -13,8 +13,15 @@ constexpr Result ResultNotUpdated{ErrorModule::Mii, 3}; constexpr Result ResultNotFound{ErrorModule::Mii, 4}; constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5}; constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100}; +constexpr Result ResultInvalidDatabaseChecksum{ErrorModule::Mii, 101}; +constexpr Result ResultInvalidDatabaseSignature{ErrorModule::Mii, 103}; +constexpr Result ResultInvalidDatabaseVersion{ErrorModule::Mii, 104}; +constexpr Result ResultInvalidDatabaseLength{ErrorModule::Mii, 105}; +constexpr Result ResultInvalidCharInfo2{ErrorModule::Mii, 107}; constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109}; constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202}; constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203}; +constexpr Result ResultTestModeOnly{ErrorModule::Mii, 204}; +constexpr Result ResultInvalidCharInfoType{ErrorModule::Mii, 205}; }; // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_types.h b/src/core/hle/service/mii/mii_types.h index 95476f745..f43efd83c 100644 --- a/src/core/hle/service/mii/mii_types.h +++ b/src/core/hle/service/mii/mii_types.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -13,6 +13,7 @@ namespace Service::Mii { +constexpr std::size_t MaxNameSize = 10; constexpr u8 MaxHeight = 127; constexpr u8 MaxBuild = 127; constexpr u8 MaxType = 1; @@ -26,14 +27,14 @@ constexpr u8 MaxEyebrowScale = 8; constexpr u8 MaxEyebrowAspect = 6; constexpr u8 MaxEyebrowRotate = 11; constexpr u8 MaxEyebrowX = 12; -constexpr u8 MaxEyebrowY = 18; +constexpr u8 MaxEyebrowY = 15; constexpr u8 MaxNoseScale = 8; constexpr u8 MaxNoseY = 18; constexpr u8 MaxMouthScale = 8; constexpr u8 MaxMoutAspect = 6; constexpr u8 MaxMouthY = 18; constexpr u8 MaxMustacheScale = 8; -constexpr u8 MasMustacheY = 16; +constexpr u8 MaxMustacheY = 16; constexpr u8 MaxGlassScale = 7; constexpr u8 MaxGlassY = 20; constexpr u8 MaxMoleScale = 8; @@ -599,22 +600,19 @@ enum class ValidationResult : u32 { InvalidRegionMove = 0x31, InvalidCreateId = 0x32, InvalidName = 0x33, + InvalidChecksum = 0x34, InvalidType = 0x35, }; struct Nickname { - static constexpr std::size_t MaxNameSize = 10; - std::array<char16_t, MaxNameSize> data; + std::array<char16_t, MaxNameSize> data{}; - // Checks for null, non-zero terminated or dirty strings + // Checks for null or dirty strings bool IsValid() const { if (data[0] == 0) { return false; } - if (data[MaxNameSize] != 0) { - return false; - } std::size_t index = 1; while (data[index] != 0) { index++; diff --git a/src/core/hle/service/mii/mii_util.h b/src/core/hle/service/mii/mii_util.h index ddb544c23..3534fa31d 100644 --- a/src/core/hle/service/mii/mii_util.h +++ b/src/core/hle/service/mii/mii_util.h @@ -28,6 +28,32 @@ public: return Common::swap16(static_cast<u16>(crc)); } + static u16 CalculateDeviceCrc16(const Common::UUID& uuid, std::size_t data_size) { + constexpr u16 magic{0x1021}; + s32 crc{}; + + for (std::size_t i = 0; i < uuid.uuid.size(); i++) { + for (std::size_t j = 0; j < 8; j++) { + crc <<= 1; + if ((crc & 0x10000) != 0) { + crc = crc ^ magic; + } + } + crc ^= uuid.uuid[i]; + } + + // As much as this looks wrong this is what N's does + + for (std::size_t i = 0; i < data_size * 8; i++) { + crc <<= 1; + if ((crc & 0x10000) != 0) { + crc = crc ^ magic; + } + } + + return Common::swap16(static_cast<u16>(crc)); + } + static Common::UUID MakeCreateId() { return Common::UUID::MakeRandomRFC4122V4(); } diff --git a/src/core/hle/service/mii/types/char_info.cpp b/src/core/hle/service/mii/types/char_info.cpp index bb948c628..e90124af4 100644 --- a/src/core/hle/service/mii/types/char_info.cpp +++ b/src/core/hle/service/mii/types/char_info.cpp @@ -37,7 +37,7 @@ void CharInfo::SetFromStoreData(const StoreData& store_data) { eyebrow_aspect = store_data.GetEyebrowAspect(); eyebrow_rotate = store_data.GetEyebrowRotate(); eyebrow_x = store_data.GetEyebrowX(); - eyebrow_y = store_data.GetEyebrowY(); + eyebrow_y = store_data.GetEyebrowY() + 3; nose_type = store_data.GetNoseType(); nose_scale = store_data.GetNoseScale(); nose_y = store_data.GetNoseY(); @@ -150,7 +150,7 @@ ValidationResult CharInfo::Verify() const { if (eyebrow_x > MaxEyebrowX) { return ValidationResult::InvalidEyebrowX; } - if (eyebrow_y > MaxEyebrowY) { + if (eyebrow_y - 3 > MaxEyebrowY) { return ValidationResult::InvalidEyebrowY; } if (nose_type > NoseType::Max) { @@ -189,7 +189,7 @@ ValidationResult CharInfo::Verify() const { if (mustache_scale > MaxMustacheScale) { return ValidationResult::InvalidMustacheScale; } - if (mustache_y > MasMustacheY) { + if (mustache_y > MaxMustacheY) { return ValidationResult::InvalidMustacheY; } if (glass_type > GlassType::Max) { diff --git a/src/core/hle/service/mii/types/char_info.h b/src/core/hle/service/mii/types/char_info.h index d069b221f..d0c457fd5 100644 --- a/src/core/hle/service/mii/types/char_info.h +++ b/src/core/hle/service/mii/types/char_info.h @@ -70,59 +70,59 @@ public: bool operator==(const CharInfo& info); private: - Common::UUID create_id; - Nickname name; - u16 null_terminator; - FontRegion font_region; - FavoriteColor favorite_color; - Gender gender; - u8 height; - u8 build; - u8 type; - u8 region_move; - FacelineType faceline_type; - FacelineColor faceline_color; - FacelineWrinkle faceline_wrinkle; - FacelineMake faceline_make; - HairType hair_type; - CommonColor hair_color; - HairFlip hair_flip; - EyeType eye_type; - CommonColor eye_color; - u8 eye_scale; - u8 eye_aspect; - u8 eye_rotate; - u8 eye_x; - u8 eye_y; - EyebrowType eyebrow_type; - CommonColor eyebrow_color; - u8 eyebrow_scale; - u8 eyebrow_aspect; - u8 eyebrow_rotate; - u8 eyebrow_x; - u8 eyebrow_y; - NoseType nose_type; - u8 nose_scale; - u8 nose_y; - MouthType mouth_type; - CommonColor mouth_color; - u8 mouth_scale; - u8 mouth_aspect; - u8 mouth_y; - CommonColor beard_color; - BeardType beard_type; - MustacheType mustache_type; - u8 mustache_scale; - u8 mustache_y; - GlassType glass_type; - CommonColor glass_color; - u8 glass_scale; - u8 glass_y; - MoleType mole_type; - u8 mole_scale; - u8 mole_x; - u8 mole_y; - u8 padding; + Common::UUID create_id{}; + Nickname name{}; + u16 null_terminator{}; + FontRegion font_region{}; + FavoriteColor favorite_color{}; + Gender gender{}; + u8 height{}; + u8 build{}; + u8 type{}; + u8 region_move{}; + FacelineType faceline_type{}; + FacelineColor faceline_color{}; + FacelineWrinkle faceline_wrinkle{}; + FacelineMake faceline_make{}; + HairType hair_type{}; + CommonColor hair_color{}; + HairFlip hair_flip{}; + EyeType eye_type{}; + CommonColor eye_color{}; + u8 eye_scale{}; + u8 eye_aspect{}; + u8 eye_rotate{}; + u8 eye_x{}; + u8 eye_y{}; + EyebrowType eyebrow_type{}; + CommonColor eyebrow_color{}; + u8 eyebrow_scale{}; + u8 eyebrow_aspect{}; + u8 eyebrow_rotate{}; + u8 eyebrow_x{}; + u8 eyebrow_y{}; + NoseType nose_type{}; + u8 nose_scale{}; + u8 nose_y{}; + MouthType mouth_type{}; + CommonColor mouth_color{}; + u8 mouth_scale{}; + u8 mouth_aspect{}; + u8 mouth_y{}; + CommonColor beard_color{}; + BeardType beard_type{}; + MustacheType mustache_type{}; + u8 mustache_scale{}; + u8 mustache_y{}; + GlassType glass_type{}; + CommonColor glass_color{}; + u8 glass_scale{}; + u8 glass_y{}; + MoleType mole_type{}; + u8 mole_scale{}; + u8 mole_x{}; + u8 mole_y{}; + u8 padding{}; }; static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size."); static_assert(std::has_unique_object_representations_v<CharInfo>, diff --git a/src/core/hle/service/mii/types/core_data.cpp b/src/core/hle/service/mii/types/core_data.cpp index 659288b51..465c6293a 100644 --- a/src/core/hle/service/mii/types/core_data.cpp +++ b/src/core/hle/service/mii/types/core_data.cpp @@ -3,6 +3,7 @@ #include "common/assert.h" #include "core/hle/service/mii/mii_util.h" +#include "core/hle/service/mii/types/char_info.h" #include "core/hle/service/mii/types/core_data.h" #include "core/hle/service/mii/types/raw_data.h" @@ -185,9 +186,211 @@ void CoreData::BuildRandom(Age age, Gender gender, Race race) { SetMoleY(20); } -u32 CoreData::IsValid() const { - // TODO: Complete this - return 0; +void CoreData::BuildFromCharInfo(const CharInfo& char_info) { + name = char_info.GetNickname(); + SetFontRegion(char_info.GetFontRegion()); + SetFavoriteColor(char_info.GetFavoriteColor()); + SetGender(char_info.GetGender()); + SetHeight(char_info.GetHeight()); + SetBuild(char_info.GetBuild()); + SetType(char_info.GetType()); + SetRegionMove(char_info.GetRegionMove()); + SetFacelineType(char_info.GetFacelineType()); + SetFacelineColor(char_info.GetFacelineColor()); + SetFacelineWrinkle(char_info.GetFacelineWrinkle()); + SetFacelineMake(char_info.GetFacelineMake()); + SetHairType(char_info.GetHairType()); + SetHairColor(char_info.GetHairColor()); + SetHairFlip(char_info.GetHairFlip()); + SetEyeType(char_info.GetEyeType()); + SetEyeColor(char_info.GetEyeColor()); + SetEyeScale(char_info.GetEyeScale()); + SetEyeAspect(char_info.GetEyeAspect()); + SetEyeRotate(char_info.GetEyeRotate()); + SetEyeX(char_info.GetEyeX()); + SetEyeY(char_info.GetEyeY()); + SetEyebrowType(char_info.GetEyebrowType()); + SetEyebrowColor(char_info.GetEyebrowColor()); + SetEyebrowScale(char_info.GetEyebrowScale()); + SetEyebrowAspect(char_info.GetEyebrowAspect()); + SetEyebrowRotate(char_info.GetEyebrowRotate()); + SetEyebrowX(char_info.GetEyebrowX()); + SetEyebrowY(char_info.GetEyebrowY() - 3); + SetNoseType(char_info.GetNoseType()); + SetNoseScale(char_info.GetNoseScale()); + SetNoseY(char_info.GetNoseY()); + SetMouthType(char_info.GetMouthType()); + SetMouthColor(char_info.GetMouthColor()); + SetMouthScale(char_info.GetMouthScale()); + SetMouthAspect(char_info.GetMouthAspect()); + SetMouthY(char_info.GetMouthY()); + SetBeardColor(char_info.GetBeardColor()); + SetBeardType(char_info.GetBeardType()); + SetMustacheType(char_info.GetMustacheType()); + SetMustacheScale(char_info.GetMustacheScale()); + SetMustacheY(char_info.GetMustacheY()); + SetGlassType(char_info.GetGlassType()); + SetGlassColor(char_info.GetGlassColor()); + SetGlassScale(char_info.GetGlassScale()); + SetGlassY(char_info.GetGlassY()); + SetMoleType(char_info.GetMoleType()); + SetMoleScale(char_info.GetMoleScale()); + SetMoleX(char_info.GetMoleX()); + SetMoleY(char_info.GetMoleY()); +} + +ValidationResult CoreData::IsValid() const { + if (!name.IsValid()) { + return ValidationResult::InvalidName; + } + if (GetFontRegion() > FontRegion::Max) { + return ValidationResult::InvalidFont; + } + if (GetFavoriteColor() > FavoriteColor::Max) { + return ValidationResult::InvalidColor; + } + if (GetGender() > Gender::Max) { + return ValidationResult::InvalidGender; + } + if (GetHeight() > MaxHeight) { + return ValidationResult::InvalidHeight; + } + if (GetBuild() > MaxBuild) { + return ValidationResult::InvalidBuild; + } + if (GetType() > MaxType) { + return ValidationResult::InvalidType; + } + if (GetRegionMove() > MaxRegionMove) { + return ValidationResult::InvalidRegionMove; + } + if (GetFacelineType() > FacelineType::Max) { + return ValidationResult::InvalidFacelineType; + } + if (GetFacelineColor() > FacelineColor::Max) { + return ValidationResult::InvalidFacelineColor; + } + if (GetFacelineWrinkle() > FacelineWrinkle::Max) { + return ValidationResult::InvalidFacelineWrinkle; + } + if (GetFacelineMake() > FacelineMake::Max) { + return ValidationResult::InvalidFacelineMake; + } + if (GetHairType() > HairType::Max) { + return ValidationResult::InvalidHairType; + } + if (GetHairColor() > CommonColor::Max) { + return ValidationResult::InvalidHairColor; + } + if (GetHairFlip() > HairFlip::Max) { + return ValidationResult::InvalidHairFlip; + } + if (GetEyeType() > EyeType::Max) { + return ValidationResult::InvalidEyeType; + } + if (GetEyeColor() > CommonColor::Max) { + return ValidationResult::InvalidEyeColor; + } + if (GetEyeScale() > MaxEyeScale) { + return ValidationResult::InvalidEyeScale; + } + if (GetEyeAspect() > MaxEyeAspect) { + return ValidationResult::InvalidEyeAspect; + } + if (GetEyeRotate() > MaxEyeRotate) { + return ValidationResult::InvalidEyeRotate; + } + if (GetEyeX() > MaxEyeX) { + return ValidationResult::InvalidEyeX; + } + if (GetEyeY() > MaxEyeY) { + return ValidationResult::InvalidEyeY; + } + if (GetEyebrowType() > EyebrowType::Max) { + return ValidationResult::InvalidEyebrowType; + } + if (GetEyebrowColor() > CommonColor::Max) { + return ValidationResult::InvalidEyebrowColor; + } + if (GetEyebrowScale() > MaxEyebrowScale) { + return ValidationResult::InvalidEyebrowScale; + } + if (GetEyebrowAspect() > MaxEyebrowAspect) { + return ValidationResult::InvalidEyebrowAspect; + } + if (GetEyebrowRotate() > MaxEyebrowRotate) { + return ValidationResult::InvalidEyebrowRotate; + } + if (GetEyebrowX() > MaxEyebrowX) { + return ValidationResult::InvalidEyebrowX; + } + if (GetEyebrowY() > MaxEyebrowY) { + return ValidationResult::InvalidEyebrowY; + } + if (GetNoseType() > NoseType::Max) { + return ValidationResult::InvalidNoseType; + } + if (GetNoseScale() > MaxNoseScale) { + return ValidationResult::InvalidNoseScale; + } + if (GetNoseY() > MaxNoseY) { + return ValidationResult::InvalidNoseY; + } + if (GetMouthType() > MouthType::Max) { + return ValidationResult::InvalidMouthType; + } + if (GetMouthColor() > CommonColor::Max) { + return ValidationResult::InvalidMouthColor; + } + if (GetMouthScale() > MaxMouthScale) { + return ValidationResult::InvalidMouthScale; + } + if (GetMouthAspect() > MaxMoutAspect) { + return ValidationResult::InvalidMouthAspect; + } + if (GetMouthY() > MaxMouthY) { + return ValidationResult::InvalidMouthY; + } + if (GetBeardColor() > CommonColor::Max) { + return ValidationResult::InvalidBeardColor; + } + if (GetBeardType() > BeardType::Max) { + return ValidationResult::InvalidBeardType; + } + if (GetMustacheType() > MustacheType::Max) { + return ValidationResult::InvalidMustacheType; + } + if (GetMustacheScale() > MaxMustacheScale) { + return ValidationResult::InvalidMustacheScale; + } + if (GetMustacheY() > MaxMustacheY) { + return ValidationResult::InvalidMustacheY; + } + if (GetGlassType() > GlassType::Max) { + return ValidationResult::InvalidGlassType; + } + if (GetGlassColor() > CommonColor::Max) { + return ValidationResult::InvalidGlassColor; + } + if (GetGlassScale() > MaxGlassScale) { + return ValidationResult::InvalidGlassScale; + } + if (GetGlassY() > MaxGlassY) { + return ValidationResult::InvalidGlassY; + } + if (GetMoleType() > MoleType::Max) { + return ValidationResult::InvalidMoleType; + } + if (GetMoleScale() > MaxMoleScale) { + return ValidationResult::InvalidMoleScale; + } + if (GetMoleX() > MaxMoleX) { + return ValidationResult::InvalidMoleX; + } + if (GetMoleY() > MaxMoleY) { + return ValidationResult::InvalidMoleY; + } + return ValidationResult::NoErrors; } void CoreData::SetFontRegion(FontRegion value) { @@ -314,8 +517,8 @@ void CoreData::SetNoseY(u8 value) { data.nose_y.Assign(value); } -void CoreData::SetMouthType(u8 value) { - data.mouth_type.Assign(value); +void CoreData::SetMouthType(MouthType value) { + data.mouth_type.Assign(static_cast<u32>(value)); } void CoreData::SetMouthColor(CommonColor value) { diff --git a/src/core/hle/service/mii/types/core_data.h b/src/core/hle/service/mii/types/core_data.h index cebcd2ee4..8897e4f3b 100644 --- a/src/core/hle/service/mii/types/core_data.h +++ b/src/core/hle/service/mii/types/core_data.h @@ -6,6 +6,7 @@ #include "core/hle/service/mii/mii_types.h" namespace Service::Mii { +class CharInfo; struct StoreDataBitFields { union { @@ -100,8 +101,9 @@ class CoreData { public: void SetDefault(); void BuildRandom(Age age, Gender gender, Race race); + void BuildFromCharInfo(const CharInfo& char_info); - u32 IsValid() const; + ValidationResult IsValid() const; void SetFontRegion(FontRegion value); void SetFavoriteColor(FavoriteColor value); @@ -134,7 +136,7 @@ public: void SetNoseType(NoseType value); void SetNoseScale(u8 value); void SetNoseY(u8 value); - void SetMouthType(u8 value); + void SetMouthType(MouthType value); void SetMouthColor(CommonColor value); void SetMouthScale(u8 value); void SetMouthAspect(u8 value); @@ -212,5 +214,6 @@ private: Nickname name{}; }; static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size."); +static_assert(std::is_trivially_copyable_v<CoreData>, "CoreData type must be trivially copyable."); }; // namespace Service::Mii diff --git a/src/core/hle/service/mii/types/store_data.cpp b/src/core/hle/service/mii/types/store_data.cpp index 8fce636c7..127221fdb 100644 --- a/src/core/hle/service/mii/types/store_data.cpp +++ b/src/core/hle/service/mii/types/store_data.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "core/hle/service/mii/mii_result.h" #include "core/hle/service/mii/mii_util.h" #include "core/hle/service/mii/types/raw_data.h" #include "core/hle/service/mii/types/store_data.h" @@ -35,13 +36,13 @@ void StoreData::BuildDefault(u32 mii_index) { core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); - core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y)); + core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3)); core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); - core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type)); + core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type)); core_data.SetMouthColor( RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); @@ -75,10 +76,8 @@ void StoreData::BuildDefault(u32 mii_index) { core_data.SetType(static_cast<u8>(default_mii.type)); core_data.SetNickname(default_mii.nickname); - const auto device_id = MiiUtil::GetDeviceId(); create_id = MiiUtil::MakeCreateId(); - device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); - data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); + SetChecksum(); } void StoreData::BuildBase(Gender gender) { @@ -109,13 +108,13 @@ void StoreData::BuildBase(Gender gender) { core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); - core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y)); + core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3)); core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); - core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type)); + core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type)); core_data.SetMouthColor( RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); @@ -149,37 +148,51 @@ void StoreData::BuildBase(Gender gender) { core_data.SetType(static_cast<u8>(default_mii.type)); core_data.SetNickname(default_mii.nickname); - const auto device_id = MiiUtil::GetDeviceId(); create_id = MiiUtil::MakeCreateId(); - device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); - data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); + SetChecksum(); } void StoreData::BuildRandom(Age age, Gender gender, Race race) { core_data.BuildRandom(age, gender, race); - const auto device_id = MiiUtil::GetDeviceId(); create_id = MiiUtil::MakeCreateId(); - device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); - data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); + SetChecksum(); } -void StoreData::SetInvalidName() { - const auto& invalid_name = core_data.GetInvalidNickname(); +void StoreData::BuildWithCharInfo(const CharInfo& char_info) { + core_data.BuildFromCharInfo(char_info); + create_id = MiiUtil::MakeCreateId(); + SetChecksum(); +} + +void StoreData::BuildWithCoreData(const CoreData& in_core_data) { + core_data = in_core_data; + create_id = MiiUtil::MakeCreateId(); + SetChecksum(); +} + +Result StoreData::Restore() { + // TODO: Implement this + return ResultNotUpdated; +} + +ValidationResult StoreData::IsValid() const { + if (core_data.IsValid() != ValidationResult::NoErrors) { + return core_data.IsValid(); + } + if (data_crc != MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID))) { + return ValidationResult::InvalidChecksum; + } const auto device_id = MiiUtil::GetDeviceId(); - core_data.SetNickname(invalid_name); - device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID)); - data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData)); + if (device_crc != MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData))) { + return ValidationResult::InvalidChecksum; + } + return ValidationResult::NoErrors; } bool StoreData::IsSpecial() const { return GetType() == 1; } -u32 StoreData::IsValid() const { - // TODO: complete this - return 0; -} - void StoreData::SetFontRegion(FontRegion value) { core_data.SetFontRegion(value); } @@ -304,7 +317,7 @@ void StoreData::SetNoseY(u8 value) { core_data.SetNoseY(value); } -void StoreData::SetMouthType(u8 value) { +void StoreData::SetMouthType(MouthType value) { core_data.SetMouthType(value); } @@ -380,6 +393,26 @@ void StoreData::SetNickname(Nickname value) { core_data.SetNickname(value); } +void StoreData::SetInvalidName() { + const auto& invalid_name = core_data.GetInvalidNickname(); + core_data.SetNickname(invalid_name); + SetChecksum(); +} + +void StoreData::SetChecksum() { + SetDataChecksum(); + SetDeviceChecksum(); +} + +void StoreData::SetDataChecksum() { + data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID)); +} + +void StoreData::SetDeviceChecksum() { + const auto device_id = MiiUtil::GetDeviceId(); + device_crc = MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData)); +} + Common::UUID StoreData::GetCreateId() const { return create_id; } @@ -585,7 +618,7 @@ Nickname StoreData::GetNickname() const { } bool StoreData::operator==(const StoreData& data) { - bool is_identical = data.core_data.IsValid() == 0; + bool is_identical = data.core_data.IsValid() == ValidationResult::NoErrors; is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data; is_identical &= GetCreateId() == data.GetCreateId(); is_identical &= GetFontRegion() == data.GetFontRegion(); diff --git a/src/core/hle/service/mii/types/store_data.h b/src/core/hle/service/mii/types/store_data.h index 224c32cf8..ed5dfb949 100644 --- a/src/core/hle/service/mii/types/store_data.h +++ b/src/core/hle/service/mii/types/store_data.h @@ -3,6 +3,7 @@ #pragma once +#include "core/hle/result.h" #include "core/hle/service/mii/mii_types.h" #include "core/hle/service/mii/types/core_data.h" @@ -10,17 +11,16 @@ namespace Service::Mii { class StoreData { public: - // nn::mii::detail::StoreDataRaw::BuildDefault void BuildDefault(u32 mii_index); - // nn::mii::detail::StoreDataRaw::BuildDefault - void BuildBase(Gender gender); - // nn::mii::detail::StoreDataRaw::BuildRandom void BuildRandom(Age age, Gender gender, Race race); + void BuildWithCharInfo(const CharInfo& char_info); + void BuildWithCoreData(const CoreData& in_core_data); + Result Restore(); - bool IsSpecial() const; + ValidationResult IsValid() const; - u32 IsValid() const; + bool IsSpecial() const; void SetFontRegion(FontRegion value); void SetFavoriteColor(FavoriteColor value); @@ -53,7 +53,7 @@ public: void SetNoseType(NoseType value); void SetNoseScale(u8 value); void SetNoseY(u8 value); - void SetMouthType(u8 value); + void SetMouthType(MouthType value); void SetMouthColor(CommonColor value); void SetMouthScale(u8 value); void SetMouthAspect(u8 value); @@ -73,6 +73,9 @@ public: void SetMoleY(u8 value); void SetNickname(Nickname nickname); void SetInvalidName(); + void SetChecksum(); + void SetDataChecksum(); + void SetDeviceChecksum(); Common::UUID GetCreateId() const; FontRegion GetFontRegion() const; @@ -135,6 +138,8 @@ private: u16 device_crc{}; }; static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size."); +static_assert(std::is_trivially_copyable_v<StoreData>, + "StoreData type must be trivially copyable."); struct StoreDataElement { StoreData store_data{}; diff --git a/src/core/hle/service/mii/types/ver3_store_data.cpp b/src/core/hle/service/mii/types/ver3_store_data.cpp index 1c28e0b1b..a019cc9f7 100644 --- a/src/core/hle/service/mii/types/ver3_store_data.cpp +++ b/src/core/hle/service/mii/types/ver3_store_data.cpp @@ -22,12 +22,6 @@ void NfpStoreDataExtension::SetFromStoreData(const StoreData& store_data) { void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const { out_store_data.BuildBase(Gender::Male); - if (!IsValid()) { - return; - } - - // TODO: We are ignoring a bunch of data from the mii_v3 - out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value())); out_store_data.SetFavoriteColor( static_cast<FavoriteColor>(mii_information.favorite_color.Value())); @@ -36,65 +30,71 @@ void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const { out_store_data.SetNickname(mii_name); out_store_data.SetFontRegion( - static_cast<FontRegion>(static_cast<u8>(region_information.font_region))); + static_cast<FontRegion>(static_cast<u8>(region_information.font_region.Value()))); out_store_data.SetFacelineType( static_cast<FacelineType>(appearance_bits1.faceline_type.Value())); out_store_data.SetFacelineColor( - static_cast<FacelineColor>(appearance_bits1.faceline_color.Value())); + RawData::GetFacelineColorFromVer3(appearance_bits1.faceline_color.Value())); out_store_data.SetFacelineWrinkle( static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value())); out_store_data.SetFacelineMake( static_cast<FacelineMake>(appearance_bits2.faceline_make.Value())); out_store_data.SetHairType(static_cast<HairType>(hair_type)); - out_store_data.SetHairColor(static_cast<CommonColor>(appearance_bits3.hair_color.Value())); + out_store_data.SetHairColor(RawData::GetHairColorFromVer3(appearance_bits3.hair_color.Value())); out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value())); out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value())); - out_store_data.SetEyeColor(static_cast<CommonColor>(appearance_bits4.eye_color.Value())); - out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale)); - out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect)); - out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate)); - out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x)); - out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y)); + out_store_data.SetEyeColor(RawData::GetEyeColorFromVer3(appearance_bits4.eye_color.Value())); + out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale.Value())); + out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect.Value())); + out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate.Value())); + out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x.Value())); + out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y.Value())); out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value())); out_store_data.SetEyebrowColor( - static_cast<CommonColor>(appearance_bits5.eyebrow_color.Value())); - out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale)); - out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect)); - out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate)); - out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x)); - out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y)); + RawData::GetHairColorFromVer3(appearance_bits5.eyebrow_color.Value())); + out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale.Value())); + out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect.Value())); + out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate.Value())); + out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x.Value())); + out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y.Value() - 3)); out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value())); - out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale)); - out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y)); + out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale.Value())); + out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y.Value())); - out_store_data.SetMouthType(static_cast<u8>(appearance_bits7.mouth_type)); - out_store_data.SetMouthColor(static_cast<CommonColor>(appearance_bits7.mouth_color.Value())); - out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale)); - out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect)); - out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y)); + out_store_data.SetMouthType(static_cast<MouthType>(appearance_bits7.mouth_type.Value())); + out_store_data.SetMouthColor( + RawData::GetMouthColorFromVer3(appearance_bits7.mouth_color.Value())); + out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale.Value())); + out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect.Value())); + out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y.Value())); out_store_data.SetMustacheType( static_cast<MustacheType>(appearance_bits8.mustache_type.Value())); - out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale)); - out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y)); + out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale.Value())); + out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y.Value())); out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value())); - out_store_data.SetBeardColor(static_cast<CommonColor>(appearance_bits9.beard_color.Value())); + out_store_data.SetBeardColor( + RawData::GetHairColorFromVer3(appearance_bits9.beard_color.Value())); + // Glass type is compatible as it is. It doesn't need a table out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value())); - out_store_data.SetGlassColor(static_cast<CommonColor>(appearance_bits10.glass_color.Value())); - out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale)); - out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y)); + out_store_data.SetGlassColor( + RawData::GetGlassColorFromVer3(appearance_bits10.glass_color.Value())); + out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale.Value())); + out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y.Value())); out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value())); - out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale)); - out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x)); - out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y)); + out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale.Value())); + out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x.Value())); + out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y.Value())); + + out_store_data.SetChecksum(); } void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) { @@ -220,7 +220,7 @@ u32 Ver3StoreData::IsValid() const { is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max)); is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale); - is_valid = is_valid && (appearance_bits9.mustache_y <= MasMustacheY); + is_valid = is_valid && (appearance_bits9.mustache_y <= MaxMustacheY); is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max)); is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor); @@ -228,7 +228,7 @@ u32 Ver3StoreData::IsValid() const { is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType); is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2); is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale); - is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassScale); + is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassY); is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max)); is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale); diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp index 674d2e4b2..05951d8cb 100644 --- a/src/core/hle/service/nfc/common/device.cpp +++ b/src/core/hle/service/nfc/common/device.cpp @@ -439,6 +439,7 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target device_state = DeviceState::TagMounted; mount_target = mount_target_; + return ResultSuccess; } @@ -716,12 +717,13 @@ Result NfcDevice::GetRegisterInfoPrivate(NFP::RegisterInfoPrivate& register_info return ResultRegistrationIsNotInitialized; } - Service::Mii::MiiManager manager; + Mii::StoreData store_data{}; const auto& settings = tag_data.settings; + tag_data.owner_mii.BuildToStoreData(store_data); // TODO: Validate and complete this data register_info = { - .mii_store_data = {}, + .mii_store_data = store_data, .creation_date = settings.init_date.GetWriteDate(), .amiibo_name = GetAmiiboName(settings), .font_region = settings.settings.font_region, @@ -1372,7 +1374,7 @@ NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) co // Convert from utf16 to utf8 const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data()); - memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size()); + memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size() - 1); return amiibo_name; } diff --git a/src/core/tools/renderdoc.cpp b/src/core/tools/renderdoc.cpp index 44d24822a..947fa6cb3 100644 --- a/src/core/tools/renderdoc.cpp +++ b/src/core/tools/renderdoc.cpp @@ -7,7 +7,7 @@ #include "common/dynamic_library.h" #include "core/tools/renderdoc.h" -#ifdef WIN32 +#ifdef _WIN32 #include <windows.h> #else #include <dlfcn.h> diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp index 11ced6c38..56307d030 100644 --- a/src/video_core/texture_cache/format_lookup_table.cpp +++ b/src/video_core/texture_cache/format_lookup_table.cpp @@ -140,6 +140,8 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red, return PixelFormat::D32_FLOAT; case Hash(TextureFormat::Z16, UNORM): return PixelFormat::D16_UNORM; + case Hash(TextureFormat::Z16, UNORM, UINT, UINT, UINT, LINEAR): + return PixelFormat::D16_UNORM; case Hash(TextureFormat::Z24S8, UINT, UNORM, UNORM, UNORM, LINEAR): return PixelFormat::S8_UINT_D24_UNORM; case Hash(TextureFormat::Z24S8, UINT, UNORM, UINT, UINT, LINEAR): |