summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--CMakeModules/FindRenderDoc.cmake19
-rw-r--r--externals/CMakeLists.txt7
-rw-r--r--src/android/app/build.gradle.kts19
-rw-r--r--src/audio_core/CMakeLists.txt16
-rw-r--r--src/audio_core/adsp/adsp.cpp13
-rw-r--r--src/audio_core/adsp/adsp.h3
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp48
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.h45
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decode_object.cpp107
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decode_object.h38
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decoder.cpp269
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decoder.h92
-rw-r--r--src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp111
-rw-r--r--src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h39
-rw-r--r--src/audio_core/adsp/apps/opus/shared_memory.h17
-rw-r--r--src/audio_core/adsp/mailbox.h27
-rw-r--r--src/audio_core/opus/decoder.cpp179
-rw-r--r--src/audio_core/opus/decoder.h53
-rw-r--r--src/audio_core/opus/decoder_manager.cpp102
-rw-r--r--src/audio_core/opus/decoder_manager.h38
-rw-r--r--src/audio_core/opus/hardware_opus.cpp241
-rw-r--r--src/audio_core/opus/hardware_opus.h45
-rw-r--r--src/audio_core/opus/parameters.h54
-rw-r--r--src/audio_core/renderer/command/command_processing_time_estimator.cpp4
-rw-r--r--src/audio_core/renderer/system.cpp10
-rw-r--r--src/common/CMakeLists.txt5
-rw-r--r--src/common/bounded_threadsafe_queue.h4
-rw-r--r--src/core/CMakeLists.txt6
-rw-r--r--src/core/file_sys/registered_cache.cpp4
-rw-r--r--src/core/hle/result.h2
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp20
-rw-r--r--src/core/hle/service/audio/errors.h12
-rw-r--r--src/core/hle/service/audio/hwopus.cpp722
-rw-r--r--src/core/hle/service/audio/hwopus.h25
-rw-r--r--src/core/hle/service/mii/mii.cpp357
-rw-r--r--src/core/hle/service/mii/mii_database.cpp142
-rw-r--r--src/core/hle/service/mii/mii_database.h66
-rw-r--r--src/core/hle/service/mii/mii_database_manager.cpp420
-rw-r--r--src/core/hle/service/mii/mii_database_manager.h58
-rw-r--r--src/core/hle/service/mii/mii_manager.cpp410
-rw-r--r--src/core/hle/service/mii/mii_manager.h82
-rw-r--r--src/core/hle/service/mii/mii_result.h9
-rw-r--r--src/core/hle/service/mii/mii_types.h16
-rw-r--r--src/core/hle/service/mii/mii_util.h26
-rw-r--r--src/core/hle/service/mii/types/char_info.cpp6
-rw-r--r--src/core/hle/service/mii/types/char_info.h106
-rw-r--r--src/core/hle/service/mii/types/core_data.cpp213
-rw-r--r--src/core/hle/service/mii/types/core_data.h7
-rw-r--r--src/core/hle/service/mii/types/store_data.cpp83
-rw-r--r--src/core/hle/service/mii/types/store_data.h19
-rw-r--r--src/core/hle/service/mii/types/ver3_store_data.cpp78
-rw-r--r--src/core/hle/service/nfc/common/device.cpp8
-rw-r--r--src/core/tools/renderdoc.cpp2
-rw-r--r--src/video_core/texture_cache/format_lookup_table.cpp2
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(&params, 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(&params, 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(&param, 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(&params, 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(&params, 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(&params, 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(&params, 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):