summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio_core/CMakeLists.txt1
-rw-r--r--src/audio_core/audio_core.cpp20
-rw-r--r--src/audio_core/audio_core.h26
-rw-r--r--src/audio_core/audio_event.h4
-rw-r--r--src/audio_core/audio_in_manager.cpp2
-rw-r--r--src/audio_core/audio_in_manager.h5
-rw-r--r--src/audio_core/audio_manager.cpp17
-rw-r--r--src/audio_core/audio_manager.h21
-rw-r--r--src/audio_core/audio_out_manager.cpp2
-rw-r--r--src/audio_core/audio_render_manager.cpp6
-rw-r--r--src/audio_core/audio_render_manager.h12
-rw-r--r--src/audio_core/device/audio_buffer.h4
-rw-r--r--src/audio_core/device/audio_buffers.h21
-rw-r--r--src/audio_core/device/device_session.cpp64
-rw-r--r--src/audio_core/device/device_session.h34
-rw-r--r--src/audio_core/in/audio_in.cpp8
-rw-r--r--src/audio_core/in/audio_in.h8
-rw-r--r--src/audio_core/in/audio_in_system.cpp22
-rw-r--r--src/audio_core/in/audio_in_system.h12
-rw-r--r--src/audio_core/out/audio_out.cpp8
-rw-r--r--src/audio_core/out/audio_out.h8
-rw-r--r--src/audio_core/out/audio_out_system.cpp21
-rw-r--r--src/audio_core/out/audio_out_system.h10
-rw-r--r--src/audio_core/renderer/adsp/adsp.cpp2
-rw-r--r--src/audio_core/renderer/adsp/adsp.h4
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.cpp13
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.h10
-rw-r--r--src/audio_core/renderer/adsp/command_list_processor.h13
-rw-r--r--src/audio_core/renderer/audio_device.cpp34
-rw-r--r--src/audio_core/renderer/audio_device.h22
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.cpp16
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.h4
-rw-r--r--src/audio_core/renderer/behavior/info_updater.cpp2
-rw-r--r--src/audio_core/renderer/behavior/info_updater.h2
-rw-r--r--src/audio_core/renderer/command/command_buffer.h12
-rw-r--r--src/audio_core/renderer/command/command_generator.h46
-rw-r--r--src/audio_core/renderer/command/effect/compressor.cpp11
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp.cpp18
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp.h8
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp_grouped.h4
-rw-r--r--src/audio_core/renderer/command/sink/device.cpp4
-rw-r--r--src/audio_core/renderer/effect/effect_context.h14
-rw-r--r--src/audio_core/renderer/effect/effect_info_base.h4
-rw-r--r--src/audio_core/renderer/effect/i3dl2.h4
-rw-r--r--src/audio_core/renderer/effect/reverb.h4
-rw-r--r--src/audio_core/renderer/memory/address_info.h5
-rw-r--r--src/audio_core/renderer/nodes/node_states.h8
-rw-r--r--src/audio_core/renderer/performance/performance_manager.h8
-rw-r--r--src/audio_core/renderer/system_manager.cpp46
-rw-r--r--src/audio_core/renderer/system_manager.h9
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_manager.h2
-rw-r--r--src/audio_core/renderer/voice/voice_info.h26
-rw-r--r--src/audio_core/sink/cubeb_sink.cpp383
-rw-r--r--src/audio_core/sink/cubeb_sink.h17
-rw-r--r--src/audio_core/sink/null_sink.h49
-rw-r--r--src/audio_core/sink/sdl2_sink.cpp377
-rw-r--r--src/audio_core/sink/sdl2_sink.h17
-rw-r--r--src/audio_core/sink/sink.h15
-rw-r--r--src/audio_core/sink/sink_details.cpp6
-rw-r--r--src/audio_core/sink/sink_stream.cpp284
-rw-r--r--src/audio_core/sink/sink_stream.h175
-rw-r--r--src/common/CMakeLists.txt32
-rw-r--r--src/common/input.h5
-rw-r--r--src/common/settings.h2
-rw-r--r--src/common/thread.h4
-rw-r--r--src/core/CMakeLists.txt11
-rw-r--r--src/core/core.cpp4
-rw-r--r--src/core/core_timing.cpp67
-rw-r--r--src/core/core_timing.h6
-rw-r--r--src/core/hid/emulated_controller.cpp17
-rw-r--r--src/core/hid/input_converter.cpp3
-rw-r--r--src/core/hle/result.h2
-rw-r--r--src/core/hle/service/am/applets/applet_mii_edit_types.h2
-rw-r--r--src/core/hle/service/audio/audout_u.cpp3
-rw-r--r--src/core/hle/service/audio/audren_u.cpp4
-rw-r--r--src/core/hle/service/audio/hwopus.cpp28
-rw-r--r--src/core/hle/service/audio/hwopus.h11
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp6
-rw-r--r--src/core/hle/service/hid/controllers/palma.cpp229
-rw-r--r--src/core/hle/service/hid/controllers/palma.h163
-rw-r--r--src/core/hle/service/hid/errors.h2
-rw-r--r--src/core/hle/service/hid/hid.cpp444
-rw-r--r--src/core/hle/service/hid/hid.h29
-rw-r--r--src/core/hle/service/hid/irs.cpp3
-rw-r--r--src/core/hle/service/mii/mii.cpp32
-rw-r--r--src/core/hle/service/mii/mii_manager.cpp89
-rw-r--r--src/core/hle/service/mii/mii_manager.h9
-rw-r--r--src/core/hle/service/mii/types.h138
-rw-r--r--src/core/hle/service/nfp/amiibo_crypto.cpp383
-rw-r--r--src/core/hle/service/nfp/amiibo_crypto.h98
-rw-r--r--src/core/hle/service/nfp/amiibo_types.h197
-rw-r--r--src/core/hle/service/nfp/nfp.cpp555
-rw-r--r--src/core/hle/service/nfp/nfp.h145
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp7
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp42
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.h5
-rw-r--r--src/core/hle/service/sockets/bsd.cpp2
-rw-r--r--src/core/internal_network/network.cpp12
-rw-r--r--src/core/internal_network/sockets.h11
-rw-r--r--src/core/loader/loader.cpp4
-rw-r--r--src/input_common/input_poller.cpp1
-rw-r--r--src/network/network.cpp2
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm.cpp2
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl.cpp2
-rw-r--r--src/video_core/host_shaders/astc_decoder.comp2
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp2
-rw-r--r--src/video_core/textures/astc.cpp58
-rw-r--r--src/yuzu/CMakeLists.txt10
-rw-r--r--src/yuzu/applets/qt_controller.cpp4
-rw-r--r--src/yuzu/configuration/config.cpp8
-rw-r--r--src/yuzu/configuration/config.h4
-rw-r--r--src/yuzu/configuration/configure_debug.cpp23
-rw-r--r--src/yuzu/configuration/configure_debug.h2
-rw-r--r--src/yuzu/configuration/configure_debug.ui132
-rw-r--r--src/yuzu/configuration/configure_input.cpp9
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp23
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp3
-rw-r--r--src/yuzu/configuration/configure_tas.ui3
-rw-r--r--src/yuzu/configuration/configure_web.cpp21
-rw-r--r--src/yuzu/configuration/input_profiles.cpp11
-rw-r--r--src/yuzu/configuration/input_profiles.h4
-rw-r--r--src/yuzu/main.cpp62
-rw-r--r--src/yuzu/main.h2
-rw-r--r--src/yuzu/mini_dump.cpp202
-rw-r--r--src/yuzu/mini_dump.h19
-rw-r--r--src/yuzu/startup_checks.cpp116
-rw-r--r--src/yuzu/startup_checks.h7
127 files changed, 3744 insertions, 1810 deletions
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 5fe1d5fa5..144f1bab2 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -194,6 +194,7 @@ add_library(audio_core STATIC
194 sink/sink.h 194 sink/sink.h
195 sink/sink_details.cpp 195 sink/sink_details.cpp
196 sink/sink_details.h 196 sink/sink_details.h
197 sink/sink_stream.cpp
197 sink/sink_stream.h 198 sink/sink_stream.h
198) 199)
199 200
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp
index 78e615a10..07a679c32 100644
--- a/src/audio_core/audio_core.cpp
+++ b/src/audio_core/audio_core.cpp
@@ -8,7 +8,7 @@
8 8
9namespace AudioCore { 9namespace AudioCore {
10 10
11AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>(system)} { 11AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>()} {
12 CreateSinks(); 12 CreateSinks();
13 // Must be created after the sinks 13 // Must be created after the sinks
14 adsp = std::make_unique<AudioRenderer::ADSP::ADSP>(system, *output_sink); 14 adsp = std::make_unique<AudioRenderer::ADSP::ADSP>(system, *output_sink);
@@ -47,22 +47,12 @@ AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() {
47 return *adsp; 47 return *adsp;
48} 48}
49 49
50void AudioCore::PauseSinks(const bool pausing) const { 50void AudioCore::SetNVDECActive(bool active) {
51 if (pausing) { 51 nvdec_active = active;
52 output_sink->PauseStreams();
53 input_sink->PauseStreams();
54 } else {
55 output_sink->UnpauseStreams();
56 input_sink->UnpauseStreams();
57 }
58} 52}
59 53
60u32 AudioCore::GetStreamQueue() const { 54bool AudioCore::IsNVDECActive() const {
61 return estimated_queue.load(); 55 return nvdec_active;
62}
63
64void AudioCore::SetStreamQueue(u32 size) {
65 estimated_queue.store(size);
66} 56}
67 57
68} // namespace AudioCore 58} // namespace AudioCore
diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h
index 0f7d61ee4..e33e00a3e 100644
--- a/src/audio_core/audio_core.h
+++ b/src/audio_core/audio_core.h
@@ -17,7 +17,7 @@ namespace AudioCore {
17 17
18class AudioManager; 18class AudioManager;
19/** 19/**
20 * Main audio class, sotred inside the core, and holding the audio manager, all sinks, and the ADSP. 20 * Main audio class, stored inside the core, and holding the audio manager, all sinks, and the ADSP.
21 */ 21 */
22class AudioCore { 22class AudioCore {
23public: 23public:
@@ -58,26 +58,16 @@ public:
58 AudioRenderer::ADSP::ADSP& GetADSP(); 58 AudioRenderer::ADSP::ADSP& GetADSP();
59 59
60 /** 60 /**
61 * Pause the sink. Called from the core. 61 * Toggle NVDEC state, used to avoid stall in playback.
62 * 62 *
63 * @param pausing - Is this pause due to an actual pause, or shutdown? 63 * @param active - Set true if nvdec is active, otherwise false.
64 * Unfortunately, shutdown also pauses streams, which can cause issues.
65 */ 64 */
66 void PauseSinks(bool pausing) const; 65 void SetNVDECActive(bool active);
67 66
68 /** 67 /**
69 * Get the size of the current stream queue. 68 * Get NVDEC state.
70 *
71 * @return Current stream queue size.
72 */
73 u32 GetStreamQueue() const;
74
75 /**
76 * Get the size of the current stream queue.
77 *
78 * @param size - New stream size.
79 */ 69 */
80 void SetStreamQueue(u32 size); 70 bool IsNVDECActive() const;
81 71
82private: 72private:
83 /** 73 /**
@@ -93,8 +83,8 @@ private:
93 std::unique_ptr<Sink::Sink> input_sink; 83 std::unique_ptr<Sink::Sink> input_sink;
94 /// The ADSP in the sysmodule 84 /// The ADSP in the sysmodule
95 std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp; 85 std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp;
96 /// Current size of the stream queue 86 /// Is NVDec currently active?
97 std::atomic<u32> estimated_queue{0}; 87 bool nvdec_active{false};
98}; 88};
99 89
100} // namespace AudioCore 90} // namespace AudioCore
diff --git a/src/audio_core/audio_event.h b/src/audio_core/audio_event.h
index 82dd32dca..012d2ed70 100644
--- a/src/audio_core/audio_event.h
+++ b/src/audio_core/audio_event.h
@@ -14,7 +14,7 @@ namespace AudioCore {
14 * Responsible for the input/output events, set by the stream backend when buffers are consumed, and 14 * Responsible for the input/output events, set by the stream backend when buffers are consumed, and
15 * waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer 15 * waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer
16 * recycling going. 16 * recycling going.
17 * In a real Switch this is not a seprate class, and exists entirely within the audio manager. 17 * In a real Switch this is not a separate class, and exists entirely within the audio manager.
18 * On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can 18 * On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can
19 * wait on multiple events at once, and the events are not needed by the backend. 19 * wait on multiple events at once, and the events are not needed by the backend.
20 */ 20 */
@@ -81,7 +81,7 @@ public:
81 void ClearEvents(); 81 void ClearEvents();
82 82
83private: 83private:
84 /// Lock, used bythe audio manager 84 /// Lock, used by the audio manager
85 std::mutex event_lock; 85 std::mutex event_lock;
86 /// Array of events, one per system type (see Type), last event is used to terminate 86 /// Array of events, one per system type (see Type), last event is used to terminate
87 std::array<std::atomic<bool>, 4> events_signalled; 87 std::array<std::atomic<bool>, 4> events_signalled;
diff --git a/src/audio_core/audio_in_manager.cpp b/src/audio_core/audio_in_manager.cpp
index 4aadb7fd6..f39fb4002 100644
--- a/src/audio_core/audio_in_manager.cpp
+++ b/src/audio_core/audio_in_manager.cpp
@@ -82,7 +82,7 @@ u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceN
82 82
83 auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)}; 83 auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)};
84 if (input_devices.size() > 1) { 84 if (input_devices.size() > 1) {
85 names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac")); 85 names.emplace_back("Uac");
86 return 1; 86 return 1;
87 } 87 }
88 return 0; 88 return 0;
diff --git a/src/audio_core/audio_in_manager.h b/src/audio_core/audio_in_manager.h
index 75b73a0b6..8a519df99 100644
--- a/src/audio_core/audio_in_manager.h
+++ b/src/audio_core/audio_in_manager.h
@@ -59,9 +59,10 @@ public:
59 /** 59 /**
60 * Get a list of audio in device names. 60 * Get a list of audio in device names.
61 * 61 *
62 * @oaram names - Output container to write names to. 62 * @param names - Output container to write names to.
63 * @param max_count - Maximum numebr of deivce names to write. Unused 63 * @param max_count - Maximum number of device names to write. Unused
64 * @param filter - Should the list be filtered? Unused. 64 * @param filter - Should the list be filtered? Unused.
65 *
65 * @return Number of names written. 66 * @return Number of names written.
66 */ 67 */
67 u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names, 68 u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names,
diff --git a/src/audio_core/audio_manager.cpp b/src/audio_core/audio_manager.cpp
index 2f1bba9c3..2acde668e 100644
--- a/src/audio_core/audio_manager.cpp
+++ b/src/audio_core/audio_manager.cpp
@@ -1,14 +1,13 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/audio_in_manager.h"
5#include "audio_core/audio_manager.h" 4#include "audio_core/audio_manager.h"
6#include "audio_core/audio_out_manager.h"
7#include "core/core.h" 5#include "core/core.h"
6#include "core/hle/service/audio/errors.h"
8 7
9namespace AudioCore { 8namespace AudioCore {
10 9
11AudioManager::AudioManager(Core::System& system_) : system{system_} { 10AudioManager::AudioManager() {
12 thread = std::jthread([this]() { ThreadFunc(); }); 11 thread = std::jthread([this]() { ThreadFunc(); });
13} 12}
14 13
@@ -27,7 +26,7 @@ Result AudioManager::SetOutManager(BufferEventFunc buffer_func) {
27 26
28 const auto index{events.GetManagerIndex(Event::Type::AudioOutManager)}; 27 const auto index{events.GetManagerIndex(Event::Type::AudioOutManager)};
29 if (buffer_events[index] == nullptr) { 28 if (buffer_events[index] == nullptr) {
30 buffer_events[index] = buffer_func; 29 buffer_events[index] = std::move(buffer_func);
31 needs_update = true; 30 needs_update = true;
32 events.SetAudioEvent(Event::Type::AudioOutManager, true); 31 events.SetAudioEvent(Event::Type::AudioOutManager, true);
33 } 32 }
@@ -43,7 +42,7 @@ Result AudioManager::SetInManager(BufferEventFunc buffer_func) {
43 42
44 const auto index{events.GetManagerIndex(Event::Type::AudioInManager)}; 43 const auto index{events.GetManagerIndex(Event::Type::AudioInManager)};
45 if (buffer_events[index] == nullptr) { 44 if (buffer_events[index] == nullptr) {
46 buffer_events[index] = buffer_func; 45 buffer_events[index] = std::move(buffer_func);
47 needs_update = true; 46 needs_update = true;
48 events.SetAudioEvent(Event::Type::AudioInManager, true); 47 events.SetAudioEvent(Event::Type::AudioInManager, true);
49 } 48 }
@@ -60,19 +59,21 @@ void AudioManager::ThreadFunc() {
60 running = true; 59 running = true;
61 60
62 while (running) { 61 while (running) {
63 auto timed_out{events.Wait(l, std::chrono::seconds(2))}; 62 const auto timed_out{events.Wait(l, std::chrono::seconds(2))};
64 63
65 if (events.CheckAudioEventSet(Event::Type::Max)) { 64 if (events.CheckAudioEventSet(Event::Type::Max)) {
66 break; 65 break;
67 } 66 }
68 67
69 for (size_t i = 0; i < buffer_events.size(); i++) { 68 for (size_t i = 0; i < buffer_events.size(); i++) {
70 if (events.CheckAudioEventSet(Event::Type(i)) || timed_out) { 69 const auto event_type = static_cast<Event::Type>(i);
70
71 if (events.CheckAudioEventSet(event_type) || timed_out) {
71 if (buffer_events[i]) { 72 if (buffer_events[i]) {
72 buffer_events[i](); 73 buffer_events[i]();
73 } 74 }
74 } 75 }
75 events.SetAudioEvent(Event::Type(i), false); 76 events.SetAudioEvent(event_type, false);
76 } 77 }
77 } 78 }
78} 79}
diff --git a/src/audio_core/audio_manager.h b/src/audio_core/audio_manager.h
index 70316e9cb..abf077de4 100644
--- a/src/audio_core/audio_manager.h
+++ b/src/audio_core/audio_manager.h
@@ -10,22 +10,11 @@
10#include <thread> 10#include <thread>
11 11
12#include "audio_core/audio_event.h" 12#include "audio_core/audio_event.h"
13#include "core/hle/service/audio/errors.h"
14 13
15namespace Core { 14union Result;
16class System;
17}
18 15
19namespace AudioCore { 16namespace AudioCore {
20 17
21namespace AudioOut {
22class Manager;
23}
24
25namespace AudioIn {
26class Manager;
27}
28
29/** 18/**
30 * The AudioManager's main purpose is to wait for buffer events for the audio in and out managers, 19 * The AudioManager's main purpose is to wait for buffer events for the audio in and out managers,
31 * and call an associated callback to release buffers. 20 * and call an associated callback to release buffers.
@@ -43,7 +32,7 @@ class AudioManager {
43 using BufferEventFunc = std::function<void()>; 32 using BufferEventFunc = std::function<void()>;
44 33
45public: 34public:
46 explicit AudioManager(Core::System& system); 35 explicit AudioManager();
47 36
48 /** 37 /**
49 * Shutdown the audio manager. 38 * Shutdown the audio manager.
@@ -76,14 +65,10 @@ public:
76 65
77private: 66private:
78 /** 67 /**
79 * Main thread, waiting on a manager signal and calling the registered fucntion. 68 * Main thread, waiting on a manager signal and calling the registered function.
80 */ 69 */
81 void ThreadFunc(); 70 void ThreadFunc();
82 71
83 /// Core system
84 Core::System& system;
85 /// Have sessions started palying?
86 bool sessions_started{};
87 /// Is the main thread running? 72 /// Is the main thread running?
88 std::atomic<bool> running{}; 73 std::atomic<bool> running{};
89 /// Unused 74 /// Unused
diff --git a/src/audio_core/audio_out_manager.cpp b/src/audio_core/audio_out_manager.cpp
index 71d67de64..1766efde1 100644
--- a/src/audio_core/audio_out_manager.cpp
+++ b/src/audio_core/audio_out_manager.cpp
@@ -74,7 +74,7 @@ void Manager::BufferReleaseAndRegister() {
74 74
75u32 Manager::GetAudioOutDeviceNames( 75u32 Manager::GetAudioOutDeviceNames(
76 std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const { 76 std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const {
77 names.push_back({"DeviceOut"}); 77 names.emplace_back("DeviceOut");
78 return 1; 78 return 1;
79} 79}
80 80
diff --git a/src/audio_core/audio_render_manager.cpp b/src/audio_core/audio_render_manager.cpp
index 7a846835b..7aba2b423 100644
--- a/src/audio_core/audio_render_manager.cpp
+++ b/src/audio_core/audio_render_manager.cpp
@@ -25,8 +25,8 @@ SystemManager& Manager::GetSystemManager() {
25 return *system_manager; 25 return *system_manager;
26} 26}
27 27
28auto Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) 28Result Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params,
29 -> Result { 29 u64& out_count) const {
30 if (!CheckValidRevision(params.revision)) { 30 if (!CheckValidRevision(params.revision)) {
31 return Service::Audio::ERR_INVALID_REVISION; 31 return Service::Audio::ERR_INVALID_REVISION;
32 } 32 }
@@ -54,7 +54,7 @@ void Manager::ReleaseSessionId(const s32 session_id) {
54 session_ids[--session_count] = session_id; 54 session_ids[--session_count] = session_id;
55} 55}
56 56
57u32 Manager::GetSessionCount() { 57u32 Manager::GetSessionCount() const {
58 std::scoped_lock l{session_lock}; 58 std::scoped_lock l{session_lock};
59 return session_count; 59 return session_count;
60} 60}
diff --git a/src/audio_core/audio_render_manager.h b/src/audio_core/audio_render_manager.h
index 6a508ec56..bf4837190 100644
--- a/src/audio_core/audio_render_manager.h
+++ b/src/audio_core/audio_render_manager.h
@@ -46,7 +46,7 @@ public:
46 * @param out_count - Output size of the required workbuffer. 46 * @param out_count - Output size of the required workbuffer.
47 * @return Result code. 47 * @return Result code.
48 */ 48 */
49 Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count); 49 Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) const;
50 50
51 /** 51 /**
52 * Get a new session id. 52 * Get a new session id.
@@ -60,14 +60,14 @@ public:
60 * 60 *
61 * @return The number of active sessions. 61 * @return The number of active sessions.
62 */ 62 */
63 u32 GetSessionCount(); 63 u32 GetSessionCount() const;
64 64
65 /** 65 /**
66 * Add a renderer system to the manager. 66 * Add a renderer system to the manager.
67 * The system will be reguarly called to generate commands for the AudioRenderer. 67 * The system will be regularly called to generate commands for the AudioRenderer.
68 * 68 *
69 * @param system - The system to add. 69 * @param system - The system to add.
70 * @return True if the system was sucessfully added, otherwise false. 70 * @return True if the system was successfully added, otherwise false.
71 */ 71 */
72 bool AddSystem(System& system); 72 bool AddSystem(System& system);
73 73
@@ -75,7 +75,7 @@ public:
75 * Remove a renderer system from the manager. 75 * Remove a renderer system from the manager.
76 * 76 *
77 * @param system - The system to remove. 77 * @param system - The system to remove.
78 * @return True if the system was sucessfully removed, otherwise false. 78 * @return True if the system was successfully removed, otherwise false.
79 */ 79 */
80 bool RemoveSystem(System& system); 80 bool RemoveSystem(System& system);
81 81
@@ -94,7 +94,7 @@ private:
94 /// Number of active renderers 94 /// Number of active renderers
95 u32 session_count{}; 95 u32 session_count{};
96 /// Lock for interacting with the sessions 96 /// Lock for interacting with the sessions
97 std::mutex session_lock{}; 97 mutable std::mutex session_lock{};
98 /// Regularly generates commands from the registered systems for the AudioRenderer 98 /// Regularly generates commands from the registered systems for the AudioRenderer
99 std::unique_ptr<SystemManager> system_manager{}; 99 std::unique_ptr<SystemManager> system_manager{};
100}; 100};
diff --git a/src/audio_core/device/audio_buffer.h b/src/audio_core/device/audio_buffer.h
index cae7fa970..7128ef72a 100644
--- a/src/audio_core/device/audio_buffer.h
+++ b/src/audio_core/device/audio_buffer.h
@@ -8,6 +8,10 @@
8namespace AudioCore { 8namespace AudioCore {
9 9
10struct AudioBuffer { 10struct AudioBuffer {
11 /// Timestamp this buffer started playing.
12 u64 start_timestamp;
13 /// Timestamp this buffer should finish playing.
14 u64 end_timestamp;
11 /// Timestamp this buffer completed playing. 15 /// Timestamp this buffer completed playing.
12 s64 played_timestamp; 16 s64 played_timestamp;
13 /// Game memory address for these samples. 17 /// Game memory address for these samples.
diff --git a/src/audio_core/device/audio_buffers.h b/src/audio_core/device/audio_buffers.h
index 5d1979ea0..3dae1a3b7 100644
--- a/src/audio_core/device/audio_buffers.h
+++ b/src/audio_core/device/audio_buffers.h
@@ -36,7 +36,7 @@ public:
36 * 36 *
37 * @param buffer - The new buffer. 37 * @param buffer - The new buffer.
38 */ 38 */
39 void AppendBuffer(AudioBuffer& buffer) { 39 void AppendBuffer(const AudioBuffer& buffer) {
40 std::scoped_lock l{lock}; 40 std::scoped_lock l{lock};
41 buffers[appended_index] = buffer; 41 buffers[appended_index] = buffer;
42 appended_count++; 42 appended_count++;
@@ -58,6 +58,7 @@ public:
58 if (index < 0) { 58 if (index < 0) {
59 index += N; 59 index += N;
60 } 60 }
61
61 out_buffers.push_back(buffers[index]); 62 out_buffers.push_back(buffers[index]);
62 registered_count++; 63 registered_count++;
63 registered_index = (registered_index + 1) % append_limit; 64 registered_index = (registered_index + 1) % append_limit;
@@ -87,10 +88,12 @@ public:
87 /** 88 /**
88 * Release all registered buffers. 89 * Release all registered buffers.
89 * 90 *
90 * @param timestamp - The released timestamp for this buffer. 91 * @param core_timing - The CoreTiming instance
92 * @param session - The device session
93 *
91 * @return Is the buffer was released. 94 * @return Is the buffer was released.
92 */ 95 */
93 bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) { 96 bool ReleaseBuffers(const Core::Timing::CoreTiming& core_timing, const DeviceSession& session) {
94 std::scoped_lock l{lock}; 97 std::scoped_lock l{lock};
95 bool buffer_released{false}; 98 bool buffer_released{false};
96 while (registered_count > 0) { 99 while (registered_count > 0) {
@@ -100,7 +103,7 @@ public:
100 } 103 }
101 104
102 // Check with the backend if this buffer can be released yet. 105 // Check with the backend if this buffer can be released yet.
103 if (!session.IsBufferConsumed(buffers[index].tag)) { 106 if (!session.IsBufferConsumed(buffers[index])) {
104 break; 107 break;
105 } 108 }
106 109
@@ -280,6 +283,16 @@ public:
280 return true; 283 return true;
281 } 284 }
282 285
286 u64 GetNextTimestamp() const {
287 // Iterate backwards through the buffer queue, and take the most recent buffer's end
288 std::scoped_lock l{lock};
289 auto index{appended_index - 1};
290 if (index < 0) {
291 index += append_limit;
292 }
293 return buffers[index].end_timestamp;
294 }
295
283private: 296private:
284 /// Buffer lock 297 /// Buffer lock
285 mutable std::recursive_mutex lock{}; 298 mutable std::recursive_mutex lock{};
diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp
index 095fc96ce..995060414 100644
--- a/src/audio_core/device/device_session.cpp
+++ b/src/audio_core/device/device_session.cpp
@@ -7,11 +7,20 @@
7#include "audio_core/device/device_session.h" 7#include "audio_core/device/device_session.h"
8#include "audio_core/sink/sink_stream.h" 8#include "audio_core/sink/sink_stream.h"
9#include "core/core.h" 9#include "core/core.h"
10#include "core/core_timing.h"
10#include "core/memory.h" 11#include "core/memory.h"
11 12
12namespace AudioCore { 13namespace AudioCore {
13 14
14DeviceSession::DeviceSession(Core::System& system_) : system{system_} {} 15using namespace std::literals;
16constexpr auto INCREMENT_TIME{5ms};
17
18DeviceSession::DeviceSession(Core::System& system_)
19 : system{system_}, thread_event{Core::Timing::CreateEvent(
20 "AudioOutSampleTick",
21 [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
22 return ThreadFunc();
23 })} {}
15 24
16DeviceSession::~DeviceSession() { 25DeviceSession::~DeviceSession() {
17 Finalize(); 26 Finalize();
@@ -50,25 +59,26 @@ void DeviceSession::Finalize() {
50} 59}
51 60
52void DeviceSession::Start() { 61void DeviceSession::Start() {
53 stream->SetPlayedSampleCount(played_sample_count); 62 if (stream) {
54 stream->Start(); 63 stream->Start();
64 system.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds::zero(), INCREMENT_TIME,
65 thread_event);
66 }
55} 67}
56 68
57void DeviceSession::Stop() { 69void DeviceSession::Stop() {
58 if (stream) { 70 if (stream) {
59 played_sample_count = stream->GetPlayedSampleCount();
60 stream->Stop(); 71 stream->Stop();
72 system.CoreTiming().UnscheduleEvent(thread_event, {});
61 } 73 }
62} 74}
63 75
64void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { 76void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) const {
65 auto& memory{system.Memory()}; 77 for (const auto& buffer : buffers) {
66
67 for (size_t i = 0; i < buffers.size(); i++) {
68 Sink::SinkBuffer new_buffer{ 78 Sink::SinkBuffer new_buffer{
69 .frames = buffers[i].size / (channel_count * sizeof(s16)), 79 .frames = buffer.size / (channel_count * sizeof(s16)),
70 .frames_played = 0, 80 .frames_played = 0,
71 .tag = buffers[i].tag, 81 .tag = buffer.tag,
72 .consumed = false, 82 .consumed = false,
73 }; 83 };
74 84
@@ -76,26 +86,22 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
76 std::vector<s16> samples{}; 86 std::vector<s16> samples{};
77 stream->AppendBuffer(new_buffer, samples); 87 stream->AppendBuffer(new_buffer, samples);
78 } else { 88 } else {
79 std::vector<s16> samples(buffers[i].size / sizeof(s16)); 89 std::vector<s16> samples(buffer.size / sizeof(s16));
80 memory.ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size); 90 system.Memory().ReadBlockUnsafe(buffer.samples, samples.data(), buffer.size);
81 stream->AppendBuffer(new_buffer, samples); 91 stream->AppendBuffer(new_buffer, samples);
82 } 92 }
83 } 93 }
84} 94}
85 95
86void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const { 96void DeviceSession::ReleaseBuffer(const AudioBuffer& buffer) const {
87 if (type == Sink::StreamType::In) { 97 if (type == Sink::StreamType::In) {
88 auto& memory{system.Memory()};
89 auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))}; 98 auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))};
90 memory.WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size); 99 system.Memory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
91 } 100 }
92} 101}
93 102
94bool DeviceSession::IsBufferConsumed(u64 tag) const { 103bool DeviceSession::IsBufferConsumed(const AudioBuffer& buffer) const {
95 if (stream) { 104 return played_sample_count >= buffer.end_timestamp;
96 return stream->IsBufferConsumed(tag);
97 }
98 return true;
99} 105}
100 106
101void DeviceSession::SetVolume(f32 volume) const { 107void DeviceSession::SetVolume(f32 volume) const {
@@ -105,10 +111,22 @@ void DeviceSession::SetVolume(f32 volume) const {
105} 111}
106 112
107u64 DeviceSession::GetPlayedSampleCount() const { 113u64 DeviceSession::GetPlayedSampleCount() const {
108 if (stream) { 114 return played_sample_count;
109 return stream->GetPlayedSampleCount(); 115}
116
117std::optional<std::chrono::nanoseconds> DeviceSession::ThreadFunc() {
118 // Add 5ms of samples at a 48K sample rate.
119 played_sample_count += 48'000 * INCREMENT_TIME / 1s;
120 if (type == Sink::StreamType::Out) {
121 system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioOutManager, true);
122 } else {
123 system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioInManager, true);
110 } 124 }
111 return 0; 125 return std::nullopt;
126}
127
128void DeviceSession::SetRingSize(u32 ring_size) {
129 stream->SetRingSize(ring_size);
112} 130}
113 131
114} // namespace AudioCore 132} // namespace AudioCore
diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h
index 4a031b765..74f4dc085 100644
--- a/src/audio_core/device/device_session.h
+++ b/src/audio_core/device/device_session.h
@@ -3,6 +3,9 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <chrono>
7#include <memory>
8#include <optional>
6#include <span> 9#include <span>
7 10
8#include "audio_core/common/common.h" 11#include "audio_core/common/common.h"
@@ -11,9 +14,13 @@
11 14
12namespace Core { 15namespace Core {
13class System; 16class System;
14} 17namespace Timing {
18struct EventType;
19} // namespace Timing
20} // namespace Core
15 21
16namespace AudioCore { 22namespace AudioCore {
23
17namespace Sink { 24namespace Sink {
18class SinkStream; 25class SinkStream;
19struct SinkBuffer; 26struct SinkBuffer;
@@ -55,22 +62,23 @@ public:
55 * 62 *
56 * @param buffers - The buffers to play. 63 * @param buffers - The buffers to play.
57 */ 64 */
58 void AppendBuffers(std::span<AudioBuffer> buffers) const; 65 void AppendBuffers(std::span<const AudioBuffer> buffers) const;
59 66
60 /** 67 /**
61 * (Audio In only) Pop samples from the backend, and write them back to this buffer's address. 68 * (Audio In only) Pop samples from the backend, and write them back to this buffer's address.
62 * 69 *
63 * @param buffer - The buffer to write to. 70 * @param buffer - The buffer to write to.
64 */ 71 */
65 void ReleaseBuffer(AudioBuffer& buffer) const; 72 void ReleaseBuffer(const AudioBuffer& buffer) const;
66 73
67 /** 74 /**
68 * Check if the buffer for the given tag has been consumed by the backend. 75 * Check if the buffer for the given tag has been consumed by the backend.
69 * 76 *
70 * @param tag - Unqiue tag of the buffer to check. 77 * @param buffer - the buffer to check.
78 *
71 * @return true if the buffer has been consumed, otherwise false. 79 * @return true if the buffer has been consumed, otherwise false.
72 */ 80 */
73 bool IsBufferConsumed(u64 tag) const; 81 bool IsBufferConsumed(const AudioBuffer& buffer) const;
74 82
75 /** 83 /**
76 * Start this device session, starting the backend stream. 84 * Start this device session, starting the backend stream.
@@ -96,6 +104,16 @@ public:
96 */ 104 */
97 u64 GetPlayedSampleCount() const; 105 u64 GetPlayedSampleCount() const;
98 106
107 /*
108 * CoreTiming callback to increment played_sample_count over time.
109 */
110 std::optional<std::chrono::nanoseconds> ThreadFunc();
111
112 /*
113 * Set the size of the ring buffer.
114 */
115 void SetRingSize(u32 ring_size);
116
99private: 117private:
100 /// System 118 /// System
101 Core::System& system; 119 Core::System& system;
@@ -118,9 +136,13 @@ private:
118 /// Applet resource user id of this device session 136 /// Applet resource user id of this device session
119 u64 applet_resource_user_id{}; 137 u64 applet_resource_user_id{};
120 /// Total number of samples played by this device session 138 /// Total number of samples played by this device session
121 u64 played_sample_count{}; 139 std::atomic<u64> played_sample_count{};
140 /// Event increasing the played sample count every 5ms
141 std::shared_ptr<Core::Timing::EventType> thread_event;
122 /// Is this session initialised? 142 /// Is this session initialised?
123 bool initialized{}; 143 bool initialized{};
144 /// Buffer queue
145 std::vector<AudioBuffer> buffer_queue{};
124}; 146};
125 147
126} // namespace AudioCore 148} // namespace AudioCore
diff --git a/src/audio_core/in/audio_in.cpp b/src/audio_core/in/audio_in.cpp
index c946895d6..91ccd5ad7 100644
--- a/src/audio_core/in/audio_in.cpp
+++ b/src/audio_core/in/audio_in.cpp
@@ -72,7 +72,7 @@ Kernel::KReadableEvent& In::GetBufferEvent() {
72 return event->GetReadableEvent(); 72 return event->GetReadableEvent();
73} 73}
74 74
75f32 In::GetVolume() { 75f32 In::GetVolume() const {
76 std::scoped_lock l{parent_mutex}; 76 std::scoped_lock l{parent_mutex};
77 return system.GetVolume(); 77 return system.GetVolume();
78} 78}
@@ -82,17 +82,17 @@ void In::SetVolume(f32 volume) {
82 system.SetVolume(volume); 82 system.SetVolume(volume);
83} 83}
84 84
85bool In::ContainsAudioBuffer(u64 tag) { 85bool In::ContainsAudioBuffer(u64 tag) const {
86 std::scoped_lock l{parent_mutex}; 86 std::scoped_lock l{parent_mutex};
87 return system.ContainsAudioBuffer(tag); 87 return system.ContainsAudioBuffer(tag);
88} 88}
89 89
90u32 In::GetBufferCount() { 90u32 In::GetBufferCount() const {
91 std::scoped_lock l{parent_mutex}; 91 std::scoped_lock l{parent_mutex};
92 return system.GetBufferCount(); 92 return system.GetBufferCount();
93} 93}
94 94
95u64 In::GetPlayedSampleCount() { 95u64 In::GetPlayedSampleCount() const {
96 std::scoped_lock l{parent_mutex}; 96 std::scoped_lock l{parent_mutex};
97 return system.GetPlayedSampleCount(); 97 return system.GetPlayedSampleCount();
98} 98}
diff --git a/src/audio_core/in/audio_in.h b/src/audio_core/in/audio_in.h
index 6253891d5..092ab7236 100644
--- a/src/audio_core/in/audio_in.h
+++ b/src/audio_core/in/audio_in.h
@@ -102,7 +102,7 @@ public:
102 * 102 *
103 * @return The current volume. 103 * @return The current volume.
104 */ 104 */
105 f32 GetVolume(); 105 f32 GetVolume() const;
106 106
107 /** 107 /**
108 * Set the system volume. 108 * Set the system volume.
@@ -117,21 +117,21 @@ public:
117 * @param tag - The tag to search for. 117 * @param tag - The tag to search for.
118 * @return True if the buffer is in the system, otherwise false. 118 * @return True if the buffer is in the system, otherwise false.
119 */ 119 */
120 bool ContainsAudioBuffer(u64 tag); 120 bool ContainsAudioBuffer(u64 tag) const;
121 121
122 /** 122 /**
123 * Get the maximum number of buffers. 123 * Get the maximum number of buffers.
124 * 124 *
125 * @return The maximum number of buffers. 125 * @return The maximum number of buffers.
126 */ 126 */
127 u32 GetBufferCount(); 127 u32 GetBufferCount() const;
128 128
129 /** 129 /**
130 * Get the total played sample count for this audio in. 130 * Get the total played sample count for this audio in.
131 * 131 *
132 * @return The played sample count. 132 * @return The played sample count.
133 */ 133 */
134 u64 GetPlayedSampleCount(); 134 u64 GetPlayedSampleCount() const;
135 135
136private: 136private:
137 /// The AudioIn::Manager this audio in is registered with 137 /// The AudioIn::Manager this audio in is registered with
diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp
index ec5d37ed4..e7f918a47 100644
--- a/src/audio_core/in/audio_in_system.cpp
+++ b/src/audio_core/in/audio_in_system.cpp
@@ -34,16 +34,16 @@ size_t System::GetSessionId() const {
34 return session_id; 34 return session_id;
35} 35}
36 36
37std::string_view System::GetDefaultDeviceName() { 37std::string_view System::GetDefaultDeviceName() const {
38 return "BuiltInHeadset"; 38 return "BuiltInHeadset";
39} 39}
40 40
41std::string_view System::GetDefaultUacDeviceName() { 41std::string_view System::GetDefaultUacDeviceName() const {
42 return "Uac"; 42 return "Uac";
43} 43}
44 44
45Result System::IsConfigValid(const std::string_view device_name, 45Result System::IsConfigValid(const std::string_view device_name,
46 const AudioInParameter& in_params) { 46 const AudioInParameter& in_params) const {
47 if ((device_name.size() > 0) && 47 if ((device_name.size() > 0) &&
48 (device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) { 48 (device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) {
49 return Service::Audio::ERR_INVALID_DEVICE_NAME; 49 return Service::Audio::ERR_INVALID_DEVICE_NAME;
@@ -93,6 +93,7 @@ Result System::Start() {
93 std::vector<AudioBuffer> buffers_to_flush{}; 93 std::vector<AudioBuffer> buffers_to_flush{};
94 buffers.RegisterBuffers(buffers_to_flush); 94 buffers.RegisterBuffers(buffers_to_flush);
95 session->AppendBuffers(buffers_to_flush); 95 session->AppendBuffers(buffers_to_flush);
96 session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
96 97
97 return ResultSuccess; 98 return ResultSuccess;
98} 99}
@@ -112,8 +113,15 @@ bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) {
112 return false; 113 return false;
113 } 114 }
114 115
115 AudioBuffer new_buffer{ 116 const auto timestamp{buffers.GetNextTimestamp()};
116 .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; 117 const AudioBuffer new_buffer{
118 .start_timestamp = timestamp,
119 .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
120 .played_timestamp = 0,
121 .samples = buffer.samples,
122 .tag = tag,
123 .size = buffer.size,
124 };
117 125
118 buffers.AppendBuffer(new_buffer); 126 buffers.AppendBuffer(new_buffer);
119 RegisterBuffers(); 127 RegisterBuffers();
@@ -194,11 +202,11 @@ void System::SetVolume(const f32 volume_) {
194 session->SetVolume(volume_); 202 session->SetVolume(volume_);
195} 203}
196 204
197bool System::ContainsAudioBuffer(const u64 tag) { 205bool System::ContainsAudioBuffer(const u64 tag) const {
198 return buffers.ContainsBuffer(tag); 206 return buffers.ContainsBuffer(tag);
199} 207}
200 208
201u32 System::GetBufferCount() { 209u32 System::GetBufferCount() const {
202 return buffers.GetAppendedRegisteredCount(); 210 return buffers.GetAppendedRegisteredCount();
203} 211}
204 212
diff --git a/src/audio_core/in/audio_in_system.h b/src/audio_core/in/audio_in_system.h
index 165e35d83..b9dc0e60f 100644
--- a/src/audio_core/in/audio_in_system.h
+++ b/src/audio_core/in/audio_in_system.h
@@ -68,7 +68,7 @@ public:
68 * 68 *
69 * @return The default audio input device name. 69 * @return The default audio input device name.
70 */ 70 */
71 std::string_view GetDefaultDeviceName(); 71 std::string_view GetDefaultDeviceName() const;
72 72
73 /** 73 /**
74 * Get the default USB audio input device name. 74 * Get the default USB audio input device name.
@@ -77,7 +77,7 @@ public:
77 * 77 *
78 * @return The default USB audio input device name. 78 * @return The default USB audio input device name.
79 */ 79 */
80 std::string_view GetDefaultUacDeviceName(); 80 std::string_view GetDefaultUacDeviceName() const;
81 81
82 /** 82 /**
83 * Is the given initialize config valid? 83 * Is the given initialize config valid?
@@ -86,7 +86,7 @@ public:
86 * @param in_params - Input parameters, see AudioInParameter. 86 * @param in_params - Input parameters, see AudioInParameter.
87 * @return Result code. 87 * @return Result code.
88 */ 88 */
89 Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params); 89 Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params) const;
90 90
91 /** 91 /**
92 * Initialize this system. 92 * Initialize this system.
@@ -208,7 +208,7 @@ public:
208 /** 208 /**
209 * Set this system's current volume. 209 * Set this system's current volume.
210 * 210 *
211 * @param The new volume. 211 * @param volume The new volume.
212 */ 212 */
213 void SetVolume(f32 volume); 213 void SetVolume(f32 volume);
214 214
@@ -218,14 +218,14 @@ public:
218 * @param tag - Unique tag to search for. 218 * @param tag - Unique tag to search for.
219 * @return True if the buffer is in the system, otherwise false. 219 * @return True if the buffer is in the system, otherwise false.
220 */ 220 */
221 bool ContainsAudioBuffer(u64 tag); 221 bool ContainsAudioBuffer(u64 tag) const;
222 222
223 /** 223 /**
224 * Get the maximum number of usable buffers (default 32). 224 * Get the maximum number of usable buffers (default 32).
225 * 225 *
226 * @return The number of buffers. 226 * @return The number of buffers.
227 */ 227 */
228 u32 GetBufferCount(); 228 u32 GetBufferCount() const;
229 229
230 /** 230 /**
231 * Get the total number of samples played by this system. 231 * Get the total number of samples played by this system.
diff --git a/src/audio_core/out/audio_out.cpp b/src/audio_core/out/audio_out.cpp
index 9a8d8a742..d3ee4f0eb 100644
--- a/src/audio_core/out/audio_out.cpp
+++ b/src/audio_core/out/audio_out.cpp
@@ -72,7 +72,7 @@ Kernel::KReadableEvent& Out::GetBufferEvent() {
72 return event->GetReadableEvent(); 72 return event->GetReadableEvent();
73} 73}
74 74
75f32 Out::GetVolume() { 75f32 Out::GetVolume() const {
76 std::scoped_lock l{parent_mutex}; 76 std::scoped_lock l{parent_mutex};
77 return system.GetVolume(); 77 return system.GetVolume();
78} 78}
@@ -82,17 +82,17 @@ void Out::SetVolume(const f32 volume) {
82 system.SetVolume(volume); 82 system.SetVolume(volume);
83} 83}
84 84
85bool Out::ContainsAudioBuffer(const u64 tag) { 85bool Out::ContainsAudioBuffer(const u64 tag) const {
86 std::scoped_lock l{parent_mutex}; 86 std::scoped_lock l{parent_mutex};
87 return system.ContainsAudioBuffer(tag); 87 return system.ContainsAudioBuffer(tag);
88} 88}
89 89
90u32 Out::GetBufferCount() { 90u32 Out::GetBufferCount() const {
91 std::scoped_lock l{parent_mutex}; 91 std::scoped_lock l{parent_mutex};
92 return system.GetBufferCount(); 92 return system.GetBufferCount();
93} 93}
94 94
95u64 Out::GetPlayedSampleCount() { 95u64 Out::GetPlayedSampleCount() const {
96 std::scoped_lock l{parent_mutex}; 96 std::scoped_lock l{parent_mutex};
97 return system.GetPlayedSampleCount(); 97 return system.GetPlayedSampleCount();
98} 98}
diff --git a/src/audio_core/out/audio_out.h b/src/audio_core/out/audio_out.h
index f6b921645..946f345c6 100644
--- a/src/audio_core/out/audio_out.h
+++ b/src/audio_core/out/audio_out.h
@@ -102,7 +102,7 @@ public:
102 * 102 *
103 * @return The current volume. 103 * @return The current volume.
104 */ 104 */
105 f32 GetVolume(); 105 f32 GetVolume() const;
106 106
107 /** 107 /**
108 * Set the system volume. 108 * Set the system volume.
@@ -117,21 +117,21 @@ public:
117 * @param tag - The tag to search for. 117 * @param tag - The tag to search for.
118 * @return True if the buffer is in the system, otherwise false. 118 * @return True if the buffer is in the system, otherwise false.
119 */ 119 */
120 bool ContainsAudioBuffer(u64 tag); 120 bool ContainsAudioBuffer(u64 tag) const;
121 121
122 /** 122 /**
123 * Get the maximum number of buffers. 123 * Get the maximum number of buffers.
124 * 124 *
125 * @return The maximum number of buffers. 125 * @return The maximum number of buffers.
126 */ 126 */
127 u32 GetBufferCount(); 127 u32 GetBufferCount() const;
128 128
129 /** 129 /**
130 * Get the total played sample count for this audio out. 130 * Get the total played sample count for this audio out.
131 * 131 *
132 * @return The played sample count. 132 * @return The played sample count.
133 */ 133 */
134 u64 GetPlayedSampleCount(); 134 u64 GetPlayedSampleCount() const;
135 135
136private: 136private:
137 /// The AudioOut::Manager this audio out is registered with 137 /// The AudioOut::Manager this audio out is registered with
diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp
index 35afddf06..8b907590a 100644
--- a/src/audio_core/out/audio_out_system.cpp
+++ b/src/audio_core/out/audio_out_system.cpp
@@ -27,11 +27,12 @@ void System::Finalize() {
27 buffer_event->GetWritableEvent().Signal(); 27 buffer_event->GetWritableEvent().Signal();
28} 28}
29 29
30std::string_view System::GetDefaultOutputDeviceName() { 30std::string_view System::GetDefaultOutputDeviceName() const {
31 return "DeviceOut"; 31 return "DeviceOut";
32} 32}
33 33
34Result System::IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) { 34Result System::IsConfigValid(std::string_view device_name,
35 const AudioOutParameter& in_params) const {
35 if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) { 36 if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) {
36 return Service::Audio::ERR_INVALID_DEVICE_NAME; 37 return Service::Audio::ERR_INVALID_DEVICE_NAME;
37 } 38 }
@@ -92,6 +93,7 @@ Result System::Start() {
92 std::vector<AudioBuffer> buffers_to_flush{}; 93 std::vector<AudioBuffer> buffers_to_flush{};
93 buffers.RegisterBuffers(buffers_to_flush); 94 buffers.RegisterBuffers(buffers_to_flush);
94 session->AppendBuffers(buffers_to_flush); 95 session->AppendBuffers(buffers_to_flush);
96 session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
95 97
96 return ResultSuccess; 98 return ResultSuccess;
97} 99}
@@ -111,8 +113,15 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) {
111 return false; 113 return false;
112 } 114 }
113 115
114 AudioBuffer new_buffer{ 116 const auto timestamp{buffers.GetNextTimestamp()};
115 .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; 117 const AudioBuffer new_buffer{
118 .start_timestamp = timestamp,
119 .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
120 .played_timestamp = 0,
121 .samples = buffer.samples,
122 .tag = tag,
123 .size = buffer.size,
124 };
116 125
117 buffers.AppendBuffer(new_buffer); 126 buffers.AppendBuffer(new_buffer);
118 RegisterBuffers(); 127 RegisterBuffers();
@@ -192,11 +201,11 @@ void System::SetVolume(const f32 volume_) {
192 session->SetVolume(volume_); 201 session->SetVolume(volume_);
193} 202}
194 203
195bool System::ContainsAudioBuffer(const u64 tag) { 204bool System::ContainsAudioBuffer(const u64 tag) const {
196 return buffers.ContainsBuffer(tag); 205 return buffers.ContainsBuffer(tag);
197} 206}
198 207
199u32 System::GetBufferCount() { 208u32 System::GetBufferCount() const {
200 return buffers.GetAppendedRegisteredCount(); 209 return buffers.GetAppendedRegisteredCount();
201} 210}
202 211
diff --git a/src/audio_core/out/audio_out_system.h b/src/audio_core/out/audio_out_system.h
index 4ca2f3417..0817b2f37 100644
--- a/src/audio_core/out/audio_out_system.h
+++ b/src/audio_core/out/audio_out_system.h
@@ -68,7 +68,7 @@ public:
68 * 68 *
69 * @return The default audio output device name. 69 * @return The default audio output device name.
70 */ 70 */
71 std::string_view GetDefaultOutputDeviceName(); 71 std::string_view GetDefaultOutputDeviceName() const;
72 72
73 /** 73 /**
74 * Is the given initialize config valid? 74 * Is the given initialize config valid?
@@ -77,7 +77,7 @@ public:
77 * @param in_params - Input parameters, see AudioOutParameter. 77 * @param in_params - Input parameters, see AudioOutParameter.
78 * @return Result code. 78 * @return Result code.
79 */ 79 */
80 Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params); 80 Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) const;
81 81
82 /** 82 /**
83 * Initialize this system. 83 * Initialize this system.
@@ -199,7 +199,7 @@ public:
199 /** 199 /**
200 * Set this system's current volume. 200 * Set this system's current volume.
201 * 201 *
202 * @param The new volume. 202 * @param volume The new volume.
203 */ 203 */
204 void SetVolume(f32 volume); 204 void SetVolume(f32 volume);
205 205
@@ -209,14 +209,14 @@ public:
209 * @param tag - Unique tag to search for. 209 * @param tag - Unique tag to search for.
210 * @return True if the buffer is in the system, otherwise false. 210 * @return True if the buffer is in the system, otherwise false.
211 */ 211 */
212 bool ContainsAudioBuffer(u64 tag); 212 bool ContainsAudioBuffer(u64 tag) const;
213 213
214 /** 214 /**
215 * Get the maximum number of usable buffers (default 32). 215 * Get the maximum number of usable buffers (default 32).
216 * 216 *
217 * @return The number of buffers. 217 * @return The number of buffers.
218 */ 218 */
219 u32 GetBufferCount(); 219 u32 GetBufferCount() const;
220 220
221 /** 221 /**
222 * Get the total number of samples played by this system. 222 * Get the total number of samples played by this system.
diff --git a/src/audio_core/renderer/adsp/adsp.cpp b/src/audio_core/renderer/adsp/adsp.cpp
index e05a22d86..a28395663 100644
--- a/src/audio_core/renderer/adsp/adsp.cpp
+++ b/src/audio_core/renderer/adsp/adsp.cpp
@@ -50,7 +50,7 @@ u32 ADSP::GetRemainCommandCount(const u32 session_id) const {
50 return render_mailbox.GetRemainCommandCount(session_id); 50 return render_mailbox.GetRemainCommandCount(session_id);
51} 51}
52 52
53void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) { 53void ADSP::SendCommandBuffer(const u32 session_id, const CommandBuffer& command_buffer) {
54 render_mailbox.SetCommandBuffer(session_id, command_buffer); 54 render_mailbox.SetCommandBuffer(session_id, command_buffer);
55} 55}
56 56
diff --git a/src/audio_core/renderer/adsp/adsp.h b/src/audio_core/renderer/adsp/adsp.h
index 4dfcef4a5..f7a2f25e4 100644
--- a/src/audio_core/renderer/adsp/adsp.h
+++ b/src/audio_core/renderer/adsp/adsp.h
@@ -63,8 +63,6 @@ public:
63 63
64 /** 64 /**
65 * Stop the ADSP. 65 * Stop the ADSP.
66 *
67 * @return True if started or already running, otherwise false.
68 */ 66 */
69 void Stop(); 67 void Stop();
70 68
@@ -133,7 +131,7 @@ public:
133 * @param session_id - The session id to check (0 or 1). 131 * @param session_id - The session id to check (0 or 1).
134 * @param command_buffer - The command buffer to process. 132 * @param command_buffer - The command buffer to process.
135 */ 133 */
136 void SendCommandBuffer(u32 session_id, CommandBuffer& command_buffer); 134 void SendCommandBuffer(u32 session_id, const CommandBuffer& command_buffer);
137 135
138 /** 136 /**
139 * Clear the command buffers (does not clear the time taken or the remaining command count) 137 * Clear the command buffers (does not clear the time taken or the remaining command count)
diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp
index 3967ccfe6..ab2257bd8 100644
--- a/src/audio_core/renderer/adsp/audio_renderer.cpp
+++ b/src/audio_core/renderer/adsp/audio_renderer.cpp
@@ -47,11 +47,11 @@ RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() {
47 return msg; 47 return msg;
48} 48}
49 49
50CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) { 50CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const u32 session_id) {
51 return command_buffers[session_id]; 51 return command_buffers[session_id];
52} 52}
53 53
54void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) { 54void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, const CommandBuffer& buffer) {
55 command_buffers[session_id] = buffer; 55 command_buffers[session_id] = buffer;
56} 56}
57 57
@@ -106,9 +106,6 @@ void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
106 106
107 mailbox = mailbox_; 107 mailbox = mailbox_;
108 thread = std::thread(&AudioRenderer::ThreadFunc, this); 108 thread = std::thread(&AudioRenderer::ThreadFunc, this);
109 for (auto& stream : streams) {
110 stream->Start();
111 }
112 running = true; 109 running = true;
113} 110}
114 111
@@ -130,6 +127,7 @@ void AudioRenderer::CreateSinkStreams() {
130 std::string name{fmt::format("ADSP_RenderStream-{}", i)}; 127 std::string name{fmt::format("ADSP_RenderStream-{}", i)};
131 streams[i] = 128 streams[i] =
132 sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render); 129 sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
130 streams[i]->SetRingSize(4);
133 } 131 }
134} 132}
135 133
@@ -198,11 +196,6 @@ void AudioRenderer::ThreadFunc() {
198 command_list_processor.Process(index) - start_time; 196 command_list_processor.Process(index) - start_time;
199 } 197 }
200 198
201 if (index == 0) {
202 auto stream{command_list_processor.GetOutputSinkStream()};
203 system.AudioCore().SetStreamQueue(stream->GetQueueSize());
204 }
205
206 const auto end_time{system.CoreTiming().GetClockTicks()}; 199 const auto end_time{system.CoreTiming().GetClockTicks()};
207 200
208 command_buffer.remaining_command_count = 201 command_buffer.remaining_command_count =
diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h
index b6ced9d2b..151f38c1b 100644
--- a/src/audio_core/renderer/adsp/audio_renderer.h
+++ b/src/audio_core/renderer/adsp/audio_renderer.h
@@ -52,7 +52,7 @@ public:
52 /** 52 /**
53 * Send a message from the host to the AudioRenderer. 53 * Send a message from the host to the AudioRenderer.
54 * 54 *
55 * @param message_ - The message to send to the AudioRenderer. 55 * @param message - The message to send to the AudioRenderer.
56 */ 56 */
57 void HostSendMessage(RenderMessage message); 57 void HostSendMessage(RenderMessage message);
58 58
@@ -66,7 +66,7 @@ public:
66 /** 66 /**
67 * Send a message from the AudioRenderer to the host. 67 * Send a message from the AudioRenderer to the host.
68 * 68 *
69 * @param message_ - The message to send to the host. 69 * @param message - The message to send to the host.
70 */ 70 */
71 void ADSPSendMessage(RenderMessage message); 71 void ADSPSendMessage(RenderMessage message);
72 72
@@ -83,7 +83,7 @@ public:
83 * @param session_id - The session id to get (0 or 1). 83 * @param session_id - The session id to get (0 or 1).
84 * @return The command buffer. 84 * @return The command buffer.
85 */ 85 */
86 CommandBuffer& GetCommandBuffer(s32 session_id); 86 CommandBuffer& GetCommandBuffer(u32 session_id);
87 87
88 /** 88 /**
89 * Set the command buffer with the given session id (0 or 1). 89 * Set the command buffer with the given session id (0 or 1).
@@ -91,7 +91,7 @@ public:
91 * @param session_id - The session id to get (0 or 1). 91 * @param session_id - The session id to get (0 or 1).
92 * @param buffer - The command buffer to set. 92 * @param buffer - The command buffer to set.
93 */ 93 */
94 void SetCommandBuffer(u32 session_id, CommandBuffer& buffer); 94 void SetCommandBuffer(u32 session_id, const CommandBuffer& buffer);
95 95
96 /** 96 /**
97 * Get the total render time taken for the last command lists sent. 97 * Get the total render time taken for the last command lists sent.
@@ -163,7 +163,7 @@ public:
163 /** 163 /**
164 * Start the AudioRenderer. 164 * Start the AudioRenderer.
165 * 165 *
166 * @param The mailbox to use for this session. 166 * @param mailbox The mailbox to use for this session.
167 */ 167 */
168 void Start(AudioRenderer_Mailbox* mailbox); 168 void Start(AudioRenderer_Mailbox* mailbox);
169 169
diff --git a/src/audio_core/renderer/adsp/command_list_processor.h b/src/audio_core/renderer/adsp/command_list_processor.h
index 3f99173e3..d78269e1d 100644
--- a/src/audio_core/renderer/adsp/command_list_processor.h
+++ b/src/audio_core/renderer/adsp/command_list_processor.h
@@ -33,10 +33,10 @@ public:
33 /** 33 /**
34 * Initialize the processor. 34 * Initialize the processor.
35 * 35 *
36 * @param system_ - The core system. 36 * @param system - The core system.
37 * @param buffer - The command buffer to process. 37 * @param buffer - The command buffer to process.
38 * @param size - The size of the buffer. 38 * @param size - The size of the buffer.
39 * @param stream_ - The stream to be used for sending the samples. 39 * @param stream - The stream to be used for sending the samples.
40 */ 40 */
41 void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream); 41 void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream);
42 42
@@ -72,7 +72,8 @@ public:
72 /** 72 /**
73 * Process the command list. 73 * Process the command list.
74 * 74 *
75 * @param index - Index of the current command list. 75 * @param session_id - Session ID for the commands being processed.
76 *
76 * @return The time taken to process. 77 * @return The time taken to process.
77 */ 78 */
78 u64 Process(u32 session_id); 79 u64 Process(u32 session_id);
@@ -89,7 +90,7 @@ public:
89 u8* commands{}; 90 u8* commands{};
90 /// The command buffer size 91 /// The command buffer size
91 u64 commands_buffer_size{}; 92 u64 commands_buffer_size{};
92 /// The maximum processing time alloted 93 /// The maximum processing time allotted
93 u64 max_process_time{}; 94 u64 max_process_time{};
94 /// The number of commands in the buffer 95 /// The number of commands in the buffer
95 u32 command_count{}; 96 u32 command_count{};
diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp
index d5886e55e..0d9d8f6ce 100644
--- a/src/audio_core/renderer/audio_device.cpp
+++ b/src/audio_core/renderer/audio_device.cpp
@@ -1,6 +1,9 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <array>
5#include <span>
6
4#include "audio_core/audio_core.h" 7#include "audio_core/audio_core.h"
5#include "audio_core/common/feature_support.h" 8#include "audio_core/common/feature_support.h"
6#include "audio_core/renderer/audio_device.h" 9#include "audio_core/renderer/audio_device.h"
@@ -9,14 +12,33 @@
9 12
10namespace AudioCore::AudioRenderer { 13namespace AudioCore::AudioRenderer {
11 14
15constexpr std::array usb_device_names{
16 AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
17 AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
18 AudioDevice::AudioDeviceName{"AudioTvOutput"},
19 AudioDevice::AudioDeviceName{"AudioUsbDeviceOutput"},
20};
21
22constexpr std::array device_names{
23 AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
24 AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
25 AudioDevice::AudioDeviceName{"AudioTvOutput"},
26};
27
28constexpr std::array output_device_names{
29 AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
30 AudioDevice::AudioDeviceName{"AudioTvOutput"},
31 AudioDevice::AudioDeviceName{"AudioExternalOutput"},
32};
33
12AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_, 34AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_,
13 const u32 revision) 35 const u32 revision)
14 : output_sink{system.AudioCore().GetOutputSink()}, 36 : output_sink{system.AudioCore().GetOutputSink()},
15 applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {} 37 applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {}
16 38
17u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, 39u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
18 const size_t max_count) { 40 const size_t max_count) const {
19 std::span<AudioDeviceName> names{}; 41 std::span<const AudioDeviceName> names{};
20 42
21 if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) { 43 if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) {
22 names = usb_device_names; 44 names = usb_device_names;
@@ -24,7 +46,7 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
24 names = device_names; 46 names = device_names;
25 } 47 }
26 48
27 u32 out_count{static_cast<u32>(std::min(max_count, names.size()))}; 49 const u32 out_count{static_cast<u32>(std::min(max_count, names.size()))};
28 for (u32 i = 0; i < out_count; i++) { 50 for (u32 i = 0; i < out_count; i++) {
29 out_buffer.push_back(names[i]); 51 out_buffer.push_back(names[i]);
30 } 52 }
@@ -32,8 +54,8 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
32} 54}
33 55
34u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, 56u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer,
35 const size_t max_count) { 57 const size_t max_count) const {
36 u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))}; 58 const u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))};
37 59
38 for (u32 i = 0; i < out_count; i++) { 60 for (u32 i = 0; i < out_count; i++) {
39 out_buffer.push_back(output_device_names[i]); 61 out_buffer.push_back(output_device_names[i]);
@@ -45,7 +67,7 @@ void AudioDevice::SetDeviceVolumes(const f32 volume) {
45 output_sink.SetDeviceVolume(volume); 67 output_sink.SetDeviceVolume(volume);
46} 68}
47 69
48f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) { 70f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const {
49 return output_sink.GetDeviceVolume(); 71 return output_sink.GetDeviceVolume();
50} 72}
51 73
diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h
index 1f449f261..dd6be70ee 100644
--- a/src/audio_core/renderer/audio_device.h
+++ b/src/audio_core/renderer/audio_device.h
@@ -3,7 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <span> 6#include <string_view>
7 7
8#include "audio_core/audio_render_manager.h" 8#include "audio_core/audio_render_manager.h"
9 9
@@ -23,21 +23,13 @@ namespace AudioRenderer {
23class AudioDevice { 23class AudioDevice {
24public: 24public:
25 struct AudioDeviceName { 25 struct AudioDeviceName {
26 std::array<char, 0x100> name; 26 std::array<char, 0x100> name{};
27 27
28 AudioDeviceName(const char* name_) { 28 constexpr AudioDeviceName(std::string_view name_) {
29 std::strncpy(name.data(), name_, name.size()); 29 name_.copy(name.data(), name.size() - 1);
30 } 30 }
31 }; 31 };
32 32
33 std::array<AudioDeviceName, 4> usb_device_names{"AudioStereoJackOutput",
34 "AudioBuiltInSpeakerOutput", "AudioTvOutput",
35 "AudioUsbDeviceOutput"};
36 std::array<AudioDeviceName, 3> device_names{"AudioStereoJackOutput",
37 "AudioBuiltInSpeakerOutput", "AudioTvOutput"};
38 std::array<AudioDeviceName, 3> output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput",
39 "AudioExternalOutput"};
40
41 explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision); 33 explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision);
42 34
43 /** 35 /**
@@ -47,7 +39,7 @@ public:
47 * @param max_count - Maximum number of devices to write (count of out_buffer). 39 * @param max_count - Maximum number of devices to write (count of out_buffer).
48 * @return Number of device names written. 40 * @return Number of device names written.
49 */ 41 */
50 u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); 42 u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
51 43
52 /** 44 /**
53 * Get a list of the available output devices. 45 * Get a list of the available output devices.
@@ -57,7 +49,7 @@ public:
57 * @param max_count - Maximum number of devices to write (count of out_buffer). 49 * @param max_count - Maximum number of devices to write (count of out_buffer).
58 * @return Number of device names written. 50 * @return Number of device names written.
59 */ 51 */
60 u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); 52 u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
61 53
62 /** 54 /**
63 * Set the volume of all streams in the backend sink. 55 * Set the volume of all streams in the backend sink.
@@ -73,7 +65,7 @@ public:
73 * @param name - Name of the device to check. Unused. 65 * @param name - Name of the device to check. Unused.
74 * @return Volume of the device. 66 * @return Volume of the device.
75 */ 67 */
76 f32 GetDeviceVolume(std::string_view name); 68 f32 GetDeviceVolume(std::string_view name) const;
77 69
78private: 70private:
79 /// Backend output sink for the device 71 /// Backend output sink for the device
diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp
index c5d4d66d8..3d2a91312 100644
--- a/src/audio_core/renderer/behavior/behavior_info.cpp
+++ b/src/audio_core/renderer/behavior/behavior_info.cpp
@@ -34,7 +34,7 @@ void BehaviorInfo::ClearError() {
34 error_count = 0; 34 error_count = 0;
35} 35}
36 36
37void BehaviorInfo::AppendError(ErrorInfo& error) { 37void BehaviorInfo::AppendError(const ErrorInfo& error) {
38 LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}", 38 LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}",
39 error.error_code.raw, error.address); 39 error.error_code.raw, error.address);
40 if (error_count < MaxErrors) { 40 if (error_count < MaxErrors) {
@@ -42,14 +42,16 @@ void BehaviorInfo::AppendError(ErrorInfo& error) {
42 } 42 }
43} 43}
44 44
45void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) { 45void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const {
46 auto error_count_{std::min(error_count, MaxErrors)}; 46 out_count = std::min(error_count, MaxErrors);
47 std::memset(out_errors.data(), 0, MaxErrors * sizeof(ErrorInfo));
48 47
49 for (size_t i = 0; i < error_count_; i++) { 48 for (size_t i = 0; i < MaxErrors; i++) {
50 out_errors[i] = errors[i]; 49 if (i < out_count) {
50 out_errors[i] = errors[i];
51 } else {
52 out_errors[i] = {};
53 }
51 } 54 }
52 out_count = error_count_;
53} 55}
54 56
55void BehaviorInfo::UpdateFlags(const Flags flags_) { 57void BehaviorInfo::UpdateFlags(const Flags flags_) {
diff --git a/src/audio_core/renderer/behavior/behavior_info.h b/src/audio_core/renderer/behavior/behavior_info.h
index 7333c297f..15c948344 100644
--- a/src/audio_core/renderer/behavior/behavior_info.h
+++ b/src/audio_core/renderer/behavior/behavior_info.h
@@ -94,7 +94,7 @@ public:
94 * 94 *
95 * @param error - The new error. 95 * @param error - The new error.
96 */ 96 */
97 void AppendError(ErrorInfo& error); 97 void AppendError(const ErrorInfo& error);
98 98
99 /** 99 /**
100 * Copy errors to the given output container. 100 * Copy errors to the given output container.
@@ -102,7 +102,7 @@ public:
102 * @param out_errors - Output container to receive the errors. 102 * @param out_errors - Output container to receive the errors.
103 * @param out_count - The number of errors written. 103 * @param out_count - The number of errors written.
104 */ 104 */
105 void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count); 105 void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const;
106 106
107 /** 107 /**
108 * Update the behaviour flags. 108 * Update the behaviour flags.
diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp
index 06a37e1a6..c0a307b89 100644
--- a/src/audio_core/renderer/behavior/info_updater.cpp
+++ b/src/audio_core/renderer/behavior/info_updater.cpp
@@ -485,7 +485,7 @@ Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) {
485 return ResultSuccess; 485 return ResultSuccess;
486} 486}
487 487
488Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) { 488Result InfoUpdater::UpdateErrorInfo(const BehaviorInfo& behaviour_) {
489 auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)}; 489 auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)};
490 behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count); 490 behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count);
491 491
diff --git a/src/audio_core/renderer/behavior/info_updater.h b/src/audio_core/renderer/behavior/info_updater.h
index f0b445d9c..c817d8d8d 100644
--- a/src/audio_core/renderer/behavior/info_updater.h
+++ b/src/audio_core/renderer/behavior/info_updater.h
@@ -130,7 +130,7 @@ public:
130 * @param behaviour - Behaviour to update. 130 * @param behaviour - Behaviour to update.
131 * @return Result code. 131 * @return Result code.
132 */ 132 */
133 Result UpdateErrorInfo(BehaviorInfo& behaviour); 133 Result UpdateErrorInfo(const BehaviorInfo& behaviour);
134 134
135 /** 135 /**
136 * Update splitter. 136 * Update splitter.
diff --git a/src/audio_core/renderer/command/command_buffer.h b/src/audio_core/renderer/command/command_buffer.h
index 496b0e50a..162170846 100644
--- a/src/audio_core/renderer/command/command_buffer.h
+++ b/src/audio_core/renderer/command/command_buffer.h
@@ -191,6 +191,7 @@ public:
191 * @param volume - Current mix volume used for calculating the ramp. 191 * @param volume - Current mix volume used for calculating the ramp.
192 * @param prev_volume - Previous mix volume, used for calculating the ramp, 192 * @param prev_volume - Previous mix volume, used for calculating the ramp,
193 * also applied to the input. 193 * also applied to the input.
194 * @param prev_samples - Previous sample buffer. Used for depopping.
194 * @param precision - Number of decimal bits for fixed point operations. 195 * @param precision - Number of decimal bits for fixed point operations.
195 */ 196 */
196 void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index, 197 void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index,
@@ -208,6 +209,7 @@ public:
208 * @param volumes - Current mix volumes used for calculating the ramp. 209 * @param volumes - Current mix volumes used for calculating the ramp.
209 * @param prev_volumes - Previous mix volumes, used for calculating the ramp, 210 * @param prev_volumes - Previous mix volumes, used for calculating the ramp,
210 * also applied to the input. 211 * also applied to the input.
212 * @param prev_samples - Previous sample buffer. Used for depopping.
211 * @param precision - Number of decimal bits for fixed point operations. 213 * @param precision - Number of decimal bits for fixed point operations.
212 */ 214 */
213 void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index, 215 void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index,
@@ -297,11 +299,11 @@ public:
297 /** 299 /**
298 * Generate a device sink command, adding it to the command list. 300 * Generate a device sink command, adding it to the command list.
299 * 301 *
300 * @param node_id - Node id of the voice this command is generated for. 302 * @param node_id - Node id of the voice this command is generated for.
301 * @param buffer_offset - Base mix buffer offset to use. 303 * @param buffer_offset - Base mix buffer offset to use.
302 * @param sink_info - The sink_info to generate this command from. 304 * @param sink_info - The sink_info to generate this command from.
303 * @session_id - System session id this command is generated from. 305 * @param session_id - System session id this command is generated from.
304 * @samples_buffer - The buffer to be sent to the sink if upsampling is not used. 306 * @param samples_buffer - The buffer to be sent to the sink if upsampling is not used.
305 */ 307 */
306 void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info, 308 void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info,
307 u32 session_id, std::span<s32> samples_buffer); 309 u32 session_id, std::span<s32> samples_buffer);
diff --git a/src/audio_core/renderer/command/command_generator.h b/src/audio_core/renderer/command/command_generator.h
index d80d9b0d8..b3cd7b408 100644
--- a/src/audio_core/renderer/command/command_generator.h
+++ b/src/audio_core/renderer/command/command_generator.h
@@ -197,9 +197,9 @@ public:
197 /** 197 /**
198 * Generate an I3DL2 reverb effect command. 198 * Generate an I3DL2 reverb effect command.
199 * 199 *
200 * @param buffer_offset - Base mix buffer offset to use. 200 * @param buffer_offset - Base mix buffer offset to use.
201 * @param effect_info_base - I3DL2Reverb effect info. 201 * @param effect_info - I3DL2Reverb effect info.
202 * @param node_id - Node id of the mix this command is generated for. 202 * @param node_id - Node id of the mix this command is generated for.
203 */ 203 */
204 void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, 204 void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
205 s32 node_id); 205 s32 node_id);
@@ -207,18 +207,18 @@ public:
207 /** 207 /**
208 * Generate an aux effect command. 208 * Generate an aux effect command.
209 * 209 *
210 * @param buffer_offset - Base mix buffer offset to use. 210 * @param buffer_offset - Base mix buffer offset to use.
211 * @param effect_info_base - Aux effect info. 211 * @param effect_info - Aux effect info.
212 * @param node_id - Node id of the mix this command is generated for. 212 * @param node_id - Node id of the mix this command is generated for.
213 */ 213 */
214 void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); 214 void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
215 215
216 /** 216 /**
217 * Generate a biquad filter effect command. 217 * Generate a biquad filter effect command.
218 * 218 *
219 * @param buffer_offset - Base mix buffer offset to use. 219 * @param buffer_offset - Base mix buffer offset to use.
220 * @param effect_info_base - Aux effect info. 220 * @param effect_info - Aux effect info.
221 * @param node_id - Node id of the mix this command is generated for. 221 * @param node_id - Node id of the mix this command is generated for.
222 */ 222 */
223 void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, 223 void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
224 s32 node_id); 224 s32 node_id);
@@ -226,10 +226,10 @@ public:
226 /** 226 /**
227 * Generate a light limiter effect command. 227 * Generate a light limiter effect command.
228 * 228 *
229 * @param buffer_offset - Base mix buffer offset to use. 229 * @param buffer_offset - Base mix buffer offset to use.
230 * @param effect_info_base - Limiter effect info. 230 * @param effect_info - Limiter effect info.
231 * @param node_id - Node id of the mix this command is generated for. 231 * @param node_id - Node id of the mix this command is generated for.
232 * @param effect_index - Index for the statistics state. 232 * @param effect_index - Index for the statistics state.
233 */ 233 */
234 void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, 234 void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
235 s32 node_id, u32 effect_index); 235 s32 node_id, u32 effect_index);
@@ -238,21 +238,20 @@ public:
238 * Generate a capture effect command. 238 * Generate a capture effect command.
239 * Writes a mix buffer back to game memory. 239 * Writes a mix buffer back to game memory.
240 * 240 *
241 * @param buffer_offset - Base mix buffer offset to use. 241 * @param buffer_offset - Base mix buffer offset to use.
242 * @param effect_info_base - Capture effect info. 242 * @param effect_info - Capture effect info.
243 * @param node_id - Node id of the mix this command is generated for. 243 * @param node_id - Node id of the mix this command is generated for.
244 */ 244 */
245 void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); 245 void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
246 246
247 /** 247 /**
248 * Generate a compressor effect command. 248 * Generate a compressor effect command.
249 * 249 *
250 * @param buffer_offset - Base mix buffer offset to use. 250 * @param buffer_offset - Base mix buffer offset to use.
251 * @param effect_info_base - Compressor effect info. 251 * @param effect_info - Compressor effect info.
252 * @param node_id - Node id of the mix this command is generated for. 252 * @param node_id - Node id of the mix this command is generated for.
253 */ 253 */
254 void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info, 254 void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
255 const s32 node_id);
256 255
257 /** 256 /**
258 * Generate all effect commands for a mix. 257 * Generate all effect commands for a mix.
@@ -318,8 +317,9 @@ public:
318 * Generate a performance command. 317 * Generate a performance command.
319 * Used to report performance metrics of the AudioRenderer back to the game. 318 * Used to report performance metrics of the AudioRenderer back to the game.
320 * 319 *
321 * @param buffer_offset - Base mix buffer offset to use. 320 * @param node_id - Node ID of the mix this command is generated for
322 * @param sink_info - Sink info to generate the commands from. 321 * @param state - Output state of the generated performance command
322 * @param entry_addresses - Addresses to be written
323 */ 323 */
324 void GeneratePerformanceCommand(s32 node_id, PerformanceState state, 324 void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
325 const PerformanceEntryAddresses& entry_addresses); 325 const PerformanceEntryAddresses& entry_addresses);
diff --git a/src/audio_core/renderer/command/effect/compressor.cpp b/src/audio_core/renderer/command/effect/compressor.cpp
index 2ebc140f1..7229618e8 100644
--- a/src/audio_core/renderer/command/effect/compressor.cpp
+++ b/src/audio_core/renderer/command/effect/compressor.cpp
@@ -11,7 +11,7 @@
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::AudioRenderer {
13 13
14static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params, 14static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2& params,
15 CompressorInfo::State& state) { 15 CompressorInfo::State& state) {
16 const auto ratio{1.0f / params.compressor_ratio}; 16 const auto ratio{1.0f / params.compressor_ratio};
17 auto makeup_gain{0.0f}; 17 auto makeup_gain{0.0f};
@@ -31,9 +31,9 @@ static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& para
31 state.unk_20 = c; 31 state.unk_20 = c;
32} 32}
33 33
34static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params, 34static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2& params,
35 CompressorInfo::State& state) { 35 CompressorInfo::State& state) {
36 std::memset(&state, 0, sizeof(CompressorInfo::State)); 36 state = {};
37 37
38 state.unk_00 = 0; 38 state.unk_00 = 0;
39 state.unk_04 = 1.0f; 39 state.unk_04 = 1.0f;
@@ -42,7 +42,7 @@ static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params
42 SetCompressorEffectParameter(params, state); 42 SetCompressorEffectParameter(params, state);
43} 43}
44 44
45static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params, 45static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params,
46 CompressorInfo::State& state, bool enabled, 46 CompressorInfo::State& state, bool enabled,
47 std::vector<std::span<const s32>> input_buffers, 47 std::vector<std::span<const s32>> input_buffers,
48 std::vector<std::span<s32>> output_buffers, u32 sample_count) { 48 std::vector<std::span<s32>> output_buffers, u32 sample_count) {
@@ -103,8 +103,7 @@ static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params,
103 } else { 103 } else {
104 for (s16 channel = 0; channel < params.channel_count; channel++) { 104 for (s16 channel = 0; channel < params.channel_count; channel++) {
105 if (params.inputs[channel] != params.outputs[channel]) { 105 if (params.inputs[channel] != params.outputs[channel]) {
106 std::memcpy((char*)output_buffers[channel].data(), 106 std::memcpy(output_buffers[channel].data(), input_buffers[channel].data(),
107 (char*)input_buffers[channel].data(),
108 output_buffers[channel].size_bytes()); 107 output_buffers[channel].size_bytes());
109 } 108 }
110 } 109 }
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.cpp b/src/audio_core/renderer/command/mix/mix_ramp.cpp
index ffdafa1c8..d67123cd8 100644
--- a/src/audio_core/renderer/command/mix/mix_ramp.cpp
+++ b/src/audio_core/renderer/command/mix/mix_ramp.cpp
@@ -7,17 +7,7 @@
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::AudioRenderer {
10/** 10
11 * Mix input mix buffer into output mix buffer, with volume applied to the input.
12 *
13 * @tparam Q - Number of bits for fixed point operations.
14 * @param output - Output mix buffer.
15 * @param input - Input mix buffer.
16 * @param volume - Volume applied to the input.
17 * @param ramp - Ramp applied to volume every sample.
18 * @param sample_count - Number of samples to process.
19 * @return The final gained input sample, used for depopping.
20 */
21template <size_t Q> 11template <size_t Q>
22s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, 12s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
23 const f32 ramp_, const u32 sample_count) { 13 const f32 ramp_, const u32 sample_count) {
@@ -40,10 +30,8 @@ s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 vo
40 return sample.to_int(); 30 return sample.to_int();
41} 31}
42 32
43template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, const f32, const f32, 33template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32);
44 const u32); 34template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32);
45template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, const f32, const f32,
46 const u32);
47 35
48void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { 36void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
49 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; 37 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.h b/src/audio_core/renderer/command/mix/mix_ramp.h
index 770f57e80..52f74a273 100644
--- a/src/audio_core/renderer/command/mix/mix_ramp.h
+++ b/src/audio_core/renderer/command/mix/mix_ramp.h
@@ -61,13 +61,13 @@ struct MixRampCommand : ICommand {
61 * @tparam Q - Number of bits for fixed point operations. 61 * @tparam Q - Number of bits for fixed point operations.
62 * @param output - Output mix buffer. 62 * @param output - Output mix buffer.
63 * @param input - Input mix buffer. 63 * @param input - Input mix buffer.
64 * @param volume - Volume applied to the input. 64 * @param volume_ - Volume applied to the input.
65 * @param ramp - Ramp applied to volume every sample. 65 * @param ramp_ - Ramp applied to volume every sample.
66 * @param sample_count - Number of samples to process. 66 * @param sample_count - Number of samples to process.
67 * @return The final gained input sample, used for depopping. 67 * @return The final gained input sample, used for depopping.
68 */ 68 */
69template <size_t Q> 69template <size_t Q>
70s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, 70s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, f32 volume_, f32 ramp_,
71 const f32 ramp_, const u32 sample_count); 71 u32 sample_count);
72 72
73} // namespace AudioCore::AudioRenderer 73} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
index 027276e5a..3b0ce67ef 100644
--- a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
+++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
@@ -50,9 +50,9 @@ struct MixRampGroupedCommand : ICommand {
50 std::array<s16, MaxMixBuffers> inputs; 50 std::array<s16, MaxMixBuffers> inputs;
51 /// Output mix buffer indexes for each mix buffer 51 /// Output mix buffer indexes for each mix buffer
52 std::array<s16, MaxMixBuffers> outputs; 52 std::array<s16, MaxMixBuffers> outputs;
53 /// Previous mix vloumes for each mix buffer 53 /// Previous mix volumes for each mix buffer
54 std::array<f32, MaxMixBuffers> prev_volumes; 54 std::array<f32, MaxMixBuffers> prev_volumes;
55 /// Current mix vloumes for each mix buffer 55 /// Current mix volumes for each mix buffer
56 std::array<f32, MaxMixBuffers> volumes; 56 std::array<f32, MaxMixBuffers> volumes;
57 /// Pointer to the previous sample buffer, used for depop 57 /// Pointer to the previous sample buffer, used for depop
58 CpuAddr previous_samples; 58 CpuAddr previous_samples;
diff --git a/src/audio_core/renderer/command/sink/device.cpp b/src/audio_core/renderer/command/sink/device.cpp
index 47e0c6722..e88372a75 100644
--- a/src/audio_core/renderer/command/sink/device.cpp
+++ b/src/audio_core/renderer/command/sink/device.cpp
@@ -46,6 +46,10 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
46 46
47 out_buffer.tag = reinterpret_cast<u64>(samples.data()); 47 out_buffer.tag = reinterpret_cast<u64>(samples.data());
48 stream->AppendBuffer(out_buffer, samples); 48 stream->AppendBuffer(out_buffer, samples);
49
50 if (stream->IsPaused()) {
51 stream->Start();
52 }
49} 53}
50 54
51bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { 55bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
diff --git a/src/audio_core/renderer/effect/effect_context.h b/src/audio_core/renderer/effect/effect_context.h
index 85955bd9c..8f6d6e7d8 100644
--- a/src/audio_core/renderer/effect/effect_context.h
+++ b/src/audio_core/renderer/effect/effect_context.h
@@ -15,15 +15,15 @@ class EffectContext {
15public: 15public:
16 /** 16 /**
17 * Initialize the effect context 17 * Initialize the effect context
18 * @param effect_infos List of effect infos for this context 18 * @param effect_infos_ - List of effect infos for this context
19 * @param effect_count The number of effects in the list 19 * @param effect_count_ - The number of effects in the list
20 * @param result_states_cpu The workbuffer of result states for the CPU for this context 20 * @param result_states_cpu_ - The workbuffer of result states for the CPU for this context
21 * @param result_states_dsp The workbuffer of result states for the DSP for this context 21 * @param result_states_dsp_ - The workbuffer of result states for the DSP for this context
22 * @param state_count The number of result states 22 * @param dsp_state_count - The number of result states
23 */ 23 */
24 void Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_, 24 void Initialize(std::span<EffectInfoBase> effect_infos_, u32 effect_count_,
25 std::span<EffectResultState> result_states_cpu_, 25 std::span<EffectResultState> result_states_cpu_,
26 std::span<EffectResultState> result_states_dsp_, const size_t dsp_state_count); 26 std::span<EffectResultState> result_states_dsp_, size_t dsp_state_count);
27 27
28 /** 28 /**
29 * Get the EffectInfo for a given index 29 * Get the EffectInfo for a given index
diff --git a/src/audio_core/renderer/effect/effect_info_base.h b/src/audio_core/renderer/effect/effect_info_base.h
index 8c9583878..8525fde05 100644
--- a/src/audio_core/renderer/effect/effect_info_base.h
+++ b/src/audio_core/renderer/effect/effect_info_base.h
@@ -291,7 +291,7 @@ public:
291 * Update the info with new parameters, version 1. 291 * Update the info with new parameters, version 1.
292 * 292 *
293 * @param error_info - Used to write call result code. 293 * @param error_info - Used to write call result code.
294 * @param in_params - New parameters to update the info with. 294 * @param params - New parameters to update the info with.
295 * @param pool_mapper - Pool for mapping buffers. 295 * @param pool_mapper - Pool for mapping buffers.
296 */ 296 */
297 virtual void Update(BehaviorInfo::ErrorInfo& error_info, 297 virtual void Update(BehaviorInfo::ErrorInfo& error_info,
@@ -305,7 +305,7 @@ public:
305 * Update the info with new parameters, version 2. 305 * Update the info with new parameters, version 2.
306 * 306 *
307 * @param error_info - Used to write call result code. 307 * @param error_info - Used to write call result code.
308 * @param in_params - New parameters to update the info with. 308 * @param params - New parameters to update the info with.
309 * @param pool_mapper - Pool for mapping buffers. 309 * @param pool_mapper - Pool for mapping buffers.
310 */ 310 */
311 virtual void Update(BehaviorInfo::ErrorInfo& error_info, 311 virtual void Update(BehaviorInfo::ErrorInfo& error_info,
diff --git a/src/audio_core/renderer/effect/i3dl2.h b/src/audio_core/renderer/effect/i3dl2.h
index 7a088a627..1ebbc5c4c 100644
--- a/src/audio_core/renderer/effect/i3dl2.h
+++ b/src/audio_core/renderer/effect/i3dl2.h
@@ -99,7 +99,7 @@ public:
99 return out_sample; 99 return out_sample;
100 } 100 }
101 101
102 Common::FixedPoint<50, 14> Read() { 102 Common::FixedPoint<50, 14> Read() const {
103 return *output; 103 return *output;
104 } 104 }
105 105
@@ -110,7 +110,7 @@ public:
110 } 110 }
111 } 111 }
112 112
113 Common::FixedPoint<50, 14> TapOut(const s32 index) { 113 Common::FixedPoint<50, 14> TapOut(const s32 index) const {
114 auto out{input - (index + 1)}; 114 auto out{input - (index + 1)};
115 if (out < buffer.data()) { 115 if (out < buffer.data()) {
116 out += max_delay + 1; 116 out += max_delay + 1;
diff --git a/src/audio_core/renderer/effect/reverb.h b/src/audio_core/renderer/effect/reverb.h
index b4df9f6ef..a72475c3c 100644
--- a/src/audio_core/renderer/effect/reverb.h
+++ b/src/audio_core/renderer/effect/reverb.h
@@ -95,7 +95,7 @@ public:
95 return out_sample; 95 return out_sample;
96 } 96 }
97 97
98 Common::FixedPoint<50, 14> Read() { 98 Common::FixedPoint<50, 14> Read() const {
99 return *output; 99 return *output;
100 } 100 }
101 101
@@ -106,7 +106,7 @@ public:
106 } 106 }
107 } 107 }
108 108
109 Common::FixedPoint<50, 14> TapOut(const s32 index) { 109 Common::FixedPoint<50, 14> TapOut(const s32 index) const {
110 auto out{input - (index + 1)}; 110 auto out{input - (index + 1)};
111 if (out < buffer.data()) { 111 if (out < buffer.data()) {
112 out += sample_count; 112 out += sample_count;
diff --git a/src/audio_core/renderer/memory/address_info.h b/src/audio_core/renderer/memory/address_info.h
index 4cfefea8e..bb5c930e1 100644
--- a/src/audio_core/renderer/memory/address_info.h
+++ b/src/audio_core/renderer/memory/address_info.h
@@ -19,8 +19,8 @@ public:
19 /** 19 /**
20 * Setup a new AddressInfo. 20 * Setup a new AddressInfo.
21 * 21 *
22 * @param cpu_address - The CPU address of this region. 22 * @param cpu_address_ - The CPU address of this region.
23 * @param size - The size of this region. 23 * @param size_ - The size of this region.
24 */ 24 */
25 void Setup(CpuAddr cpu_address_, u64 size_) { 25 void Setup(CpuAddr cpu_address_, u64 size_) {
26 cpu_address = cpu_address_; 26 cpu_address = cpu_address_;
@@ -42,7 +42,6 @@ public:
42 * Assign this region to a memory pool. 42 * Assign this region to a memory pool.
43 * 43 *
44 * @param memory_pool_ - Memory pool to assign. 44 * @param memory_pool_ - Memory pool to assign.
45 * @return The CpuAddr address of this region.
46 */ 45 */
47 void SetPool(MemoryPoolInfo* memory_pool_) { 46 void SetPool(MemoryPoolInfo* memory_pool_) {
48 memory_pool = memory_pool_; 47 memory_pool = memory_pool_;
diff --git a/src/audio_core/renderer/nodes/node_states.h b/src/audio_core/renderer/nodes/node_states.h
index a1e0958a2..94b1d1254 100644
--- a/src/audio_core/renderer/nodes/node_states.h
+++ b/src/audio_core/renderer/nodes/node_states.h
@@ -56,7 +56,7 @@ class NodeStates {
56 * 56 *
57 * @return The current stack position. 57 * @return The current stack position.
58 */ 58 */
59 u32 Count() { 59 u32 Count() const {
60 return pos; 60 return pos;
61 } 61 }
62 62
@@ -83,7 +83,7 @@ class NodeStates {
83 * 83 *
84 * @return The node on the top of the stack. 84 * @return The node on the top of the stack.
85 */ 85 */
86 u32 top() { 86 u32 top() const {
87 return stack[pos - 1]; 87 return stack[pos - 1];
88 } 88 }
89 89
@@ -112,11 +112,11 @@ public:
112 /** 112 /**
113 * Initialize the node states. 113 * Initialize the node states.
114 * 114 *
115 * @param buffer - The workbuffer to use. Unused. 115 * @param buffer_ - The workbuffer to use. Unused.
116 * @param node_buffer_size - The size of the workbuffer. Unused. 116 * @param node_buffer_size - The size of the workbuffer. Unused.
117 * @param count - The number of nodes in the graph. 117 * @param count - The number of nodes in the graph.
118 */ 118 */
119 void Initialize(std::span<u8> nodes, u64 node_buffer_size, u32 count); 119 void Initialize(std::span<u8> buffer_, u64 node_buffer_size, u32 count);
120 120
121 /** 121 /**
122 * Sort the graph. Only calls DepthFirstSearch. 122 * Sort the graph. Only calls DepthFirstSearch.
diff --git a/src/audio_core/renderer/performance/performance_manager.h b/src/audio_core/renderer/performance/performance_manager.h
index b82176bef..b65caa9b6 100644
--- a/src/audio_core/renderer/performance/performance_manager.h
+++ b/src/audio_core/renderer/performance/performance_manager.h
@@ -73,7 +73,8 @@ public:
73 * Calculate the required size for the performance workbuffer. 73 * Calculate the required size for the performance workbuffer.
74 * 74 *
75 * @param behavior - Check which version is supported. 75 * @param behavior - Check which version is supported.
76 * @param params - Input parameters. 76 * @param params - Input parameters.
77 *
77 * @return Required workbuffer size. 78 * @return Required workbuffer size.
78 */ 79 */
79 static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame( 80 static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame(
@@ -104,7 +105,7 @@ public:
104 * @param workbuffer - Workbuffer to use for performance frames. 105 * @param workbuffer - Workbuffer to use for performance frames.
105 * @param workbuffer_size - Size of the workbuffer. 106 * @param workbuffer_size - Size of the workbuffer.
106 * @param params - Input parameters. 107 * @param params - Input parameters.
107 * @param behavior - Behaviour to check version and data format. 108 * @param behavior - Behaviour to check version and data format.
108 * @param memory_pool - Used to translate the workbuffer address for the DSP. 109 * @param memory_pool - Used to translate the workbuffer address for the DSP.
109 */ 110 */
110 virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size, 111 virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size,
@@ -160,7 +161,8 @@ public:
160 * workbuffer, to be written by the AudioRenderer. 161 * workbuffer, to be written by the AudioRenderer.
161 * 162 *
162 * @param addresses - Filled with pointers to the new detail, which should be passed 163 * @param addresses - Filled with pointers to the new detail, which should be passed
163 * to the AudioRenderer with Performance commands to be written. 164 * to the AudioRenderer with Performance commands to be written.
165 * @param detail_type - Performance detail type.
164 * @param entry_type - The type of this detail. See PerformanceEntryType 166 * @param entry_type - The type of this detail. See PerformanceEntryType
165 * @param node_id - Node id for this detail. 167 * @param node_id - Node id for this detail.
166 * @return True if a new detail was created and the offsets are valid, otherwise false. 168 * @return True if a new detail was created and the offsets are valid, otherwise false.
diff --git a/src/audio_core/renderer/system_manager.cpp b/src/audio_core/renderer/system_manager.cpp
index b326819ed..9c1331e19 100644
--- a/src/audio_core/renderer/system_manager.cpp
+++ b/src/audio_core/renderer/system_manager.cpp
@@ -15,17 +15,14 @@ MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager",
15 MP_RGB(60, 19, 97)); 15 MP_RGB(60, 19, 97));
16 16
17namespace AudioCore::AudioRenderer { 17namespace AudioCore::AudioRenderer {
18constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL}; 18constexpr std::chrono::nanoseconds RENDER_TIME{5'000'000UL};
19constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL};
20 19
21SystemManager::SystemManager(Core::System& core_) 20SystemManager::SystemManager(Core::System& core_)
22 : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()}, 21 : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()},
23 thread_event{Core::Timing::CreateEvent( 22 thread_event{Core::Timing::CreateEvent(
24 "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) { 23 "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
25 return ThreadFunc2(time); 24 return ThreadFunc2(time);
26 })} { 25 })} {}
27 core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); });
28}
29 26
30SystemManager::~SystemManager() { 27SystemManager::~SystemManager() {
31 Stop(); 28 Stop();
@@ -36,8 +33,8 @@ bool SystemManager::InitializeUnsafe() {
36 if (adsp.Start()) { 33 if (adsp.Start()) {
37 active = true; 34 active = true;
38 thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); }); 35 thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); });
39 core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), 36 core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), RENDER_TIME,
40 BaseRenderTime - RenderTimeOffset, thread_event); 37 thread_event);
41 } 38 }
42 } 39 }
43 40
@@ -121,42 +118,9 @@ void SystemManager::ThreadFunc() {
121} 118}
122 119
123std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) { 120std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) {
124 std::optional<std::chrono::nanoseconds> new_schedule_time{std::nullopt};
125 const auto queue_size{core.AudioCore().GetStreamQueue()};
126 switch (state) {
127 case StreamState::Filling:
128 if (queue_size >= 5) {
129 new_schedule_time = BaseRenderTime;
130 state = StreamState::Steady;
131 }
132 break;
133 case StreamState::Steady:
134 if (queue_size <= 2) {
135 new_schedule_time = BaseRenderTime - RenderTimeOffset;
136 state = StreamState::Filling;
137 } else if (queue_size > 5) {
138 new_schedule_time = BaseRenderTime + RenderTimeOffset;
139 state = StreamState::Draining;
140 }
141 break;
142 case StreamState::Draining:
143 if (queue_size <= 5) {
144 new_schedule_time = BaseRenderTime;
145 state = StreamState::Steady;
146 }
147 break;
148 }
149
150 update.store(true); 121 update.store(true);
151 update.notify_all(); 122 update.notify_all();
152 return new_schedule_time; 123 return std::nullopt;
153}
154
155void SystemManager::PauseCallback(bool paused) {
156 if (paused && core.IsPoweredOn() && core.IsShuttingDown()) {
157 update.store(true);
158 update.notify_all();
159 }
160} 124}
161 125
162} // namespace AudioCore::AudioRenderer 126} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/system_manager.h b/src/audio_core/renderer/system_manager.h
index 1291e9e0e..81457a3a1 100644
--- a/src/audio_core/renderer/system_manager.h
+++ b/src/audio_core/renderer/system_manager.h
@@ -73,13 +73,6 @@ private:
73 */ 73 */
74 std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time); 74 std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time);
75 75
76 /**
77 * Callback from core timing when pausing, used to detect shutdowns and stop ThreadFunc.
78 *
79 * @param paused - Are we pausing or resuming?
80 */
81 void PauseCallback(bool paused);
82
83 enum class StreamState { 76 enum class StreamState {
84 Filling, 77 Filling,
85 Steady, 78 Steady,
@@ -106,8 +99,6 @@ private:
106 std::shared_ptr<Core::Timing::EventType> thread_event; 99 std::shared_ptr<Core::Timing::EventType> thread_event;
107 /// Atomic for main thread to wait on 100 /// Atomic for main thread to wait on
108 std::atomic<bool> update{}; 101 std::atomic<bool> update{};
109 /// Current state of the streams
110 StreamState state{StreamState::Filling};
111}; 102};
112 103
113} // namespace AudioCore::AudioRenderer 104} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.h b/src/audio_core/renderer/upsampler/upsampler_manager.h
index 70cd42b08..83c697c0c 100644
--- a/src/audio_core/renderer/upsampler/upsampler_manager.h
+++ b/src/audio_core/renderer/upsampler/upsampler_manager.h
@@ -27,7 +27,7 @@ public:
27 /** 27 /**
28 * Free the given upsampler. 28 * Free the given upsampler.
29 * 29 *
30 * @param The upsampler to be freed. 30 * @param info The upsampler to be freed.
31 */ 31 */
32 void Free(UpsamplerInfo* info); 32 void Free(UpsamplerInfo* info);
33 33
diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h
index 896723e0c..930180895 100644
--- a/src/audio_core/renderer/voice/voice_info.h
+++ b/src/audio_core/renderer/voice/voice_info.h
@@ -185,7 +185,8 @@ public:
185 /** 185 /**
186 * Does this voice ned an update? 186 * Does this voice ned an update?
187 * 187 *
188 * @param params - Input parametetrs to check matching. 188 * @param params - Input parameters to check matching.
189 *
189 * @return True if this voice needs an update, otherwise false. 190 * @return True if this voice needs an update, otherwise false.
190 */ 191 */
191 bool ShouldUpdateParameters(const InParameter& params) const; 192 bool ShouldUpdateParameters(const InParameter& params) const;
@@ -194,9 +195,9 @@ public:
194 * Update the parameters of this voice. 195 * Update the parameters of this voice.
195 * 196 *
196 * @param error_info - Output error code. 197 * @param error_info - Output error code.
197 * @param params - Input parametters to udpate from. 198 * @param params - Input parameters to update from.
198 * @param pool_mapper - Used to map buffers. 199 * @param pool_mapper - Used to map buffers.
199 * @param behavior - behavior to check supported features. 200 * @param behavior - behavior to check supported features.
200 */ 201 */
201 void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params, 202 void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
202 const PoolMapper& pool_mapper, const BehaviorInfo& behavior); 203 const PoolMapper& pool_mapper, const BehaviorInfo& behavior);
@@ -218,12 +219,12 @@ public:
218 /** 219 /**
219 * Update all wavebuffers. 220 * Update all wavebuffers.
220 * 221 *
221 * @param error_infos - Output 2D array of errors, 2 per wavebuffer. 222 * @param error_infos - Output 2D array of errors, 2 per wavebuffer.
222 * @param error_count - Number of errors provided. Unused. 223 * @param error_count - Number of errors provided. Unused.
223 * @param params - Input parametters to be used for the update. 224 * @param params - Input parameters to be used for the update.
224 * @param voice_states - The voice states for each channel in this voice to be updated. 225 * @param voice_states - The voice states for each channel in this voice to be updated.
225 * @param pool_mapper - Used to map the wavebuffers. 226 * @param pool_mapper - Used to map the wavebuffers.
226 * @param behavior - Used to check for supported features. 227 * @param behavior - Used to check for supported features.
227 */ 228 */
228 void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos, 229 void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
229 u32 error_count, const InParameter& params, 230 u32 error_count, const InParameter& params,
@@ -233,13 +234,13 @@ public:
233 /** 234 /**
234 * Update a wavebuffer. 235 * Update a wavebuffer.
235 * 236 *
236 * @param error_infos - Output array of errors. 237 * @param error_info - Output array of errors.
237 * @param wave_buffer - The wavebuffer to be updated. 238 * @param wave_buffer - The wavebuffer to be updated.
238 * @param wave_buffer_internal - Input parametters to be used for the update. 239 * @param wave_buffer_internal - Input parametters to be used for the update.
239 * @param sample_format - Sample format of the wavebuffer. 240 * @param sample_format - Sample format of the wavebuffer.
240 * @param valid - Is this wavebuffer valid? 241 * @param valid - Is this wavebuffer valid?
241 * @param pool_mapper - Used to map the wavebuffers. 242 * @param pool_mapper - Used to map the wavebuffers.
242 * @param behavior - Used to check for supported features. 243 * @param behavior - Used to check for supported features.
243 */ 244 */
244 void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer, 245 void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer,
245 const WaveBufferInternal& wave_buffer_internal, 246 const WaveBufferInternal& wave_buffer_internal,
@@ -276,7 +277,7 @@ public:
276 /** 277 /**
277 * Check if this voice has any mixing connections. 278 * Check if this voice has any mixing connections.
278 * 279 *
279 * @return True if this voice participes in mixing, otherwise false. 280 * @return True if this voice participates in mixing, otherwise false.
280 */ 281 */
281 bool HasAnyConnection() const; 282 bool HasAnyConnection() const;
282 283
@@ -301,7 +302,8 @@ public:
301 /** 302 /**
302 * Update this voice on command generation. 303 * Update this voice on command generation.
303 * 304 *
304 * @param voice_states - Voice states for these wavebuffers. 305 * @param voice_context - Voice context for these wavebuffers.
306 *
305 * @return True if this voice should be generated, otherwise false. 307 * @return True if this voice should be generated, otherwise false.
306 */ 308 */
307 bool UpdateForCommandGeneration(VoiceContext& voice_context); 309 bool UpdateForCommandGeneration(VoiceContext& voice_context);
diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp
index 90d049e8e..36b115ad6 100644
--- a/src/audio_core/sink/cubeb_sink.cpp
+++ b/src/audio_core/sink/cubeb_sink.cpp
@@ -1,21 +1,13 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <algorithm>
5#include <atomic>
6#include <span> 4#include <span>
5#include <vector>
7 6
8#include "audio_core/audio_core.h" 7#include "audio_core/common/common.h"
9#include "audio_core/audio_event.h"
10#include "audio_core/audio_manager.h"
11#include "audio_core/sink/cubeb_sink.h" 8#include "audio_core/sink/cubeb_sink.h"
12#include "audio_core/sink/sink_stream.h" 9#include "audio_core/sink/sink_stream.h"
13#include "common/assert.h"
14#include "common/fixed_point.h"
15#include "common/logging/log.h" 10#include "common/logging/log.h"
16#include "common/reader_writer_queue.h"
17#include "common/ring_buffer.h"
18#include "common/settings.h"
19#include "core/core.h" 11#include "core/core.h"
20 12
21#ifdef _WIN32 13#ifdef _WIN32
@@ -42,10 +34,10 @@ public:
42 * @param system_ - Core system. 34 * @param system_ - Core system.
43 * @param event - Event used only for audio renderer, signalled on buffer consume. 35 * @param event - Event used only for audio renderer, signalled on buffer consume.
44 */ 36 */
45 CubebSinkStream(cubeb* ctx_, const u32 device_channels_, const u32 system_channels_, 37 CubebSinkStream(cubeb* ctx_, u32 device_channels_, u32 system_channels_,
46 cubeb_devid output_device, cubeb_devid input_device, const std::string& name_, 38 cubeb_devid output_device, cubeb_devid input_device, const std::string& name_,
47 const StreamType type_, Core::System& system_) 39 StreamType type_, Core::System& system_)
48 : ctx{ctx_}, type{type_}, system{system_} { 40 : SinkStream(system_, type_), ctx{ctx_} {
49#ifdef _WIN32 41#ifdef _WIN32
50 CoInitializeEx(nullptr, COINIT_MULTITHREADED); 42 CoInitializeEx(nullptr, COINIT_MULTITHREADED);
51#endif 43#endif
@@ -79,12 +71,10 @@ public:
79 71
80 minimum_latency = std::max(minimum_latency, 256u); 72 minimum_latency = std::max(minimum_latency, 256u);
81 73
82 playing_buffer.consumed = true; 74 LOG_INFO(Service_Audio,
83 75 "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) "
84 LOG_DEBUG(Service_Audio, 76 "latency {}",
85 "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " 77 name, type, params.rate, params.channels, system_channels, minimum_latency);
86 "latency {}",
87 name, type, params.rate, params.channels, system_channels, minimum_latency);
88 78
89 auto init_error{0}; 79 auto init_error{0};
90 if (type == StreamType::In) { 80 if (type == StreamType::In) {
@@ -111,6 +101,8 @@ public:
111 ~CubebSinkStream() override { 101 ~CubebSinkStream() override {
112 LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name); 102 LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name);
113 103
104 Unstall();
105
114 if (!ctx) { 106 if (!ctx) {
115 return; 107 return;
116 } 108 }
@@ -136,21 +128,14 @@ public:
136 * @param resume - Set to true if this is resuming the stream a previously-active stream. 128 * @param resume - Set to true if this is resuming the stream a previously-active stream.
137 * Default false. 129 * Default false.
138 */ 130 */
139 void Start(const bool resume = false) override { 131 void Start(bool resume = false) override {
140 if (!ctx) { 132 if (!ctx || !paused) {
141 return; 133 return;
142 } 134 }
143 135
144 if (resume && was_playing) { 136 paused = false;
145 if (cubeb_stream_start(stream_backend) != CUBEB_OK) { 137 if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
146 LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); 138 LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
147 }
148 paused = false;
149 } else if (!resume) {
150 if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
151 LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
152 }
153 paused = false;
154 } 139 }
155 } 140 }
156 141
@@ -158,207 +143,20 @@ public:
158 * Stop the sink stream. 143 * Stop the sink stream.
159 */ 144 */
160 void Stop() override { 145 void Stop() override {
161 if (!ctx) { 146 Unstall();
147
148 if (!ctx || paused) {
162 return; 149 return;
163 } 150 }
164 151
152 paused = true;
165 if (cubeb_stream_stop(stream_backend) != CUBEB_OK) { 153 if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
166 LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); 154 LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
167 } 155 }
168
169 was_playing.store(!paused);
170 paused = true;
171 }
172
173 /**
174 * Append a new buffer and its samples to a waiting queue to play.
175 *
176 * @param buffer - Audio buffer information to be queued.
177 * @param samples - The s16 samples to be queue for playback.
178 */
179 void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override {
180 if (type == StreamType::In) {
181 queue.enqueue(buffer);
182 queued_buffers++;
183 } else {
184 constexpr s32 min{std::numeric_limits<s16>::min()};
185 constexpr s32 max{std::numeric_limits<s16>::max()};
186
187 auto yuzu_volume{Settings::Volume()};
188 if (yuzu_volume > 1.0f) {
189 yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume);
190 }
191 auto volume{system_volume * device_volume * yuzu_volume};
192
193 if (system_channels == 6 && device_channels == 2) {
194 // We're given 6 channels, but our device only outputs 2, so downmix.
195 constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
196
197 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
198 read_index += system_channels, write_index += device_channels) {
199 const auto left_sample{
200 ((Common::FixedPoint<49, 15>(
201 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
202 down_mix_coeff[0] +
203 samples[read_index + static_cast<u32>(Channels::Center)] *
204 down_mix_coeff[1] +
205 samples[read_index + static_cast<u32>(Channels::LFE)] *
206 down_mix_coeff[2] +
207 samples[read_index + static_cast<u32>(Channels::BackLeft)] *
208 down_mix_coeff[3]) *
209 volume)
210 .to_int()};
211
212 const auto right_sample{
213 ((Common::FixedPoint<49, 15>(
214 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
215 down_mix_coeff[0] +
216 samples[read_index + static_cast<u32>(Channels::Center)] *
217 down_mix_coeff[1] +
218 samples[read_index + static_cast<u32>(Channels::LFE)] *
219 down_mix_coeff[2] +
220 samples[read_index + static_cast<u32>(Channels::BackRight)] *
221 down_mix_coeff[3]) *
222 volume)
223 .to_int()};
224
225 samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
226 static_cast<s16>(std::clamp(left_sample, min, max));
227 samples[write_index + static_cast<u32>(Channels::FrontRight)] =
228 static_cast<s16>(std::clamp(right_sample, min, max));
229 }
230
231 samples.resize(samples.size() / system_channels * device_channels);
232
233 } else if (system_channels == 2 && device_channels == 6) {
234 // We need moar samples! Not all games will provide 6 channel audio.
235 // TODO: Implement some upmixing here. Currently just passthrough, with other
236 // channels left as silence.
237 std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
238
239 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
240 read_index += system_channels, write_index += device_channels) {
241 const auto left_sample{static_cast<s16>(std::clamp(
242 static_cast<s32>(
243 static_cast<f32>(
244 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
245 volume),
246 min, max))};
247
248 new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
249
250 const auto right_sample{static_cast<s16>(std::clamp(
251 static_cast<s32>(
252 static_cast<f32>(
253 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
254 volume),
255 min, max))};
256
257 new_samples[write_index + static_cast<u32>(Channels::FrontRight)] =
258 right_sample;
259 }
260 samples = std::move(new_samples);
261
262 } else if (volume != 1.0f) {
263 for (u32 i = 0; i < samples.size(); i++) {
264 samples[i] = static_cast<s16>(std::clamp(
265 static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
266 }
267 }
268
269 samples_buffer.Push(samples);
270 queue.enqueue(buffer);
271 queued_buffers++;
272 }
273 }
274
275 /**
276 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
277 *
278 * @param num_samples - Maximum number of samples to receive.
279 * @return Vector of recorded samples. May have fewer than num_samples.
280 */
281 std::vector<s16> ReleaseBuffer(const u64 num_samples) override {
282 static constexpr s32 min = std::numeric_limits<s16>::min();
283 static constexpr s32 max = std::numeric_limits<s16>::max();
284
285 auto samples{samples_buffer.Pop(num_samples)};
286
287 // TODO: Up-mix to 6 channels if the game expects it.
288 // For audio input this is unlikely to ever be the case though.
289
290 // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
291 // TODO: Play with this and find something that works better.
292 auto volume{system_volume * device_volume * 8};
293 for (u32 i = 0; i < samples.size(); i++) {
294 samples[i] = static_cast<s16>(
295 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
296 }
297
298 if (samples.size() < num_samples) {
299 samples.resize(num_samples, 0);
300 }
301 return samples;
302 }
303
304 /**
305 * Check if a certain buffer has been consumed (fully played).
306 *
307 * @param tag - Unique tag of a buffer to check for.
308 * @return True if the buffer has been played, otherwise false.
309 */
310 bool IsBufferConsumed(const u64 tag) override {
311 if (released_buffer.tag == 0) {
312 if (!released_buffers.try_dequeue(released_buffer)) {
313 return false;
314 }
315 }
316
317 if (released_buffer.tag == tag) {
318 released_buffer.tag = 0;
319 return true;
320 }
321 return false;
322 }
323
324 /**
325 * Empty out the buffer queue.
326 */
327 void ClearQueue() override {
328 samples_buffer.Pop();
329 while (queue.pop()) {
330 }
331 while (released_buffers.pop()) {
332 }
333 queued_buffers = 0;
334 released_buffer = {};
335 playing_buffer = {};
336 playing_buffer.consumed = true;
337 } 156 }
338 157
339private: 158private:
340 /** 159 /**
341 * Signal events back to the audio system that a buffer was played/can be filled.
342 *
343 * @param buffer - Consumed audio buffer to be released.
344 */
345 void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) {
346 auto& manager{system.AudioCore().GetAudioManager()};
347 switch (type) {
348 case StreamType::Out:
349 released_buffers.enqueue(buffer);
350 manager.SetEvent(Event::Type::AudioOutManager, true);
351 break;
352 case StreamType::In:
353 released_buffers.enqueue(buffer);
354 manager.SetEvent(Event::Type::AudioInManager, true);
355 break;
356 case StreamType::Render:
357 break;
358 }
359 }
360
361 /**
362 * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will 160 * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will
363 * provide samples to be copied (audio in). 161 * provide samples to be copied (audio in).
364 * 162 *
@@ -378,106 +176,15 @@ private:
378 176
379 const std::size_t num_channels = impl->GetDeviceChannels(); 177 const std::size_t num_channels = impl->GetDeviceChannels();
380 const std::size_t frame_size = num_channels; 178 const std::size_t frame_size = num_channels;
381 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
382 const std::size_t num_frames{static_cast<size_t>(num_frames_)}; 179 const std::size_t num_frames{static_cast<size_t>(num_frames_)};
383 size_t frames_written{0};
384 [[maybe_unused]] bool underrun{false};
385 180
386 if (impl->type == StreamType::In) { 181 if (impl->type == StreamType::In) {
387 // INPUT
388 std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff), 182 std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff),
389 num_frames * frame_size}; 183 num_frames * frame_size};
390 184 impl->ProcessAudioIn(input_buffer, num_frames);
391 while (frames_written < num_frames) {
392 auto& playing_buffer{impl->playing_buffer};
393
394 // If the playing buffer has been consumed or has no frames, we need a new one
395 if (playing_buffer.consumed || playing_buffer.frames == 0) {
396 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
397 // If no buffer was available we've underrun, just push the samples and
398 // continue.
399 underrun = true;
400 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
401 (num_frames - frames_written) * frame_size);
402 frames_written = num_frames;
403 continue;
404 } else {
405 // Successfully got a new buffer, mark the old one as consumed and signal.
406 impl->queued_buffers--;
407 impl->SignalEvent(impl->playing_buffer);
408 }
409 }
410
411 // Get the minimum frames available between the currently playing buffer, and the
412 // amount we have left to fill
413 size_t frames_available{
414 std::min(playing_buffer.frames - playing_buffer.frames_played,
415 num_frames - frames_written)};
416
417 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
418 frames_available * frame_size);
419
420 frames_written += frames_available;
421 playing_buffer.frames_played += frames_available;
422
423 // If that's all the frames in the current buffer, add its samples and mark it as
424 // consumed
425 if (playing_buffer.frames_played >= playing_buffer.frames) {
426 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
427 impl->playing_buffer.consumed = true;
428 }
429 }
430
431 std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size],
432 frame_size_bytes);
433 } else { 185 } else {
434 // OUTPUT
435 std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size}; 186 std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size};
436 187 impl->ProcessAudioOutAndRender(output_buffer, num_frames);
437 while (frames_written < num_frames) {
438 auto& playing_buffer{impl->playing_buffer};
439
440 // If the playing buffer has been consumed or has no frames, we need a new one
441 if (playing_buffer.consumed || playing_buffer.frames == 0) {
442 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
443 // If no buffer was available we've underrun, fill the remaining buffer with
444 // the last written frame and continue.
445 underrun = true;
446 for (size_t i = frames_written; i < num_frames; i++) {
447 std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0],
448 frame_size_bytes);
449 }
450 frames_written = num_frames;
451 continue;
452 } else {
453 // Successfully got a new buffer, mark the old one as consumed and signal.
454 impl->queued_buffers--;
455 impl->SignalEvent(impl->playing_buffer);
456 }
457 }
458
459 // Get the minimum frames available between the currently playing buffer, and the
460 // amount we have left to fill
461 size_t frames_available{
462 std::min(playing_buffer.frames - playing_buffer.frames_played,
463 num_frames - frames_written)};
464
465 impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size],
466 frames_available * frame_size);
467
468 frames_written += frames_available;
469 playing_buffer.frames_played += frames_available;
470
471 // If that's all the frames in the current buffer, add its samples and mark it as
472 // consumed
473 if (playing_buffer.frames_played >= playing_buffer.frames) {
474 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
475 impl->playing_buffer.consumed = true;
476 }
477 }
478
479 std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
480 frame_size_bytes);
481 } 188 }
482 189
483 return num_frames_; 190 return num_frames_;
@@ -490,32 +197,12 @@ private:
490 * @param user_data - Custom data pointer passed along, points to a CubebSinkStream. 197 * @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
491 * @param state - New state of the device. 198 * @param state - New state of the device.
492 */ 199 */
493 static void StateCallback([[maybe_unused]] cubeb_stream* stream, 200 static void StateCallback(cubeb_stream*, void*, cubeb_state) {}
494 [[maybe_unused]] void* user_data,
495 [[maybe_unused]] cubeb_state state) {}
496 201
497 /// Main Cubeb context 202 /// Main Cubeb context
498 cubeb* ctx{}; 203 cubeb* ctx{};
499 /// Cubeb stream backend 204 /// Cubeb stream backend
500 cubeb_stream* stream_backend{}; 205 cubeb_stream* stream_backend{};
501 /// Name of this stream
502 std::string name{};
503 /// Type of this stream
504 StreamType type;
505 /// Core system
506 Core::System& system;
507 /// Ring buffer of the samples waiting to be played or consumed
508 Common::RingBuffer<s16, 0x10000> samples_buffer;
509 /// Audio buffers queued and waiting to play
510 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue;
511 /// The currently-playing audio buffer
512 ::AudioCore::Sink::SinkBuffer playing_buffer{};
513 /// Audio buffers which have been played and are in queue to be released by the audio system
514 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{};
515 /// Currently released buffer waiting to be taken by the audio system
516 ::AudioCore::Sink::SinkBuffer released_buffer{};
517 /// The last played (or received) frame of audio, used when the callback underruns
518 std::array<s16, MaxChannels> last_frame{};
519}; 206};
520 207
521CubebSink::CubebSink(std::string_view target_device_name) { 208CubebSink::CubebSink(std::string_view target_device_name) {
@@ -569,15 +256,15 @@ CubebSink::~CubebSink() {
569#endif 256#endif
570} 257}
571 258
572SinkStream* CubebSink::AcquireSinkStream(Core::System& system, const u32 system_channels, 259SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels,
573 const std::string& name, const StreamType type) { 260 const std::string& name, StreamType type) {
574 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>( 261 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
575 ctx, device_channels, system_channels, output_device, input_device, name, type, system)); 262 ctx, device_channels, system_channels, output_device, input_device, name, type, system));
576 263
577 return stream.get(); 264 return stream.get();
578} 265}
579 266
580void CubebSink::CloseStream(const SinkStream* stream) { 267void CubebSink::CloseStream(SinkStream* stream) {
581 for (size_t i = 0; i < sink_streams.size(); i++) { 268 for (size_t i = 0; i < sink_streams.size(); i++) {
582 if (sink_streams[i].get() == stream) { 269 if (sink_streams[i].get() == stream) {
583 sink_streams[i].reset(); 270 sink_streams[i].reset();
@@ -591,18 +278,6 @@ void CubebSink::CloseStreams() {
591 sink_streams.clear(); 278 sink_streams.clear();
592} 279}
593 280
594void CubebSink::PauseStreams() {
595 for (auto& stream : sink_streams) {
596 stream->Stop();
597 }
598}
599
600void CubebSink::UnpauseStreams() {
601 for (auto& stream : sink_streams) {
602 stream->Start(true);
603 }
604}
605
606f32 CubebSink::GetDeviceVolume() const { 281f32 CubebSink::GetDeviceVolume() const {
607 if (sink_streams.empty()) { 282 if (sink_streams.empty()) {
608 return 1.0f; 283 return 1.0f;
@@ -611,19 +286,19 @@ f32 CubebSink::GetDeviceVolume() const {
611 return sink_streams[0]->GetDeviceVolume(); 286 return sink_streams[0]->GetDeviceVolume();
612} 287}
613 288
614void CubebSink::SetDeviceVolume(const f32 volume) { 289void CubebSink::SetDeviceVolume(f32 volume) {
615 for (auto& stream : sink_streams) { 290 for (auto& stream : sink_streams) {
616 stream->SetDeviceVolume(volume); 291 stream->SetDeviceVolume(volume);
617 } 292 }
618} 293}
619 294
620void CubebSink::SetSystemVolume(const f32 volume) { 295void CubebSink::SetSystemVolume(f32 volume) {
621 for (auto& stream : sink_streams) { 296 for (auto& stream : sink_streams) {
622 stream->SetSystemVolume(volume); 297 stream->SetSystemVolume(volume);
623 } 298 }
624} 299}
625 300
626std::vector<std::string> ListCubebSinkDevices(const bool capture) { 301std::vector<std::string> ListCubebSinkDevices(bool capture) {
627 std::vector<std::string> device_list; 302 std::vector<std::string> device_list;
628 cubeb* ctx; 303 cubeb* ctx;
629 304
diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h
index f0f43dfa1..4b0cb160d 100644
--- a/src/audio_core/sink/cubeb_sink.h
+++ b/src/audio_core/sink/cubeb_sink.h
@@ -34,8 +34,7 @@ public:
34 * May differ from the device's channel count. 34 * May differ from the device's channel count.
35 * @param name - Name of this stream. 35 * @param name - Name of this stream.
36 * @param type - Type of this stream, render/in/out. 36 * @param type - Type of this stream, render/in/out.
37 * @param event - Audio render only, a signal used to prevent the renderer running too 37 *
38 * fast.
39 * @return A pointer to the created SinkStream 38 * @return A pointer to the created SinkStream
40 */ 39 */
41 SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, 40 SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
@@ -46,7 +45,7 @@ public:
46 * 45 *
47 * @param stream - The stream to close. 46 * @param stream - The stream to close.
48 */ 47 */
49 void CloseStream(const SinkStream* stream) override; 48 void CloseStream(SinkStream* stream) override;
50 49
51 /** 50 /**
52 * Close all streams. 51 * Close all streams.
@@ -54,16 +53,6 @@ public:
54 void CloseStreams() override; 53 void CloseStreams() override;
55 54
56 /** 55 /**
57 * Pause all streams.
58 */
59 void PauseStreams() override;
60
61 /**
62 * Unpause all streams.
63 */
64 void UnpauseStreams() override;
65
66 /**
67 * Get the device volume. Set from calls to the IAudioDevice service. 56 * Get the device volume. Set from calls to the IAudioDevice service.
68 * 57 *
69 * @return Volume of the device. 58 * @return Volume of the device.
@@ -101,7 +90,7 @@ private:
101}; 90};
102 91
103/** 92/**
104 * Get a list of conencted devices from Cubeb. 93 * Get a list of connected devices from Cubeb.
105 * 94 *
106 * @param capture - Return input (capture) devices if true, otherwise output devices. 95 * @param capture - Return input (capture) devices if true, otherwise output devices.
107 */ 96 */
diff --git a/src/audio_core/sink/null_sink.h b/src/audio_core/sink/null_sink.h
index 47a342171..1215d3cd2 100644
--- a/src/audio_core/sink/null_sink.h
+++ b/src/audio_core/sink/null_sink.h
@@ -3,10 +3,29 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <string>
7#include <string_view>
8#include <vector>
9
6#include "audio_core/sink/sink.h" 10#include "audio_core/sink/sink.h"
7#include "audio_core/sink/sink_stream.h" 11#include "audio_core/sink/sink_stream.h"
8 12
13namespace Core {
14class System;
15} // namespace Core
16
9namespace AudioCore::Sink { 17namespace AudioCore::Sink {
18class NullSinkStreamImpl final : public SinkStream {
19public:
20 explicit NullSinkStreamImpl(Core::System& system_, StreamType type_)
21 : SinkStream{system_, type_} {}
22 ~NullSinkStreamImpl() override {}
23 void AppendBuffer(SinkBuffer&, std::vector<s16>&) override {}
24 std::vector<s16> ReleaseBuffer(u64) override {
25 return {};
26 }
27};
28
10/** 29/**
11 * A no-op sink for when no audio out is wanted. 30 * A no-op sink for when no audio out is wanted.
12 */ 31 */
@@ -15,17 +34,16 @@ public:
15 explicit NullSink(std::string_view) {} 34 explicit NullSink(std::string_view) {}
16 ~NullSink() override = default; 35 ~NullSink() override = default;
17 36
18 SinkStream* AcquireSinkStream([[maybe_unused]] Core::System& system, 37 SinkStream* AcquireSinkStream(Core::System& system, u32, const std::string&,
19 [[maybe_unused]] u32 system_channels, 38 StreamType type) override {
20 [[maybe_unused]] const std::string& name, 39 if (null_sink == nullptr) {
21 [[maybe_unused]] StreamType type) override { 40 null_sink = std::make_unique<NullSinkStreamImpl>(system, type);
22 return &null_sink_stream; 41 }
42 return null_sink.get();
23 } 43 }
24 44
25 void CloseStream([[maybe_unused]] const SinkStream* stream) override {} 45 void CloseStream(SinkStream*) override {}
26 void CloseStreams() override {} 46 void CloseStreams() override {}
27 void PauseStreams() override {}
28 void UnpauseStreams() override {}
29 f32 GetDeviceVolume() const override { 47 f32 GetDeviceVolume() const override {
30 return 1.0f; 48 return 1.0f;
31 } 49 }
@@ -33,20 +51,7 @@ public:
33 void SetSystemVolume(f32 volume) override {} 51 void SetSystemVolume(f32 volume) override {}
34 52
35private: 53private:
36 struct NullSinkStreamImpl final : SinkStream { 54 SinkStreamPtr null_sink{};
37 void Finalize() override {}
38 void Start(bool resume = false) override {}
39 void Stop() override {}
40 void AppendBuffer([[maybe_unused]] ::AudioCore::Sink::SinkBuffer& buffer,
41 [[maybe_unused]] std::vector<s16>& samples) override {}
42 std::vector<s16> ReleaseBuffer([[maybe_unused]] u64 num_samples) override {
43 return {};
44 }
45 bool IsBufferConsumed([[maybe_unused]] const u64 tag) {
46 return true;
47 }
48 void ClearQueue() override {}
49 } null_sink_stream;
50}; 55};
51 56
52} // namespace AudioCore::Sink 57} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp
index d6c9ec90d..1bd001b94 100644
--- a/src/audio_core/sink/sdl2_sink.cpp
+++ b/src/audio_core/sink/sdl2_sink.cpp
@@ -1,20 +1,13 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <algorithm> 4#include <span>
5#include <atomic> 5#include <vector>
6 6
7#include "audio_core/audio_core.h" 7#include "audio_core/common/common.h"
8#include "audio_core/audio_event.h"
9#include "audio_core/audio_manager.h"
10#include "audio_core/sink/sdl2_sink.h" 8#include "audio_core/sink/sdl2_sink.h"
11#include "audio_core/sink/sink_stream.h" 9#include "audio_core/sink/sink_stream.h"
12#include "common/assert.h"
13#include "common/fixed_point.h"
14#include "common/logging/log.h" 10#include "common/logging/log.h"
15#include "common/reader_writer_queue.h"
16#include "common/ring_buffer.h"
17#include "common/settings.h"
18#include "core/core.h" 11#include "core/core.h"
19 12
20// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 13// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307
@@ -44,10 +37,9 @@ public:
44 * @param system_ - Core system. 37 * @param system_ - Core system.
45 * @param event - Event used only for audio renderer, signalled on buffer consume. 38 * @param event - Event used only for audio renderer, signalled on buffer consume.
46 */ 39 */
47 SDLSinkStream(u32 device_channels_, const u32 system_channels_, 40 SDLSinkStream(u32 device_channels_, u32 system_channels_, const std::string& output_device,
48 const std::string& output_device, const std::string& input_device, 41 const std::string& input_device, StreamType type_, Core::System& system_)
49 const StreamType type_, Core::System& system_) 42 : SinkStream{system_, type_} {
50 : type{type_}, system{system_} {
51 system_channels = system_channels_; 43 system_channels = system_channels_;
52 device_channels = device_channels_; 44 device_channels = device_channels_;
53 45
@@ -63,8 +55,6 @@ public:
63 spec.callback = &SDLSinkStream::DataCallback; 55 spec.callback = &SDLSinkStream::DataCallback;
64 spec.userdata = this; 56 spec.userdata = this;
65 57
66 playing_buffer.consumed = true;
67
68 std::string device_name{output_device}; 58 std::string device_name{output_device};
69 bool capture{false}; 59 bool capture{false};
70 if (type == StreamType::In) { 60 if (type == StreamType::In) {
@@ -84,31 +74,30 @@ public:
84 return; 74 return;
85 } 75 }
86 76
87 LOG_DEBUG(Service_Audio, 77 LOG_INFO(Service_Audio,
88 "Opening sdl stream {} with: rate {} channels {} (system channels {}) " 78 "Opening SDL stream {} with: rate {} channels {} (system channels {}) "
89 " samples {}", 79 " samples {}",
90 device, obtained.freq, obtained.channels, system_channels, obtained.samples); 80 device, obtained.freq, obtained.channels, system_channels, obtained.samples);
91 } 81 }
92 82
93 /** 83 /**
94 * Destroy the sink stream. 84 * Destroy the sink stream.
95 */ 85 */
96 ~SDLSinkStream() override { 86 ~SDLSinkStream() override {
97 if (device == 0) { 87 LOG_DEBUG(Service_Audio, "Destructing SDL stream {}", name);
98 return; 88 Finalize();
99 }
100
101 SDL_CloseAudioDevice(device);
102 } 89 }
103 90
104 /** 91 /**
105 * Finalize the sink stream. 92 * Finalize the sink stream.
106 */ 93 */
107 void Finalize() override { 94 void Finalize() override {
95 Unstall();
108 if (device == 0) { 96 if (device == 0) {
109 return; 97 return;
110 } 98 }
111 99
100 Stop();
112 SDL_CloseAudioDevice(device); 101 SDL_CloseAudioDevice(device);
113 } 102 }
114 103
@@ -118,217 +107,29 @@ public:
118 * @param resume - Set to true if this is resuming the stream a previously-active stream. 107 * @param resume - Set to true if this is resuming the stream a previously-active stream.
119 * Default false. 108 * Default false.
120 */ 109 */
121 void Start(const bool resume = false) override { 110 void Start(bool resume = false) override {
122 if (device == 0) { 111 if (device == 0 || !paused) {
123 return; 112 return;
124 } 113 }
125 114
126 if (resume && was_playing) { 115 paused = false;
127 SDL_PauseAudioDevice(device, 0); 116 SDL_PauseAudioDevice(device, 0);
128 paused = false;
129 } else if (!resume) {
130 SDL_PauseAudioDevice(device, 0);
131 paused = false;
132 }
133 } 117 }
134 118
135 /** 119 /**
136 * Stop the sink stream. 120 * Stop the sink stream.
137 */ 121 */
138 void Stop() { 122 void Stop() override {
139 if (device == 0) { 123 Unstall();
124 if (device == 0 || paused) {
140 return; 125 return;
141 } 126 }
142 SDL_PauseAudioDevice(device, 1);
143 paused = true; 127 paused = true;
144 } 128 SDL_PauseAudioDevice(device, 1);
145
146 /**
147 * Append a new buffer and its samples to a waiting queue to play.
148 *
149 * @param buffer - Audio buffer information to be queued.
150 * @param samples - The s16 samples to be queue for playback.
151 */
152 void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override {
153 if (type == StreamType::In) {
154 queue.enqueue(buffer);
155 queued_buffers++;
156 } else {
157 constexpr s32 min = std::numeric_limits<s16>::min();
158 constexpr s32 max = std::numeric_limits<s16>::max();
159
160 auto yuzu_volume{Settings::Volume()};
161 auto volume{system_volume * device_volume * yuzu_volume};
162
163 if (system_channels == 6 && device_channels == 2) {
164 // We're given 6 channels, but our device only outputs 2, so downmix.
165 constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
166
167 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
168 read_index += system_channels, write_index += device_channels) {
169 const auto left_sample{
170 ((Common::FixedPoint<49, 15>(
171 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
172 down_mix_coeff[0] +
173 samples[read_index + static_cast<u32>(Channels::Center)] *
174 down_mix_coeff[1] +
175 samples[read_index + static_cast<u32>(Channels::LFE)] *
176 down_mix_coeff[2] +
177 samples[read_index + static_cast<u32>(Channels::BackLeft)] *
178 down_mix_coeff[3]) *
179 volume)
180 .to_int()};
181
182 const auto right_sample{
183 ((Common::FixedPoint<49, 15>(
184 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
185 down_mix_coeff[0] +
186 samples[read_index + static_cast<u32>(Channels::Center)] *
187 down_mix_coeff[1] +
188 samples[read_index + static_cast<u32>(Channels::LFE)] *
189 down_mix_coeff[2] +
190 samples[read_index + static_cast<u32>(Channels::BackRight)] *
191 down_mix_coeff[3]) *
192 volume)
193 .to_int()};
194
195 samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
196 static_cast<s16>(std::clamp(left_sample, min, max));
197 samples[write_index + static_cast<u32>(Channels::FrontRight)] =
198 static_cast<s16>(std::clamp(right_sample, min, max));
199 }
200
201 samples.resize(samples.size() / system_channels * device_channels);
202
203 } else if (system_channels == 2 && device_channels == 6) {
204 // We need moar samples! Not all games will provide 6 channel audio.
205 // TODO: Implement some upmixing here. Currently just passthrough, with other
206 // channels left as silence.
207 std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
208
209 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
210 read_index += system_channels, write_index += device_channels) {
211 const auto left_sample{static_cast<s16>(std::clamp(
212 static_cast<s32>(
213 static_cast<f32>(
214 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
215 volume),
216 min, max))};
217
218 new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
219
220 const auto right_sample{static_cast<s16>(std::clamp(
221 static_cast<s32>(
222 static_cast<f32>(
223 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
224 volume),
225 min, max))};
226
227 new_samples[write_index + static_cast<u32>(Channels::FrontRight)] =
228 right_sample;
229 }
230 samples = std::move(new_samples);
231
232 } else if (volume != 1.0f) {
233 for (u32 i = 0; i < samples.size(); i++) {
234 samples[i] = static_cast<s16>(std::clamp(
235 static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
236 }
237 }
238
239 samples_buffer.Push(samples);
240 queue.enqueue(buffer);
241 queued_buffers++;
242 }
243 }
244
245 /**
246 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
247 *
248 * @param num_samples - Maximum number of samples to receive.
249 * @return Vector of recorded samples. May have fewer than num_samples.
250 */
251 std::vector<s16> ReleaseBuffer(const u64 num_samples) override {
252 static constexpr s32 min = std::numeric_limits<s16>::min();
253 static constexpr s32 max = std::numeric_limits<s16>::max();
254
255 auto samples{samples_buffer.Pop(num_samples)};
256
257 // TODO: Up-mix to 6 channels if the game expects it.
258 // For audio input this is unlikely to ever be the case though.
259
260 // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
261 // TODO: Play with this and find something that works better.
262 auto volume{system_volume * device_volume * 8};
263 for (u32 i = 0; i < samples.size(); i++) {
264 samples[i] = static_cast<s16>(
265 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
266 }
267
268 if (samples.size() < num_samples) {
269 samples.resize(num_samples, 0);
270 }
271 return samples;
272 }
273
274 /**
275 * Check if a certain buffer has been consumed (fully played).
276 *
277 * @param tag - Unique tag of a buffer to check for.
278 * @return True if the buffer has been played, otherwise false.
279 */
280 bool IsBufferConsumed(const u64 tag) override {
281 if (released_buffer.tag == 0) {
282 if (!released_buffers.try_dequeue(released_buffer)) {
283 return false;
284 }
285 }
286
287 if (released_buffer.tag == tag) {
288 released_buffer.tag = 0;
289 return true;
290 }
291 return false;
292 }
293
294 /**
295 * Empty out the buffer queue.
296 */
297 void ClearQueue() override {
298 samples_buffer.Pop();
299 while (queue.pop()) {
300 }
301 while (released_buffers.pop()) {
302 }
303 released_buffer = {};
304 playing_buffer = {};
305 playing_buffer.consumed = true;
306 queued_buffers = 0;
307 } 129 }
308 130
309private: 131private:
310 /** 132 /**
311 * Signal events back to the audio system that a buffer was played/can be filled.
312 *
313 * @param buffer - Consumed audio buffer to be released.
314 */
315 void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) {
316 auto& manager{system.AudioCore().GetAudioManager()};
317 switch (type) {
318 case StreamType::Out:
319 released_buffers.enqueue(buffer);
320 manager.SetEvent(Event::Type::AudioOutManager, true);
321 break;
322 case StreamType::In:
323 released_buffers.enqueue(buffer);
324 manager.SetEvent(Event::Type::AudioInManager, true);
325 break;
326 case StreamType::Render:
327 break;
328 }
329 }
330
331 /**
332 * Main callback from SDL. Either expects samples from us (audio render/audio out), or will 133 * Main callback from SDL. Either expects samples from us (audio render/audio out), or will
333 * provide samples to be copied (audio in). 134 * provide samples to be copied (audio in).
334 * 135 *
@@ -345,122 +146,20 @@ private:
345 146
346 const std::size_t num_channels = impl->GetDeviceChannels(); 147 const std::size_t num_channels = impl->GetDeviceChannels();
347 const std::size_t frame_size = num_channels; 148 const std::size_t frame_size = num_channels;
348 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
349 const std::size_t num_frames{len / num_channels / sizeof(s16)}; 149 const std::size_t num_frames{len / num_channels / sizeof(s16)};
350 size_t frames_written{0};
351 [[maybe_unused]] bool underrun{false};
352 150
353 if (impl->type == StreamType::In) { 151 if (impl->type == StreamType::In) {
354 std::span<s16> input_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; 152 std::span<const s16> input_buffer{reinterpret_cast<const s16*>(stream),
355 153 num_frames * frame_size};
356 while (frames_written < num_frames) { 154 impl->ProcessAudioIn(input_buffer, num_frames);
357 auto& playing_buffer{impl->playing_buffer};
358
359 // If the playing buffer has been consumed or has no frames, we need a new one
360 if (playing_buffer.consumed || playing_buffer.frames == 0) {
361 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
362 // If no buffer was available we've underrun, just push the samples and
363 // continue.
364 underrun = true;
365 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
366 (num_frames - frames_written) * frame_size);
367 frames_written = num_frames;
368 continue;
369 } else {
370 impl->queued_buffers--;
371 impl->SignalEvent(impl->playing_buffer);
372 }
373 }
374
375 // Get the minimum frames available between the currently playing buffer, and the
376 // amount we have left to fill
377 size_t frames_available{
378 std::min(playing_buffer.frames - playing_buffer.frames_played,
379 num_frames - frames_written)};
380
381 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
382 frames_available * frame_size);
383
384 frames_written += frames_available;
385 playing_buffer.frames_played += frames_available;
386
387 // If that's all the frames in the current buffer, add its samples and mark it as
388 // consumed
389 if (playing_buffer.frames_played >= playing_buffer.frames) {
390 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
391 impl->playing_buffer.consumed = true;
392 }
393 }
394
395 std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size],
396 frame_size_bytes);
397 } else { 155 } else {
398 std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; 156 std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size};
399 157 impl->ProcessAudioOutAndRender(output_buffer, num_frames);
400 while (frames_written < num_frames) {
401 auto& playing_buffer{impl->playing_buffer};
402
403 // If the playing buffer has been consumed or has no frames, we need a new one
404 if (playing_buffer.consumed || playing_buffer.frames == 0) {
405 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
406 // If no buffer was available we've underrun, fill the remaining buffer with
407 // the last written frame and continue.
408 underrun = true;
409 for (size_t i = frames_written; i < num_frames; i++) {
410 std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0],
411 frame_size_bytes);
412 }
413 frames_written = num_frames;
414 continue;
415 } else {
416 impl->queued_buffers--;
417 impl->SignalEvent(impl->playing_buffer);
418 }
419 }
420
421 // Get the minimum frames available between the currently playing buffer, and the
422 // amount we have left to fill
423 size_t frames_available{
424 std::min(playing_buffer.frames - playing_buffer.frames_played,
425 num_frames - frames_written)};
426
427 impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size],
428 frames_available * frame_size);
429
430 frames_written += frames_available;
431 playing_buffer.frames_played += frames_available;
432
433 // If that's all the frames in the current buffer, add its samples and mark it as
434 // consumed
435 if (playing_buffer.frames_played >= playing_buffer.frames) {
436 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
437 impl->playing_buffer.consumed = true;
438 }
439 }
440
441 std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
442 frame_size_bytes);
443 } 158 }
444 } 159 }
445 160
446 /// SDL device id of the opened input/output device 161 /// SDL device id of the opened input/output device
447 SDL_AudioDeviceID device{}; 162 SDL_AudioDeviceID device{};
448 /// Type of this stream
449 StreamType type;
450 /// Core system
451 Core::System& system;
452 /// Ring buffer of the samples waiting to be played or consumed
453 Common::RingBuffer<s16, 0x10000> samples_buffer;
454 /// Audio buffers queued and waiting to play
455 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue;
456 /// The currently-playing audio buffer
457 ::AudioCore::Sink::SinkBuffer playing_buffer{};
458 /// Audio buffers which have been played and are in queue to be released by the audio system
459 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{};
460 /// Currently released buffer waiting to be taken by the audio system
461 ::AudioCore::Sink::SinkBuffer released_buffer{};
462 /// The last played (or received) frame of audio, used when the callback underruns
463 std::array<s16, MaxChannels> last_frame{};
464}; 163};
465 164
466SDLSink::SDLSink(std::string_view target_device_name) { 165SDLSink::SDLSink(std::string_view target_device_name) {
@@ -482,14 +181,14 @@ SDLSink::SDLSink(std::string_view target_device_name) {
482 181
483SDLSink::~SDLSink() = default; 182SDLSink::~SDLSink() = default;
484 183
485SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels, 184SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels,
486 const std::string&, const StreamType type) { 185 const std::string&, StreamType type) {
487 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>( 186 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
488 device_channels, system_channels, output_device, input_device, type, system)); 187 device_channels, system_channels, output_device, input_device, type, system));
489 return stream.get(); 188 return stream.get();
490} 189}
491 190
492void SDLSink::CloseStream(const SinkStream* stream) { 191void SDLSink::CloseStream(SinkStream* stream) {
493 for (size_t i = 0; i < sink_streams.size(); i++) { 192 for (size_t i = 0; i < sink_streams.size(); i++) {
494 if (sink_streams[i].get() == stream) { 193 if (sink_streams[i].get() == stream) {
495 sink_streams[i].reset(); 194 sink_streams[i].reset();
@@ -503,18 +202,6 @@ void SDLSink::CloseStreams() {
503 sink_streams.clear(); 202 sink_streams.clear();
504} 203}
505 204
506void SDLSink::PauseStreams() {
507 for (auto& stream : sink_streams) {
508 stream->Stop();
509 }
510}
511
512void SDLSink::UnpauseStreams() {
513 for (auto& stream : sink_streams) {
514 stream->Start();
515 }
516}
517
518f32 SDLSink::GetDeviceVolume() const { 205f32 SDLSink::GetDeviceVolume() const {
519 if (sink_streams.empty()) { 206 if (sink_streams.empty()) {
520 return 1.0f; 207 return 1.0f;
@@ -523,19 +210,19 @@ f32 SDLSink::GetDeviceVolume() const {
523 return sink_streams[0]->GetDeviceVolume(); 210 return sink_streams[0]->GetDeviceVolume();
524} 211}
525 212
526void SDLSink::SetDeviceVolume(const f32 volume) { 213void SDLSink::SetDeviceVolume(f32 volume) {
527 for (auto& stream : sink_streams) { 214 for (auto& stream : sink_streams) {
528 stream->SetDeviceVolume(volume); 215 stream->SetDeviceVolume(volume);
529 } 216 }
530} 217}
531 218
532void SDLSink::SetSystemVolume(const f32 volume) { 219void SDLSink::SetSystemVolume(f32 volume) {
533 for (auto& stream : sink_streams) { 220 for (auto& stream : sink_streams) {
534 stream->SetSystemVolume(volume); 221 stream->SetSystemVolume(volume);
535 } 222 }
536} 223}
537 224
538std::vector<std::string> ListSDLSinkDevices(const bool capture) { 225std::vector<std::string> ListSDLSinkDevices(bool capture) {
539 std::vector<std::string> device_list; 226 std::vector<std::string> device_list;
540 227
541 if (!SDL_WasInit(SDL_INIT_AUDIO)) { 228 if (!SDL_WasInit(SDL_INIT_AUDIO)) {
diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h
index 186bc2fa3..f01eddc1b 100644
--- a/src/audio_core/sink/sdl2_sink.h
+++ b/src/audio_core/sink/sdl2_sink.h
@@ -32,8 +32,7 @@ public:
32 * May differ from the device's channel count. 32 * May differ from the device's channel count.
33 * @param name - Name of this stream. 33 * @param name - Name of this stream.
34 * @param type - Type of this stream, render/in/out. 34 * @param type - Type of this stream, render/in/out.
35 * @param event - Audio render only, a signal used to prevent the renderer running too 35 *
36 * fast.
37 * @return A pointer to the created SinkStream 36 * @return A pointer to the created SinkStream
38 */ 37 */
39 SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, 38 SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
@@ -44,7 +43,7 @@ public:
44 * 43 *
45 * @param stream - The stream to close. 44 * @param stream - The stream to close.
46 */ 45 */
47 void CloseStream(const SinkStream* stream) override; 46 void CloseStream(SinkStream* stream) override;
48 47
49 /** 48 /**
50 * Close all streams. 49 * Close all streams.
@@ -52,16 +51,6 @@ public:
52 void CloseStreams() override; 51 void CloseStreams() override;
53 52
54 /** 53 /**
55 * Pause all streams.
56 */
57 void PauseStreams() override;
58
59 /**
60 * Unpause all streams.
61 */
62 void UnpauseStreams() override;
63
64 /**
65 * Get the device volume. Set from calls to the IAudioDevice service. 54 * Get the device volume. Set from calls to the IAudioDevice service.
66 * 55 *
67 * @return Volume of the device. 56 * @return Volume of the device.
@@ -92,7 +81,7 @@ private:
92}; 81};
93 82
94/** 83/**
95 * Get a list of conencted devices from Cubeb. 84 * Get a list of connected devices from SDL.
96 * 85 *
97 * @param capture - Return input (capture) devices if true, otherwise output devices. 86 * @param capture - Return input (capture) devices if true, otherwise output devices.
98 */ 87 */
diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h
index 91fe455e4..f28c6d126 100644
--- a/src/audio_core/sink/sink.h
+++ b/src/audio_core/sink/sink.h
@@ -32,7 +32,7 @@ public:
32 * 32 *
33 * @param stream - The stream to close. 33 * @param stream - The stream to close.
34 */ 34 */
35 virtual void CloseStream(const SinkStream* stream) = 0; 35 virtual void CloseStream(SinkStream* stream) = 0;
36 36
37 /** 37 /**
38 * Close all streams. 38 * Close all streams.
@@ -40,16 +40,6 @@ public:
40 virtual void CloseStreams() = 0; 40 virtual void CloseStreams() = 0;
41 41
42 /** 42 /**
43 * Pause all streams.
44 */
45 virtual void PauseStreams() = 0;
46
47 /**
48 * Unpause all streams.
49 */
50 virtual void UnpauseStreams() = 0;
51
52 /**
53 * Create a new sink stream, kept within this sink, with a pointer returned for use. 43 * Create a new sink stream, kept within this sink, with a pointer returned for use.
54 * Do not free the returned pointer. When done with the stream, call CloseStream on the sink. 44 * Do not free the returned pointer. When done with the stream, call CloseStream on the sink.
55 * 45 *
@@ -58,8 +48,7 @@ public:
58 * May differ from the device's channel count. 48 * May differ from the device's channel count.
59 * @param name - Name of this stream. 49 * @param name - Name of this stream.
60 * @param type - Type of this stream, render/in/out. 50 * @param type - Type of this stream, render/in/out.
61 * @param event - Audio render only, a signal used to prevent the renderer running too 51 *
62 * fast.
63 * @return A pointer to the created SinkStream 52 * @return A pointer to the created SinkStream
64 */ 53 */
65 virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, 54 virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp
index 253c0fd1e..67bdab779 100644
--- a/src/audio_core/sink/sink_details.cpp
+++ b/src/audio_core/sink/sink_details.cpp
@@ -5,7 +5,7 @@
5#include <memory> 5#include <memory>
6#include <string> 6#include <string>
7#include <vector> 7#include <vector>
8#include "audio_core/sink/null_sink.h" 8
9#include "audio_core/sink/sink_details.h" 9#include "audio_core/sink/sink_details.h"
10#ifdef HAVE_CUBEB 10#ifdef HAVE_CUBEB
11#include "audio_core/sink/cubeb_sink.h" 11#include "audio_core/sink/cubeb_sink.h"
@@ -13,6 +13,7 @@
13#ifdef HAVE_SDL2 13#ifdef HAVE_SDL2
14#include "audio_core/sink/sdl2_sink.h" 14#include "audio_core/sink/sdl2_sink.h"
15#endif 15#endif
16#include "audio_core/sink/null_sink.h"
16#include "common/logging/log.h" 17#include "common/logging/log.h"
17 18
18namespace AudioCore::Sink { 19namespace AudioCore::Sink {
@@ -59,8 +60,7 @@ const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) {
59 60
60 if (sink_id == "auto" || iter == std::end(sink_details)) { 61 if (sink_id == "auto" || iter == std::end(sink_details)) {
61 if (sink_id != "auto") { 62 if (sink_id != "auto") {
62 LOG_ERROR(Audio, "AudioCore::Sink::GetOutputSinkDetails given invalid sink_id {}", 63 LOG_ERROR(Audio, "Invalid sink_id {}", sink_id);
63 sink_id);
64 } 64 }
65 // Auto-select. 65 // Auto-select.
66 // sink_details is ordered in terms of desirability, with the best choice at the front. 66 // sink_details is ordered in terms of desirability, with the best choice at the front.
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp
new file mode 100644
index 000000000..849f862b0
--- /dev/null
+++ b/src/audio_core/sink/sink_stream.cpp
@@ -0,0 +1,284 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5#include <atomic>
6#include <memory>
7#include <span>
8#include <vector>
9
10#include "audio_core/audio_core.h"
11#include "audio_core/common/common.h"
12#include "audio_core/sink/sink_stream.h"
13#include "common/common_types.h"
14#include "common/fixed_point.h"
15#include "common/settings.h"
16#include "core/core.h"
17
18namespace AudioCore::Sink {
19
20void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
21 if (type == StreamType::In) {
22 queue.enqueue(buffer);
23 queued_buffers++;
24 return;
25 }
26
27 constexpr s32 min{std::numeric_limits<s16>::min()};
28 constexpr s32 max{std::numeric_limits<s16>::max()};
29
30 auto yuzu_volume{Settings::Volume()};
31 if (yuzu_volume > 1.0f) {
32 yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume);
33 }
34 auto volume{system_volume * device_volume * yuzu_volume};
35
36 if (system_channels == 6 && device_channels == 2) {
37 // We're given 6 channels, but our device only outputs 2, so downmix.
38 constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
39
40 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
41 read_index += system_channels, write_index += device_channels) {
42 const auto left_sample{
43 ((Common::FixedPoint<49, 15>(
44 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
45 down_mix_coeff[0] +
46 samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
47 samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
48 samples[read_index + static_cast<u32>(Channels::BackLeft)] * down_mix_coeff[3]) *
49 volume)
50 .to_int()};
51
52 const auto right_sample{
53 ((Common::FixedPoint<49, 15>(
54 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
55 down_mix_coeff[0] +
56 samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
57 samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
58 samples[read_index + static_cast<u32>(Channels::BackRight)] * down_mix_coeff[3]) *
59 volume)
60 .to_int()};
61
62 samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
63 static_cast<s16>(std::clamp(left_sample, min, max));
64 samples[write_index + static_cast<u32>(Channels::FrontRight)] =
65 static_cast<s16>(std::clamp(right_sample, min, max));
66 }
67
68 samples.resize(samples.size() / system_channels * device_channels);
69
70 } else if (system_channels == 2 && device_channels == 6) {
71 // We need moar samples! Not all games will provide 6 channel audio.
72 // TODO: Implement some upmixing here. Currently just passthrough, with other
73 // channels left as silence.
74 std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
75
76 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
77 read_index += system_channels, write_index += device_channels) {
78 const auto left_sample{static_cast<s16>(std::clamp(
79 static_cast<s32>(
80 static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
81 volume),
82 min, max))};
83
84 new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
85
86 const auto right_sample{static_cast<s16>(std::clamp(
87 static_cast<s32>(
88 static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
89 volume),
90 min, max))};
91
92 new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = right_sample;
93 }
94 samples = std::move(new_samples);
95
96 } else if (volume != 1.0f) {
97 for (u32 i = 0; i < samples.size(); i++) {
98 samples[i] = static_cast<s16>(
99 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
100 }
101 }
102
103 samples_buffer.Push(samples);
104 queue.enqueue(buffer);
105 queued_buffers++;
106}
107
108std::vector<s16> SinkStream::ReleaseBuffer(u64 num_samples) {
109 constexpr s32 min = std::numeric_limits<s16>::min();
110 constexpr s32 max = std::numeric_limits<s16>::max();
111
112 auto samples{samples_buffer.Pop(num_samples)};
113
114 // TODO: Up-mix to 6 channels if the game expects it.
115 // For audio input this is unlikely to ever be the case though.
116
117 // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
118 // TODO: Play with this and find something that works better.
119 auto volume{system_volume * device_volume * 8};
120 for (u32 i = 0; i < samples.size(); i++) {
121 samples[i] = static_cast<s16>(
122 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
123 }
124
125 if (samples.size() < num_samples) {
126 samples.resize(num_samples, 0);
127 }
128 return samples;
129}
130
131void SinkStream::ClearQueue() {
132 samples_buffer.Pop();
133 while (queue.pop()) {
134 }
135 queued_buffers = 0;
136 playing_buffer = {};
137 playing_buffer.consumed = true;
138}
139
140void SinkStream::ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames) {
141 const std::size_t num_channels = GetDeviceChannels();
142 const std::size_t frame_size = num_channels;
143 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
144 size_t frames_written{0};
145
146 // If we're paused or going to shut down, we don't want to consume buffers as coretiming is
147 // paused and we'll desync, so just return.
148 if (system.IsPaused() || system.IsShuttingDown()) {
149 return;
150 }
151
152 if (queued_buffers > max_queue_size) {
153 Stall();
154 }
155
156 while (frames_written < num_frames) {
157 // If the playing buffer has been consumed or has no frames, we need a new one
158 if (playing_buffer.consumed || playing_buffer.frames == 0) {
159 if (!queue.try_dequeue(playing_buffer)) {
160 // If no buffer was available we've underrun, just push the samples and
161 // continue.
162 samples_buffer.Push(&input_buffer[frames_written * frame_size],
163 (num_frames - frames_written) * frame_size);
164 frames_written = num_frames;
165 continue;
166 }
167 // Successfully dequeued a new buffer.
168 queued_buffers--;
169 }
170
171 // Get the minimum frames available between the currently playing buffer, and the
172 // amount we have left to fill
173 size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played,
174 num_frames - frames_written)};
175
176 samples_buffer.Push(&input_buffer[frames_written * frame_size],
177 frames_available * frame_size);
178
179 frames_written += frames_available;
180 playing_buffer.frames_played += frames_available;
181
182 // If that's all the frames in the current buffer, add its samples and mark it as
183 // consumed
184 if (playing_buffer.frames_played >= playing_buffer.frames) {
185 playing_buffer.consumed = true;
186 }
187 }
188
189 std::memcpy(&last_frame[0], &input_buffer[(frames_written - 1) * frame_size], frame_size_bytes);
190
191 if (queued_buffers <= max_queue_size) {
192 Unstall();
193 }
194}
195
196void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames) {
197 const std::size_t num_channels = GetDeviceChannels();
198 const std::size_t frame_size = num_channels;
199 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
200 size_t frames_written{0};
201
202 // If we're paused or going to shut down, we don't want to consume buffers as coretiming is
203 // paused and we'll desync, so just play silence.
204 if (system.IsPaused() || system.IsShuttingDown()) {
205 constexpr std::array<s16, 6> silence{};
206 for (size_t i = frames_written; i < num_frames; i++) {
207 std::memcpy(&output_buffer[i * frame_size], &silence[0], frame_size_bytes);
208 }
209 return;
210 }
211
212 // Due to many frames being queued up with nvdec (5 frames or so?), a lot of buffers also get
213 // queued up (30+) but not all at once, which causes constant stalling here, so just let the
214 // video play out without attempting to stall.
215 // Can hopefully remove this later with a more complete NVDEC implementation.
216 const auto nvdec_active{system.AudioCore().IsNVDECActive()};
217
218 // Core timing cannot be paused in single-core mode, so Stall ends up being called over and over
219 // and never recovers to a normal state, so just skip attempting to sync things on single-core.
220 if (system.IsMulticore() && !nvdec_active && queued_buffers > max_queue_size) {
221 Stall();
222 } else if (system.IsMulticore() && queued_buffers <= max_queue_size) {
223 Unstall();
224 }
225
226 while (frames_written < num_frames) {
227 // If the playing buffer has been consumed or has no frames, we need a new one
228 if (playing_buffer.consumed || playing_buffer.frames == 0) {
229 if (!queue.try_dequeue(playing_buffer)) {
230 // If no buffer was available we've underrun, fill the remaining buffer with
231 // the last written frame and continue.
232 for (size_t i = frames_written; i < num_frames; i++) {
233 std::memcpy(&output_buffer[i * frame_size], &last_frame[0], frame_size_bytes);
234 }
235 frames_written = num_frames;
236 continue;
237 }
238 // Successfully dequeued a new buffer.
239 queued_buffers--;
240 }
241
242 // Get the minimum frames available between the currently playing buffer, and the
243 // amount we have left to fill
244 size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played,
245 num_frames - frames_written)};
246
247 samples_buffer.Pop(&output_buffer[frames_written * frame_size],
248 frames_available * frame_size);
249
250 frames_written += frames_available;
251 playing_buffer.frames_played += frames_available;
252
253 // If that's all the frames in the current buffer, add its samples and mark it as
254 // consumed
255 if (playing_buffer.frames_played >= playing_buffer.frames) {
256 playing_buffer.consumed = true;
257 }
258 }
259
260 std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
261 frame_size_bytes);
262
263 if (system.IsMulticore() && queued_buffers <= max_queue_size) {
264 Unstall();
265 }
266}
267
268void SinkStream::Stall() {
269 if (stalled) {
270 return;
271 }
272 stalled = true;
273 system.StallProcesses();
274}
275
276void SinkStream::Unstall() {
277 if (!stalled) {
278 return;
279 }
280 system.UnstallProcesses();
281 stalled = false;
282}
283
284} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h
index 17ed6593f..38a4b2f51 100644
--- a/src/audio_core/sink/sink_stream.h
+++ b/src/audio_core/sink/sink_stream.h
@@ -3,12 +3,20 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <array>
6#include <atomic> 7#include <atomic>
7#include <memory> 8#include <memory>
9#include <span>
8#include <vector> 10#include <vector>
9 11
10#include "audio_core/common/common.h" 12#include "audio_core/common/common.h"
11#include "common/common_types.h" 13#include "common/common_types.h"
14#include "common/reader_writer_queue.h"
15#include "common/ring_buffer.h"
16
17namespace Core {
18class System;
19} // namespace Core
12 20
13namespace AudioCore::Sink { 21namespace AudioCore::Sink {
14 22
@@ -34,20 +42,24 @@ struct SinkBuffer {
34 * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer 42 * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer
35 * has been consumed. 43 * has been consumed.
36 * 44 *
37 * Since these are a FIFO queue, always check IsBufferConsumed in the same order you appended the 45 * Since these are a FIFO queue, IsBufferConsumed must be checked in the same order buffers were
38 * buffers, skipping a buffer will result in all following buffers to never release. 46 * appended, skipping a buffer will result in the queue getting stuck, and all following buffers to
47 * never release.
39 * 48 *
40 * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this 49 * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this
41 * is what games do), or call ClearQueue to flush all of the buffers without a full restart. 50 * is what games do), or call ClearQueue to flush all of the buffers without a full restart.
42 */ 51 */
43class SinkStream { 52class SinkStream {
44public: 53public:
45 virtual ~SinkStream() = default; 54 explicit SinkStream(Core::System& system_, StreamType type_) : system{system_}, type{type_} {}
55 virtual ~SinkStream() {
56 Unstall();
57 }
46 58
47 /** 59 /**
48 * Finalize the sink stream. 60 * Finalize the sink stream.
49 */ 61 */
50 virtual void Finalize() = 0; 62 virtual void Finalize() {}
51 63
52 /** 64 /**
53 * Start the sink stream. 65 * Start the sink stream.
@@ -55,48 +67,19 @@ public:
55 * @param resume - Set to true if this is resuming the stream a previously-active stream. 67 * @param resume - Set to true if this is resuming the stream a previously-active stream.
56 * Default false. 68 * Default false.
57 */ 69 */
58 virtual void Start(bool resume = false) = 0; 70 virtual void Start(bool resume = false) {}
59 71
60 /** 72 /**
61 * Stop the sink stream. 73 * Stop the sink stream.
62 */ 74 */
63 virtual void Stop() = 0; 75 virtual void Stop() {}
64
65 /**
66 * Append a new buffer and its samples to a waiting queue to play.
67 *
68 * @param buffer - Audio buffer information to be queued.
69 * @param samples - The s16 samples to be queue for playback.
70 */
71 virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) = 0;
72
73 /**
74 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
75 *
76 * @param num_samples - Maximum number of samples to receive.
77 * @return Vector of recorded samples. May have fewer than num_samples.
78 */
79 virtual std::vector<s16> ReleaseBuffer(u64 num_samples) = 0;
80
81 /**
82 * Check if a certain buffer has been consumed (fully played).
83 *
84 * @param tag - Unique tag of a buffer to check for.
85 * @return True if the buffer has been played, otherwise false.
86 */
87 virtual bool IsBufferConsumed(u64 tag) = 0;
88
89 /**
90 * Empty out the buffer queue.
91 */
92 virtual void ClearQueue() = 0;
93 76
94 /** 77 /**
95 * Check if the stream is paused. 78 * Check if the stream is paused.
96 * 79 *
97 * @return True if paused, otherwise false. 80 * @return True if paused, otherwise false.
98 */ 81 */
99 bool IsPaused() { 82 bool IsPaused() const {
100 return paused; 83 return paused;
101 } 84 }
102 85
@@ -128,34 +111,6 @@ public:
128 } 111 }
129 112
130 /** 113 /**
131 * Get the total number of samples played by this stream.
132 *
133 * @return Number of samples played.
134 */
135 u64 GetPlayedSampleCount() const {
136 return played_sample_count;
137 }
138
139 /**
140 * Set the number of samples played.
141 * This is started and stopped on system start/stop.
142 *
143 * @param played_sample_count_ - Number of samples to set.
144 */
145 void SetPlayedSampleCount(u64 played_sample_count_) {
146 played_sample_count = played_sample_count_;
147 }
148
149 /**
150 * Add to the played sample count.
151 *
152 * @param num_samples - Number of samples to add.
153 */
154 void AddPlayedSampleCount(u64 num_samples) {
155 played_sample_count += num_samples;
156 }
157
158 /**
159 * Get the system volume. 114 * Get the system volume.
160 * 115 *
161 * @return The current system volume. 116 * @return The current system volume.
@@ -196,27 +151,97 @@ public:
196 * 151 *
197 * @return The number of queued buffers. 152 * @return The number of queued buffers.
198 */ 153 */
199 u32 GetQueueSize() { 154 u32 GetQueueSize() const {
200 return queued_buffers.load(); 155 return queued_buffers.load();
201 } 156 }
202 157
158 /**
159 * Set the maximum buffer queue size.
160 */
161 void SetRingSize(u32 ring_size) {
162 max_queue_size = ring_size;
163 }
164
165 /**
166 * Append a new buffer and its samples to a waiting queue to play.
167 *
168 * @param buffer - Audio buffer information to be queued.
169 * @param samples - The s16 samples to be queue for playback.
170 */
171 virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples);
172
173 /**
174 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
175 *
176 * @param num_samples - Maximum number of samples to receive.
177 * @return Vector of recorded samples. May have fewer than num_samples.
178 */
179 virtual std::vector<s16> ReleaseBuffer(u64 num_samples);
180
181 /**
182 * Empty out the buffer queue.
183 */
184 void ClearQueue();
185
186 /**
187 * Callback for AudioIn.
188 *
189 * @param input_buffer - Input buffer to be filled with samples.
190 * @param num_frames - Number of frames to be filled.
191 */
192 void ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames);
193
194 /**
195 * Callback for AudioOut and AudioRenderer.
196 *
197 * @param output_buffer - Output buffer to be filled with samples.
198 * @param num_frames - Number of frames to be filled.
199 */
200 void ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames);
201
202 /**
203 * Stall core processes if the audio thread falls too far behind.
204 */
205 void Stall();
206
207 /**
208 * Unstall core processes.
209 */
210 void Unstall();
211
203protected: 212protected:
204 /// Number of buffers waiting to be played 213 /// Core system
205 std::atomic<u32> queued_buffers{}; 214 Core::System& system;
206 /// Total samples played by this stream 215 /// Type of this stream
207 std::atomic<u64> played_sample_count{}; 216 StreamType type;
208 /// Set by the audio render/in/out system which uses this stream 217 /// Set by the audio render/in/out system which uses this stream
209 f32 system_volume{1.0f};
210 /// Set via IAudioDevice service calls
211 f32 device_volume{1.0f};
212 /// Set by the audio render/in/out systen which uses this stream
213 u32 system_channels{2}; 218 u32 system_channels{2};
214 /// Channels supported by hardware 219 /// Channels supported by hardware
215 u32 device_channels{2}; 220 u32 device_channels{2};
216 /// Is this stream currently paused? 221 /// Is this stream currently paused?
217 std::atomic<bool> paused{true}; 222 std::atomic<bool> paused{true};
218 /// Was this stream previously playing? 223 /// Name of this stream
219 std::atomic<bool> was_playing{false}; 224 std::string name{};
225
226private:
227 /// Ring buffer of the samples waiting to be played or consumed
228 Common::RingBuffer<s16, 0x10000> samples_buffer;
229 /// Audio buffers queued and waiting to play
230 Common::ReaderWriterQueue<SinkBuffer> queue;
231 /// The currently-playing audio buffer
232 SinkBuffer playing_buffer{};
233 /// The last played (or received) frame of audio, used when the callback underruns
234 std::array<s16, MaxChannels> last_frame{};
235 /// Number of buffers waiting to be played
236 std::atomic<u32> queued_buffers{};
237 /// The ring size for audio out buffers (usually 4, rarely 2 or 8)
238 u32 max_queue_size{};
239 /// Set by the audio render/in/out system which uses this stream
240 f32 system_volume{1.0f};
241 /// Set via IAudioDevice service calls
242 f32 device_volume{1.0f};
243 /// True if coretiming has been stalled
244 bool stalled{false};
220}; 245};
221 246
222using SinkStreamPtr = std::unique_ptr<SinkStream>; 247using SinkStreamPtr = std::unique_ptr<SinkStream>;
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 635fb85c8..3447fabd8 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -14,32 +14,7 @@ if (DEFINED ENV{DISPLAYVERSION})
14 set(DISPLAY_VERSION $ENV{DISPLAYVERSION}) 14 set(DISPLAY_VERSION $ENV{DISPLAYVERSION})
15endif () 15endif ()
16 16
17# Pass the path to git to the GenerateSCMRev.cmake as well 17include(GenerateSCMRev)
18find_package(Git QUIET)
19
20add_custom_command(OUTPUT scm_rev.cpp
21 COMMAND ${CMAKE_COMMAND}
22 -DSRC_DIR=${CMAKE_SOURCE_DIR}
23 -DBUILD_REPOSITORY=${BUILD_REPOSITORY}
24 -DTITLE_BAR_FORMAT_IDLE=${TITLE_BAR_FORMAT_IDLE}
25 -DTITLE_BAR_FORMAT_RUNNING=${TITLE_BAR_FORMAT_RUNNING}
26 -DBUILD_TAG=${BUILD_TAG}
27 -DBUILD_ID=${DISPLAY_VERSION}
28 -DGIT_REF_SPEC=${GIT_REF_SPEC}
29 -DGIT_REV=${GIT_REV}
30 -DGIT_DESC=${GIT_DESC}
31 -DGIT_BRANCH=${GIT_BRANCH}
32 -DBUILD_FULLNAME=${BUILD_FULLNAME}
33 -DGIT_EXECUTABLE=${GIT_EXECUTABLE}
34 -P ${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake
35 DEPENDS
36 # Check that the scm_rev files haven't changed
37 "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in"
38 "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h"
39 # technically we should regenerate if the git version changed, but its not worth the effort imo
40 "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
41 VERBATIM
42)
43 18
44add_library(common STATIC 19add_library(common STATIC
45 algorithm.h 20 algorithm.h
@@ -117,7 +92,7 @@ add_library(common STATIC
117 quaternion.h 92 quaternion.h
118 reader_writer_queue.h 93 reader_writer_queue.h
119 ring_buffer.h 94 ring_buffer.h
120 scm_rev.cpp 95 ${CMAKE_CURRENT_BINARY_DIR}/scm_rev.cpp
121 scm_rev.h 96 scm_rev.h
122 scope_exit.h 97 scope_exit.h
123 settings.cpp 98 settings.cpp
@@ -166,6 +141,7 @@ if(ARCHITECTURE_x86_64)
166 x64/xbyak_abi.h 141 x64/xbyak_abi.h
167 x64/xbyak_util.h 142 x64/xbyak_util.h
168 ) 143 )
144 target_link_libraries(common PRIVATE xbyak)
169endif() 145endif()
170 146
171if (MSVC) 147if (MSVC)
@@ -189,7 +165,7 @@ endif()
189create_target_directory_groups(common) 165create_target_directory_groups(common)
190 166
191target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads) 167target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads)
192target_link_libraries(common PRIVATE lz4::lz4 xbyak) 168target_link_libraries(common PRIVATE lz4::lz4)
193if (TARGET zstd::zstd) 169if (TARGET zstd::zstd)
194 target_link_libraries(common PRIVATE zstd::zstd) 170 target_link_libraries(common PRIVATE zstd::zstd)
195else() 171else()
diff --git a/src/common/input.h b/src/common/input.h
index 213aa2384..825b0d650 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -102,6 +102,8 @@ struct AnalogProperties {
102 float offset{}; 102 float offset{};
103 // Invert direction of the sensor data 103 // Invert direction of the sensor data
104 bool inverted{}; 104 bool inverted{};
105 // Press once to activate, press again to release
106 bool toggle{};
105}; 107};
106 108
107// Single analog sensor data 109// Single analog sensor data
@@ -115,8 +117,11 @@ struct AnalogStatus {
115struct ButtonStatus { 117struct ButtonStatus {
116 Common::UUID uuid{}; 118 Common::UUID uuid{};
117 bool value{}; 119 bool value{};
120 // Invert value of the button
118 bool inverted{}; 121 bool inverted{};
122 // Press once to activate, press again to release
119 bool toggle{}; 123 bool toggle{};
124 // Internal lock for the toggle status
120 bool locked{}; 125 bool locked{};
121}; 126};
122 127
diff --git a/src/common/settings.h b/src/common/settings.h
index 13651de57..d2452c93b 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -530,6 +530,8 @@ struct Values {
530 Setting<bool> use_debug_asserts{false, "use_debug_asserts"}; 530 Setting<bool> use_debug_asserts{false, "use_debug_asserts"};
531 Setting<bool> use_auto_stub{false, "use_auto_stub"}; 531 Setting<bool> use_auto_stub{false, "use_auto_stub"};
532 Setting<bool> enable_all_controllers{false, "enable_all_controllers"}; 532 Setting<bool> enable_all_controllers{false, "enable_all_controllers"};
533 Setting<bool> create_crash_dumps{false, "create_crash_dumps"};
534 Setting<bool> perform_vulkan_check{true, "perform_vulkan_check"};
533 535
534 // Miscellaneous 536 // Miscellaneous
535 Setting<std::string> log_filter{"*:Info", "log_filter"}; 537 Setting<std::string> log_filter{"*:Info", "log_filter"};
diff --git a/src/common/thread.h b/src/common/thread.h
index 1552f58e0..e17a7850f 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -54,6 +54,10 @@ public:
54 is_set = false; 54 is_set = false;
55 } 55 }
56 56
57 [[nodiscard]] bool IsSet() {
58 return is_set;
59 }
60
57private: 61private:
58 std::condition_variable condvar; 62 std::condition_variable condvar;
59 std::mutex mutex; 63 std::mutex mutex;
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 52017878c..81391f513 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -4,12 +4,6 @@
4add_library(core STATIC 4add_library(core STATIC
5 arm/arm_interface.h 5 arm/arm_interface.h
6 arm/arm_interface.cpp 6 arm/arm_interface.cpp
7 arm/dynarmic/arm_dynarmic_32.cpp
8 arm/dynarmic/arm_dynarmic_32.h
9 arm/dynarmic/arm_dynarmic_64.cpp
10 arm/dynarmic/arm_dynarmic_64.h
11 arm/dynarmic/arm_dynarmic_cp15.cpp
12 arm/dynarmic/arm_dynarmic_cp15.h
13 arm/dynarmic/arm_exclusive_monitor.cpp 7 arm/dynarmic/arm_exclusive_monitor.cpp
14 arm/dynarmic/arm_exclusive_monitor.h 8 arm/dynarmic/arm_exclusive_monitor.h
15 arm/exclusive_monitor.cpp 9 arm/exclusive_monitor.cpp
@@ -466,6 +460,8 @@ add_library(core STATIC
466 hle/service/hid/controllers/mouse.h 460 hle/service/hid/controllers/mouse.h
467 hle/service/hid/controllers/npad.cpp 461 hle/service/hid/controllers/npad.cpp
468 hle/service/hid/controllers/npad.h 462 hle/service/hid/controllers/npad.h
463 hle/service/hid/controllers/palma.cpp
464 hle/service/hid/controllers/palma.h
469 hle/service/hid/controllers/stubbed.cpp 465 hle/service/hid/controllers/stubbed.cpp
470 hle/service/hid/controllers/stubbed.h 466 hle/service/hid/controllers/stubbed.h
471 hle/service/hid/controllers/touchscreen.cpp 467 hle/service/hid/controllers/touchscreen.cpp
@@ -527,6 +523,9 @@ add_library(core STATIC
527 hle/service/ncm/ncm.h 523 hle/service/ncm/ncm.h
528 hle/service/nfc/nfc.cpp 524 hle/service/nfc/nfc.cpp
529 hle/service/nfc/nfc.h 525 hle/service/nfc/nfc.h
526 hle/service/nfp/amiibo_crypto.cpp
527 hle/service/nfp/amiibo_crypto.h
528 hle/service/nfp/amiibo_types.h
530 hle/service/nfp/nfp.cpp 529 hle/service/nfp/nfp.cpp
531 hle/service/nfp/nfp.h 530 hle/service/nfp/nfp.h
532 hle/service/nfp/nfp_user.cpp 531 hle/service/nfp/nfp_user.cpp
diff --git a/src/core/core.cpp b/src/core/core.cpp
index e651ce100..121092868 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -141,8 +141,6 @@ struct System::Impl {
141 core_timing.SyncPause(false); 141 core_timing.SyncPause(false);
142 is_paused = false; 142 is_paused = false;
143 143
144 audio_core->PauseSinks(false);
145
146 return status; 144 return status;
147 } 145 }
148 146
@@ -150,8 +148,6 @@ struct System::Impl {
150 std::unique_lock<std::mutex> lk(suspend_guard); 148 std::unique_lock<std::mutex> lk(suspend_guard);
151 status = SystemResultStatus::Success; 149 status = SystemResultStatus::Success;
152 150
153 audio_core->PauseSinks(true);
154
155 core_timing.SyncPause(true); 151 core_timing.SyncPause(true);
156 kernel.Suspend(true); 152 kernel.Suspend(true);
157 is_paused = true; 153 is_paused = true;
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index 2dbb99c8b..f6c4567ba 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -73,7 +73,6 @@ void CoreTiming::Shutdown() {
73 if (timer_thread) { 73 if (timer_thread) {
74 timer_thread->join(); 74 timer_thread->join();
75 } 75 }
76 pause_callbacks.clear();
77 ClearPendingEvents(); 76 ClearPendingEvents();
78 timer_thread.reset(); 77 timer_thread.reset();
79 has_started = false; 78 has_started = false;
@@ -86,10 +85,6 @@ void CoreTiming::Pause(bool is_paused) {
86 if (!is_paused) { 85 if (!is_paused) {
87 pause_end_time = GetGlobalTimeNs().count(); 86 pause_end_time = GetGlobalTimeNs().count();
88 } 87 }
89
90 for (auto& cb : pause_callbacks) {
91 cb(is_paused);
92 }
93} 88}
94 89
95void CoreTiming::SyncPause(bool is_paused) { 90void CoreTiming::SyncPause(bool is_paused) {
@@ -110,10 +105,6 @@ void CoreTiming::SyncPause(bool is_paused) {
110 if (!is_paused) { 105 if (!is_paused) {
111 pause_end_time = GetGlobalTimeNs().count(); 106 pause_end_time = GetGlobalTimeNs().count();
112 } 107 }
113
114 for (auto& cb : pause_callbacks) {
115 cb(is_paused);
116 }
117} 108}
118 109
119bool CoreTiming::IsRunning() const { 110bool CoreTiming::IsRunning() const {
@@ -143,13 +134,17 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time,
143 std::chrono::nanoseconds resched_time, 134 std::chrono::nanoseconds resched_time,
144 const std::shared_ptr<EventType>& event_type, 135 const std::shared_ptr<EventType>& event_type,
145 std::uintptr_t user_data, bool absolute_time) { 136 std::uintptr_t user_data, bool absolute_time) {
146 std::scoped_lock scope{basic_lock}; 137 {
147 const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time}; 138 std::scoped_lock scope{basic_lock};
139 const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time};
140
141 event_queue.emplace_back(
142 Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()});
148 143
149 event_queue.emplace_back( 144 std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
150 Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()}); 145 }
151 146
152 std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); 147 event.Set();
153} 148}
154 149
155void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, 150void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type,
@@ -219,11 +214,6 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
219 } 214 }
220} 215}
221 216
222void CoreTiming::RegisterPauseCallback(PauseCallback&& callback) {
223 std::scoped_lock lock{basic_lock};
224 pause_callbacks.emplace_back(std::move(callback));
225}
226
227std::optional<s64> CoreTiming::Advance() { 217std::optional<s64> CoreTiming::Advance() {
228 std::scoped_lock lock{advance_lock, basic_lock}; 218 std::scoped_lock lock{advance_lock, basic_lock};
229 global_timer = GetGlobalTimeNs().count(); 219 global_timer = GetGlobalTimeNs().count();
@@ -243,17 +233,17 @@ std::optional<s64> CoreTiming::Advance() {
243 basic_lock.lock(); 233 basic_lock.lock();
244 234
245 if (evt.reschedule_time != 0) { 235 if (evt.reschedule_time != 0) {
236 const auto next_schedule_time{new_schedule_time.has_value()
237 ? new_schedule_time.value().count()
238 : evt.reschedule_time};
239
246 // If this event was scheduled into a pause, its time now is going to be way behind. 240 // If this event was scheduled into a pause, its time now is going to be way behind.
247 // Re-set this event to continue from the end of the pause. 241 // Re-set this event to continue from the end of the pause.
248 auto next_time{evt.time + evt.reschedule_time}; 242 auto next_time{evt.time + next_schedule_time};
249 if (evt.time < pause_end_time) { 243 if (evt.time < pause_end_time) {
250 next_time = pause_end_time + evt.reschedule_time; 244 next_time = pause_end_time + next_schedule_time;
251 } 245 }
252 246
253 const auto next_schedule_time{new_schedule_time.has_value()
254 ? new_schedule_time.value().count()
255 : evt.reschedule_time};
256
257 event_queue.emplace_back( 247 event_queue.emplace_back(
258 Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time}); 248 Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time});
259 std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); 249 std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
@@ -264,8 +254,7 @@ std::optional<s64> CoreTiming::Advance() {
264 } 254 }
265 255
266 if (!event_queue.empty()) { 256 if (!event_queue.empty()) {
267 const s64 next_time = event_queue.front().time - global_timer; 257 return event_queue.front().time;
268 return next_time;
269 } else { 258 } else {
270 return std::nullopt; 259 return std::nullopt;
271 } 260 }
@@ -278,11 +267,29 @@ void CoreTiming::ThreadLoop() {
278 paused_set = false; 267 paused_set = false;
279 const auto next_time = Advance(); 268 const auto next_time = Advance();
280 if (next_time) { 269 if (next_time) {
281 if (*next_time > 0) { 270 // There are more events left in the queue, wait until the next event.
282 std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time); 271 const auto wait_time = *next_time - GetGlobalTimeNs().count();
283 event.WaitFor(next_time_ns); 272 if (wait_time > 0) {
273 // Assume a timer resolution of 1ms.
274 static constexpr s64 TimerResolutionNS = 1000000;
275
276 // Sleep in discrete intervals of the timer resolution, and spin the rest.
277 const auto sleep_time = wait_time - (wait_time % TimerResolutionNS);
278 if (sleep_time > 0) {
279 event.WaitFor(std::chrono::nanoseconds(sleep_time));
280 }
281
282 while (!paused && !event.IsSet() && GetGlobalTimeNs().count() < *next_time) {
283 // Yield to reduce thread starvation.
284 std::this_thread::yield();
285 }
286
287 if (event.IsSet()) {
288 event.Reset();
289 }
284 } 290 }
285 } else { 291 } else {
292 // Queue is empty, wait until another event is scheduled and signals us to continue.
286 wait_set = true; 293 wait_set = true;
287 event.Wait(); 294 event.Wait();
288 } 295 }
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index 6aa3ae923..3259397b2 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -22,7 +22,6 @@ namespace Core::Timing {
22/// A callback that may be scheduled for a particular core timing event. 22/// A callback that may be scheduled for a particular core timing event.
23using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>( 23using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>(
24 std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>; 24 std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>;
25using PauseCallback = std::function<void(bool paused)>;
26 25
27/// Contains the characteristics of a particular event. 26/// Contains the characteristics of a particular event.
28struct EventType { 27struct EventType {
@@ -134,9 +133,6 @@ public:
134 /// Checks for events manually and returns time in nanoseconds for next event, threadsafe. 133 /// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
135 std::optional<s64> Advance(); 134 std::optional<s64> Advance();
136 135
137 /// Register a callback function to be called when coretiming pauses.
138 void RegisterPauseCallback(PauseCallback&& callback);
139
140private: 136private:
141 struct Event; 137 struct Event;
142 138
@@ -176,8 +172,6 @@ private:
176 /// Cycle timing 172 /// Cycle timing
177 u64 ticks{}; 173 u64 ticks{};
178 s64 downcount{}; 174 s64 downcount{};
179
180 std::vector<PauseCallback> pause_callbacks{};
181}; 175};
182 176
183/// Creates a core timing event with the given name and callback. 177/// Creates a core timing event with the given name and callback.
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index f9f902c2d..2cff279b1 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -93,7 +93,7 @@ void EmulatedController::ReloadFromSettings() {
93 .body = GetNpadColor(player.body_color_left), 93 .body = GetNpadColor(player.body_color_left),
94 .button = GetNpadColor(player.button_color_left), 94 .button = GetNpadColor(player.button_color_left),
95 }; 95 };
96 controller.colors_state.left = { 96 controller.colors_state.right = {
97 .body = GetNpadColor(player.body_color_right), 97 .body = GetNpadColor(player.body_color_right),
98 .button = GetNpadColor(player.button_color_right), 98 .button = GetNpadColor(player.button_color_right),
99 }; 99 };
@@ -562,6 +562,16 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
562 return; 562 return;
563 } 563 }
564 564
565 // GC controllers have triggers not buttons
566 if (npad_type == NpadStyleIndex::GameCube) {
567 if (index == Settings::NativeButton::ZR) {
568 return;
569 }
570 if (index == Settings::NativeButton::ZL) {
571 return;
572 }
573 }
574
565 switch (index) { 575 switch (index) {
566 case Settings::NativeButton::A: 576 case Settings::NativeButton::A:
567 controller.npad_button_state.a.Assign(current_status.value); 577 controller.npad_button_state.a.Assign(current_status.value);
@@ -738,6 +748,11 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac
738 return; 748 return;
739 } 749 }
740 750
751 // Only GC controllers have analog triggers
752 if (npad_type != NpadStyleIndex::GameCube) {
753 return;
754 }
755
741 const auto& trigger = controller.trigger_values[index]; 756 const auto& trigger = controller.trigger_values[index];
742 757
743 switch (index) { 758 switch (index) {
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp
index 68d143a01..52fb69e9c 100644
--- a/src/core/hid/input_converter.cpp
+++ b/src/core/hid/input_converter.cpp
@@ -52,6 +52,9 @@ Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatu
52 Common::Input::ButtonStatus status{}; 52 Common::Input::ButtonStatus status{};
53 switch (callback.type) { 53 switch (callback.type) {
54 case Common::Input::InputType::Analog: 54 case Common::Input::InputType::Analog:
55 status.value = TransformToTrigger(callback).pressed.value;
56 status.toggle = callback.analog_status.properties.toggle;
57 break;
55 case Common::Input::InputType::Trigger: 58 case Common::Input::InputType::Trigger:
56 status.value = TransformToTrigger(callback).pressed.value; 59 status.value = TransformToTrigger(callback).pressed.value;
57 break; 60 break;
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 4de44cd06..47a1b829b 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -117,6 +117,7 @@ union Result {
117 BitField<0, 9, ErrorModule> module; 117 BitField<0, 9, ErrorModule> module;
118 BitField<9, 13, u32> description; 118 BitField<9, 13, u32> description;
119 119
120 Result() = default;
120 constexpr explicit Result(u32 raw_) : raw(raw_) {} 121 constexpr explicit Result(u32 raw_) : raw(raw_) {}
121 122
122 constexpr Result(ErrorModule module_, u32 description_) 123 constexpr Result(ErrorModule module_, u32 description_)
@@ -130,6 +131,7 @@ union Result {
130 return !IsSuccess(); 131 return !IsSuccess();
131 } 132 }
132}; 133};
134static_assert(std::is_trivial_v<Result>);
133 135
134[[nodiscard]] constexpr bool operator==(const Result& a, const Result& b) { 136[[nodiscard]] constexpr bool operator==(const Result& a, const Result& b) {
135 return a.raw == b.raw; 137 return a.raw == b.raw;
diff --git a/src/core/hle/service/am/applets/applet_mii_edit_types.h b/src/core/hle/service/am/applets/applet_mii_edit_types.h
index 1b145b696..4705d019f 100644
--- a/src/core/hle/service/am/applets/applet_mii_edit_types.h
+++ b/src/core/hle/service/am/applets/applet_mii_edit_types.h
@@ -32,7 +32,7 @@ enum class MiiEditResult : u32 {
32}; 32};
33 33
34struct MiiEditCharInfo { 34struct MiiEditCharInfo {
35 Service::Mii::MiiInfo mii_info{}; 35 Service::Mii::CharInfo mii_info{};
36}; 36};
37static_assert(sizeof(MiiEditCharInfo) == 0x58, "MiiEditCharInfo has incorrect size."); 37static_assert(sizeof(MiiEditCharInfo) == 0x58, "MiiEditCharInfo has incorrect size.");
38 38
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index a44dd842a..49c092301 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -246,9 +246,8 @@ void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) {
246 const auto write_count = 246 const auto write_count =
247 static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); 247 static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName));
248 std::vector<AudioDevice::AudioDeviceName> device_names{}; 248 std::vector<AudioDevice::AudioDeviceName> device_names{};
249 std::string print_names{};
250 if (write_count > 0) { 249 if (write_count > 0) {
251 device_names.push_back(AudioDevice::AudioDeviceName("DeviceOut")); 250 device_names.emplace_back("DeviceOut");
252 LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut"); 251 LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut");
253 } else { 252 } else {
254 LOG_DEBUG(Service_Audio, "called. Empty buffer passed in."); 253 LOG_DEBUG(Service_Audio, "called. Empty buffer passed in.");
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index bc69117c6..6fb07c37d 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -252,7 +252,7 @@ private:
252 252
253 std::vector<AudioDevice::AudioDeviceName> out_names{}; 253 std::vector<AudioDevice::AudioDeviceName> out_names{};
254 254
255 u32 out_count = impl->ListAudioDeviceName(out_names, in_count); 255 const u32 out_count = impl->ListAudioDeviceName(out_names, in_count);
256 256
257 std::string out{}; 257 std::string out{};
258 for (u32 i = 0; i < out_count; i++) { 258 for (u32 i = 0; i < out_count; i++) {
@@ -365,7 +365,7 @@ private:
365 365
366 std::vector<AudioDevice::AudioDeviceName> out_names{}; 366 std::vector<AudioDevice::AudioDeviceName> out_names{};
367 367
368 u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count); 368 const u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count);
369 369
370 std::string out{}; 370 std::string out{};
371 for (u32 i = 0; i < out_count; i++) { 371 for (u32 i = 0; i < out_count; i++) {
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp
index 4f2ed2d52..8bafc3a98 100644
--- a/src/core/hle/service/audio/hwopus.cpp
+++ b/src/core/hle/service/audio/hwopus.cpp
@@ -255,6 +255,32 @@ void HwOpus::GetWorkBufferSizeEx(Kernel::HLERequestContext& ctx) {
255 GetWorkBufferSize(ctx); 255 GetWorkBufferSize(ctx);
256} 256}
257 257
258void HwOpus::GetWorkBufferSizeForMultiStreamEx(Kernel::HLERequestContext& ctx) {
259 OpusMultiStreamParametersEx param;
260 std::memcpy(&param, ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
261
262 const auto sample_rate = param.sample_rate;
263 const auto channel_count = param.channel_count;
264 const auto number_streams = param.number_streams;
265 const auto number_stereo_streams = param.number_stereo_streams;
266
267 LOG_DEBUG(
268 Audio,
269 "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}",
270 sample_rate, channel_count, number_streams, number_stereo_streams);
271
272 ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
273 sample_rate == 12000 || sample_rate == 8000,
274 "Invalid sample rate");
275
276 const u32 worker_buffer_sz =
277 static_cast<u32>(opus_multistream_decoder_get_size(number_streams, number_stereo_streams));
278
279 IPC::ResponseBuilder rb{ctx, 3};
280 rb.Push(ResultSuccess);
281 rb.Push<u32>(worker_buffer_sz);
282}
283
258void HwOpus::OpenHardwareOpusDecoder(Kernel::HLERequestContext& ctx) { 284void HwOpus::OpenHardwareOpusDecoder(Kernel::HLERequestContext& ctx) {
259 IPC::RequestParser rp{ctx}; 285 IPC::RequestParser rp{ctx};
260 const auto sample_rate = rp.Pop<u32>(); 286 const auto sample_rate = rp.Pop<u32>();
@@ -335,7 +361,7 @@ HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} {
335 {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"}, 361 {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"},
336 {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"}, 362 {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"},
337 {6, nullptr, "OpenHardwareOpusDecoderForMultiStreamEx"}, 363 {6, nullptr, "OpenHardwareOpusDecoderForMultiStreamEx"},
338 {7, nullptr, "GetWorkBufferSizeForMultiStreamEx"}, 364 {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"},
339 }; 365 };
340 RegisterHandlers(functions); 366 RegisterHandlers(functions);
341} 367}
diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h
index 265dd0cc6..e6092e290 100644
--- a/src/core/hle/service/audio/hwopus.h
+++ b/src/core/hle/service/audio/hwopus.h
@@ -11,6 +11,16 @@ class System;
11 11
12namespace Service::Audio { 12namespace Service::Audio {
13 13
14struct OpusMultiStreamParametersEx {
15 u32 sample_rate;
16 u32 channel_count;
17 u32 number_streams;
18 u32 number_stereo_streams;
19 u32 use_large_frame_size;
20 u32 padding;
21 std::array<u32, 64> channel_mappings;
22};
23
14class HwOpus final : public ServiceFramework<HwOpus> { 24class HwOpus final : public ServiceFramework<HwOpus> {
15public: 25public:
16 explicit HwOpus(Core::System& system_); 26 explicit HwOpus(Core::System& system_);
@@ -21,6 +31,7 @@ private:
21 void OpenHardwareOpusDecoderEx(Kernel::HLERequestContext& ctx); 31 void OpenHardwareOpusDecoderEx(Kernel::HLERequestContext& ctx);
22 void GetWorkBufferSize(Kernel::HLERequestContext& ctx); 32 void GetWorkBufferSize(Kernel::HLERequestContext& ctx);
23 void GetWorkBufferSizeEx(Kernel::HLERequestContext& ctx); 33 void GetWorkBufferSizeEx(Kernel::HLERequestContext& ctx);
34 void GetWorkBufferSizeForMultiStreamEx(Kernel::HLERequestContext& ctx);
24}; 35};
25 36
26} // namespace Service::Audio 37} // namespace Service::Audio
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index cb29004e8..f8972ec7a 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -660,7 +660,6 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
660 ASSERT(false); 660 ASSERT(false);
661 break; 661 break;
662 case Core::HID::NpadStyleIndex::ProController: 662 case Core::HID::NpadStyleIndex::ProController:
663 case Core::HID::NpadStyleIndex::Pokeball:
664 set_motion_state(sixaxis_fullkey_state, motion_state[0]); 663 set_motion_state(sixaxis_fullkey_state, motion_state[0]);
665 break; 664 break;
666 case Core::HID::NpadStyleIndex::Handheld: 665 case Core::HID::NpadStyleIndex::Handheld:
@@ -676,6 +675,11 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
676 case Core::HID::NpadStyleIndex::JoyconRight: 675 case Core::HID::NpadStyleIndex::JoyconRight:
677 set_motion_state(sixaxis_right_lifo_state, motion_state[1]); 676 set_motion_state(sixaxis_right_lifo_state, motion_state[1]);
678 break; 677 break;
678 case Core::HID::NpadStyleIndex::Pokeball:
679 using namespace std::literals::chrono_literals;
680 set_motion_state(sixaxis_fullkey_state, motion_state[0]);
681 sixaxis_fullkey_state.delta_time = std::chrono::nanoseconds(15ms).count();
682 break;
679 default: 683 default:
680 break; 684 break;
681 } 685 }
diff --git a/src/core/hle/service/hid/controllers/palma.cpp b/src/core/hle/service/hid/controllers/palma.cpp
new file mode 100644
index 000000000..575d4e626
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/palma.cpp
@@ -0,0 +1,229 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/core_timing.h"
5#include "core/hid/emulated_controller.h"
6#include "core/hid/hid_core.h"
7#include "core/hid/hid_types.h"
8#include "core/hle/kernel/k_event.h"
9#include "core/hle/kernel/k_readable_event.h"
10#include "core/hle/service/hid/controllers/palma.h"
11#include "core/hle/service/kernel_helpers.h"
12
13namespace Service::HID {
14
15Controller_Palma::Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
16 KernelHelpers::ServiceContext& service_context_)
17 : ControllerBase{hid_core_}, service_context{service_context_} {
18 controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Other);
19 operation_complete_event = service_context.CreateEvent("hid:PalmaOperationCompleteEvent");
20}
21
22Controller_Palma::~Controller_Palma() = default;
23
24void Controller_Palma::OnInit() {}
25
26void Controller_Palma::OnRelease() {}
27
28void Controller_Palma::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
29 if (!IsControllerActivated()) {
30 return;
31 }
32}
33
34Result Controller_Palma::GetPalmaConnectionHandle(Core::HID::NpadIdType npad_id,
35 PalmaConnectionHandle& handle) {
36 active_handle.npad_id = npad_id;
37 handle = active_handle;
38 return ResultSuccess;
39}
40
41Result Controller_Palma::InitializePalma(const PalmaConnectionHandle& handle) {
42 if (handle.npad_id != active_handle.npad_id) {
43 return InvalidPalmaHandle;
44 }
45 ActivateController();
46 return ResultSuccess;
47}
48
49Kernel::KReadableEvent& Controller_Palma::AcquirePalmaOperationCompleteEvent(
50 const PalmaConnectionHandle& handle) const {
51 if (handle.npad_id != active_handle.npad_id) {
52 LOG_ERROR(Service_HID, "Invalid npad id {}", handle.npad_id);
53 }
54 return operation_complete_event->GetReadableEvent();
55}
56
57Result Controller_Palma::GetPalmaOperationInfo(const PalmaConnectionHandle& handle,
58 PalmaOperationType& operation_type,
59 PalmaOperationData& data) const {
60 if (handle.npad_id != active_handle.npad_id) {
61 return InvalidPalmaHandle;
62 }
63 operation_type = operation.operation;
64 data = operation.data;
65 return ResultSuccess;
66}
67
68Result Controller_Palma::PlayPalmaActivity(const PalmaConnectionHandle& handle,
69 u64 palma_activity) {
70 if (handle.npad_id != active_handle.npad_id) {
71 return InvalidPalmaHandle;
72 }
73 operation.operation = PalmaOperationType::PlayActivity;
74 operation.result = PalmaResultSuccess;
75 operation.data = {};
76 operation_complete_event->GetWritableEvent().Signal();
77 return ResultSuccess;
78}
79
80Result Controller_Palma::SetPalmaFrModeType(const PalmaConnectionHandle& handle,
81 PalmaFrModeType fr_mode_) {
82 if (handle.npad_id != active_handle.npad_id) {
83 return InvalidPalmaHandle;
84 }
85 fr_mode = fr_mode_;
86 return ResultSuccess;
87}
88
89Result Controller_Palma::ReadPalmaStep(const PalmaConnectionHandle& handle) {
90 if (handle.npad_id != active_handle.npad_id) {
91 return InvalidPalmaHandle;
92 }
93 operation.operation = PalmaOperationType::ReadStep;
94 operation.result = PalmaResultSuccess;
95 operation.data = {};
96 operation_complete_event->GetWritableEvent().Signal();
97 return ResultSuccess;
98}
99
100Result Controller_Palma::EnablePalmaStep(const PalmaConnectionHandle& handle, bool is_enabled) {
101 if (handle.npad_id != active_handle.npad_id) {
102 return InvalidPalmaHandle;
103 }
104 return ResultSuccess;
105}
106
107Result Controller_Palma::ResetPalmaStep(const PalmaConnectionHandle& handle) {
108 if (handle.npad_id != active_handle.npad_id) {
109 return InvalidPalmaHandle;
110 }
111 return ResultSuccess;
112}
113
114void Controller_Palma::ReadPalmaApplicationSection() {}
115
116void Controller_Palma::WritePalmaApplicationSection() {}
117
118Result Controller_Palma::ReadPalmaUniqueCode(const PalmaConnectionHandle& handle) {
119 if (handle.npad_id != active_handle.npad_id) {
120 return InvalidPalmaHandle;
121 }
122 operation.operation = PalmaOperationType::ReadUniqueCode;
123 operation.result = PalmaResultSuccess;
124 operation.data = {};
125 operation_complete_event->GetWritableEvent().Signal();
126 return ResultSuccess;
127}
128
129Result Controller_Palma::SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle) {
130 if (handle.npad_id != active_handle.npad_id) {
131 return InvalidPalmaHandle;
132 }
133 operation.operation = PalmaOperationType::SetUniqueCodeInvalid;
134 operation.result = PalmaResultSuccess;
135 operation.data = {};
136 operation_complete_event->GetWritableEvent().Signal();
137 return ResultSuccess;
138}
139
140void Controller_Palma::WritePalmaActivityEntry() {}
141
142Result Controller_Palma::WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle,
143 u64 unknown) {
144 if (handle.npad_id != active_handle.npad_id) {
145 return InvalidPalmaHandle;
146 }
147 operation.operation = PalmaOperationType::WriteRgbLedPatternEntry;
148 operation.result = PalmaResultSuccess;
149 operation.data = {};
150 operation_complete_event->GetWritableEvent().Signal();
151 return ResultSuccess;
152}
153
154Result Controller_Palma::WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWaveSet wave,
155 u8* t_mem, u64 size) {
156 if (handle.npad_id != active_handle.npad_id) {
157 return InvalidPalmaHandle;
158 }
159 operation.operation = PalmaOperationType::WriteWaveEntry;
160 operation.result = PalmaResultSuccess;
161 operation.data = {};
162 operation_complete_event->GetWritableEvent().Signal();
163 return ResultSuccess;
164}
165
166Result Controller_Palma::SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle,
167 s32 database_id_version_) {
168 if (handle.npad_id != active_handle.npad_id) {
169 return InvalidPalmaHandle;
170 }
171 database_id_version = database_id_version_;
172 operation.operation = PalmaOperationType::ReadDataBaseIdentificationVersion;
173 operation.result = PalmaResultSuccess;
174 operation.data[0] = {};
175 operation_complete_event->GetWritableEvent().Signal();
176 return ResultSuccess;
177}
178
179Result Controller_Palma::GetPalmaDataBaseIdentificationVersion(
180 const PalmaConnectionHandle& handle) {
181 if (handle.npad_id != active_handle.npad_id) {
182 return InvalidPalmaHandle;
183 }
184 operation.operation = PalmaOperationType::ReadDataBaseIdentificationVersion;
185 operation.result = PalmaResultSuccess;
186 operation.data = {};
187 operation.data[0] = static_cast<u8>(database_id_version);
188 operation_complete_event->GetWritableEvent().Signal();
189 return ResultSuccess;
190}
191
192void Controller_Palma::SuspendPalmaFeature() {}
193
194Result Controller_Palma::GetPalmaOperationResult(const PalmaConnectionHandle& handle) const {
195 if (handle.npad_id != active_handle.npad_id) {
196 return InvalidPalmaHandle;
197 }
198 return operation.result;
199}
200void Controller_Palma::ReadPalmaPlayLog() {}
201
202void Controller_Palma::ResetPalmaPlayLog() {}
203
204void Controller_Palma::SetIsPalmaAllConnectable(bool is_all_connectable) {
205 // If true controllers are able to be paired
206 is_connectable = is_all_connectable;
207}
208
209void Controller_Palma::SetIsPalmaPairedConnectable() {}
210
211Result Controller_Palma::PairPalma(const PalmaConnectionHandle& handle) {
212 if (handle.npad_id != active_handle.npad_id) {
213 return InvalidPalmaHandle;
214 }
215 // TODO: Do something
216 return ResultSuccess;
217}
218
219void Controller_Palma::SetPalmaBoostMode(bool boost_mode) {}
220
221void Controller_Palma::CancelWritePalmaWaveEntry() {}
222
223void Controller_Palma::EnablePalmaBoostMode() {}
224
225void Controller_Palma::GetPalmaBluetoothAddress() {}
226
227void Controller_Palma::SetDisallowedPalmaConnection() {}
228
229} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/palma.h b/src/core/hle/service/hid/controllers/palma.h
new file mode 100644
index 000000000..1d7fc94e1
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/palma.h
@@ -0,0 +1,163 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include "common/common_funcs.h"
8#include "common/common_types.h"
9#include "core/hle/service/hid/controllers/controller_base.h"
10#include "core/hle/service/hid/errors.h"
11
12namespace Kernel {
13class KEvent;
14class KReadableEvent;
15} // namespace Kernel
16
17namespace Service::KernelHelpers {
18class ServiceContext;
19}
20
21namespace Core::HID {
22class EmulatedController;
23} // namespace Core::HID
24
25namespace Service::HID {
26class Controller_Palma final : public ControllerBase {
27public:
28 using PalmaOperationData = std::array<u8, 0x140>;
29
30 // This is nn::hid::PalmaOperationType
31 enum class PalmaOperationType {
32 PlayActivity,
33 SetFrModeType,
34 ReadStep,
35 EnableStep,
36 ResetStep,
37 ReadApplicationSection,
38 WriteApplicationSection,
39 ReadUniqueCode,
40 SetUniqueCodeInvalid,
41 WriteActivityEntry,
42 WriteRgbLedPatternEntry,
43 WriteWaveEntry,
44 ReadDataBaseIdentificationVersion,
45 WriteDataBaseIdentificationVersion,
46 SuspendFeature,
47 ReadPlayLog,
48 ResetPlayLog,
49 };
50
51 // This is nn::hid::PalmaWaveSet
52 enum class PalmaWaveSet : u64 {
53 Small,
54 Medium,
55 Large,
56 };
57
58 // This is nn::hid::PalmaFrModeType
59 enum class PalmaFrModeType : u64 {
60 Off,
61 B01,
62 B02,
63 B03,
64 Downloaded,
65 };
66
67 // This is nn::hid::PalmaFeature
68 enum class PalmaFeature : u64 {
69 FrMode,
70 RumbleFeedback,
71 Step,
72 MuteSwitch,
73 };
74
75 // This is nn::hid::PalmaOperationInfo
76 struct PalmaOperationInfo {
77 PalmaOperationType operation{};
78 Result result{PalmaResultSuccess};
79 PalmaOperationData data{};
80 };
81 static_assert(sizeof(PalmaOperationInfo) == 0x148, "PalmaOperationInfo is an invalid size");
82
83 // This is nn::hid::PalmaActivityEntry
84 struct PalmaActivityEntry {
85 u32 rgb_led_pattern_index;
86 INSERT_PADDING_BYTES(2);
87 PalmaWaveSet wave_set;
88 u32 wave_index;
89 INSERT_PADDING_BYTES(12);
90 };
91 static_assert(sizeof(PalmaActivityEntry) == 0x20, "PalmaActivityEntry is an invalid size");
92
93 struct PalmaConnectionHandle {
94 Core::HID::NpadIdType npad_id;
95 INSERT_PADDING_BYTES(4); // Unknown
96 };
97 static_assert(sizeof(PalmaConnectionHandle) == 0x8,
98 "PalmaConnectionHandle has incorrect size.");
99
100 explicit Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
101 KernelHelpers::ServiceContext& service_context_);
102 ~Controller_Palma() override;
103
104 // Called when the controller is initialized
105 void OnInit() override;
106
107 // When the controller is released
108 void OnRelease() override;
109
110 // When the controller is requesting an update for the shared memory
111 void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
112
113 Result GetPalmaConnectionHandle(Core::HID::NpadIdType npad_id, PalmaConnectionHandle& handle);
114 Result InitializePalma(const PalmaConnectionHandle& handle);
115 Kernel::KReadableEvent& AcquirePalmaOperationCompleteEvent(
116 const PalmaConnectionHandle& handle) const;
117 Result GetPalmaOperationInfo(const PalmaConnectionHandle& handle,
118 PalmaOperationType& operation_type,
119 PalmaOperationData& data) const;
120 Result PlayPalmaActivity(const PalmaConnectionHandle& handle, u64 palma_activity);
121 Result SetPalmaFrModeType(const PalmaConnectionHandle& handle, PalmaFrModeType fr_mode_);
122 Result ReadPalmaStep(const PalmaConnectionHandle& handle);
123 Result EnablePalmaStep(const PalmaConnectionHandle& handle, bool is_enabled);
124 Result ResetPalmaStep(const PalmaConnectionHandle& handle);
125 Result ReadPalmaUniqueCode(const PalmaConnectionHandle& handle);
126 Result SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle);
127 Result WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle, u64 unknown);
128 Result WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWaveSet wave, u8* t_mem,
129 u64 size);
130 Result SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle,
131 s32 database_id_version_);
132 Result GetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle);
133 Result GetPalmaOperationResult(const PalmaConnectionHandle& handle) const;
134 void SetIsPalmaAllConnectable(bool is_all_connectable);
135 Result PairPalma(const PalmaConnectionHandle& handle);
136 void SetPalmaBoostMode(bool boost_mode);
137
138private:
139 void ReadPalmaApplicationSection();
140 void WritePalmaApplicationSection();
141 void WritePalmaActivityEntry();
142 void SuspendPalmaFeature();
143 void ReadPalmaPlayLog();
144 void ResetPalmaPlayLog();
145 void SetIsPalmaPairedConnectable();
146 void CancelWritePalmaWaveEntry();
147 void EnablePalmaBoostMode();
148 void GetPalmaBluetoothAddress();
149 void SetDisallowedPalmaConnection();
150
151 bool is_connectable{};
152 s32 database_id_version{};
153 PalmaOperationInfo operation{};
154 PalmaFrModeType fr_mode{};
155 PalmaConnectionHandle active_handle{};
156
157 Core::HID::EmulatedController* controller;
158
159 Kernel::KEvent* operation_complete_event;
160 KernelHelpers::ServiceContext& service_context;
161};
162
163} // namespace Service::HID
diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h
index 4613a4e60..76208e9a4 100644
--- a/src/core/hle/service/hid/errors.h
+++ b/src/core/hle/service/hid/errors.h
@@ -7,6 +7,7 @@
7 7
8namespace Service::HID { 8namespace Service::HID {
9 9
10constexpr Result PalmaResultSuccess{ErrorModule::HID, 0};
10constexpr Result NpadInvalidHandle{ErrorModule::HID, 100}; 11constexpr Result NpadInvalidHandle{ErrorModule::HID, 100};
11constexpr Result NpadDeviceIndexOutOfRange{ErrorModule::HID, 107}; 12constexpr Result NpadDeviceIndexOutOfRange{ErrorModule::HID, 107};
12constexpr Result VibrationInvalidStyleIndex{ErrorModule::HID, 122}; 13constexpr Result VibrationInvalidStyleIndex{ErrorModule::HID, 122};
@@ -17,6 +18,7 @@ constexpr Result NpadIsDualJoycon{ErrorModule::HID, 601};
17constexpr Result NpadIsSameType{ErrorModule::HID, 602}; 18constexpr Result NpadIsSameType{ErrorModule::HID, 602};
18constexpr Result InvalidNpadId{ErrorModule::HID, 709}; 19constexpr Result InvalidNpadId{ErrorModule::HID, 709};
19constexpr Result NpadNotConnected{ErrorModule::HID, 710}; 20constexpr Result NpadNotConnected{ErrorModule::HID, 710};
21constexpr Result InvalidPalmaHandle{ErrorModule::HID, 3302};
20 22
21} // namespace Service::HID 23} // namespace Service::HID
22 24
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 7e923462b..46bad7871 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -27,6 +27,7 @@
27#include "core/hle/service/hid/controllers/keyboard.h" 27#include "core/hle/service/hid/controllers/keyboard.h"
28#include "core/hle/service/hid/controllers/mouse.h" 28#include "core/hle/service/hid/controllers/mouse.h"
29#include "core/hle/service/hid/controllers/npad.h" 29#include "core/hle/service/hid/controllers/npad.h"
30#include "core/hle/service/hid/controllers/palma.h"
30#include "core/hle/service/hid/controllers/stubbed.h" 31#include "core/hle/service/hid/controllers/stubbed.h"
31#include "core/hle/service/hid/controllers/touchscreen.h" 32#include "core/hle/service/hid/controllers/touchscreen.h"
32#include "core/hle/service/hid/controllers/xpad.h" 33#include "core/hle/service/hid/controllers/xpad.h"
@@ -61,6 +62,7 @@ IAppletResource::IAppletResource(Core::System& system_,
61 MakeControllerWithServiceContext<Controller_NPad>(HidController::NPad, shared_memory); 62 MakeControllerWithServiceContext<Controller_NPad>(HidController::NPad, shared_memory);
62 MakeController<Controller_Gesture>(HidController::Gesture, shared_memory); 63 MakeController<Controller_Gesture>(HidController::Gesture, shared_memory);
63 MakeController<Controller_ConsoleSixAxis>(HidController::ConsoleSixAxisSensor, shared_memory); 64 MakeController<Controller_ConsoleSixAxis>(HidController::ConsoleSixAxisSensor, shared_memory);
65 MakeControllerWithServiceContext<Controller_Palma>(HidController::Palma, shared_memory);
64 66
65 // Homebrew doesn't try to activate some controllers, so we activate them by default 67 // Homebrew doesn't try to activate some controllers, so we activate them by default
66 GetController<Controller_NPad>(HidController::NPad).ActivateController(); 68 GetController<Controller_NPad>(HidController::NPad).ActivateController();
@@ -311,36 +313,36 @@ Hid::Hid(Core::System& system_)
311 {406, nullptr, "GetNpadLeftRightInterfaceType"}, 313 {406, nullptr, "GetNpadLeftRightInterfaceType"},
312 {407, nullptr, "GetNpadOfHighestBatteryLevel"}, 314 {407, nullptr, "GetNpadOfHighestBatteryLevel"},
313 {408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"}, 315 {408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"},
314 {500, nullptr, "GetPalmaConnectionHandle"}, 316 {500, &Hid::GetPalmaConnectionHandle, "GetPalmaConnectionHandle"},
315 {501, nullptr, "InitializePalma"}, 317 {501, &Hid::InitializePalma, "InitializePalma"},
316 {502, nullptr, "AcquirePalmaOperationCompleteEvent"}, 318 {502, &Hid::AcquirePalmaOperationCompleteEvent, "AcquirePalmaOperationCompleteEvent"},
317 {503, nullptr, "GetPalmaOperationInfo"}, 319 {503, &Hid::GetPalmaOperationInfo, "GetPalmaOperationInfo"},
318 {504, nullptr, "PlayPalmaActivity"}, 320 {504, &Hid::PlayPalmaActivity, "PlayPalmaActivity"},
319 {505, nullptr, "SetPalmaFrModeType"}, 321 {505, &Hid::SetPalmaFrModeType, "SetPalmaFrModeType"},
320 {506, nullptr, "ReadPalmaStep"}, 322 {506, &Hid::ReadPalmaStep, "ReadPalmaStep"},
321 {507, nullptr, "EnablePalmaStep"}, 323 {507, &Hid::EnablePalmaStep, "EnablePalmaStep"},
322 {508, nullptr, "ResetPalmaStep"}, 324 {508, &Hid::ResetPalmaStep, "ResetPalmaStep"},
323 {509, nullptr, "ReadPalmaApplicationSection"}, 325 {509, &Hid::ReadPalmaApplicationSection, "ReadPalmaApplicationSection"},
324 {510, nullptr, "WritePalmaApplicationSection"}, 326 {510, &Hid::WritePalmaApplicationSection, "WritePalmaApplicationSection"},
325 {511, nullptr, "ReadPalmaUniqueCode"}, 327 {511, &Hid::ReadPalmaUniqueCode, "ReadPalmaUniqueCode"},
326 {512, nullptr, "SetPalmaUniqueCodeInvalid"}, 328 {512, &Hid::SetPalmaUniqueCodeInvalid, "SetPalmaUniqueCodeInvalid"},
327 {513, nullptr, "WritePalmaActivityEntry"}, 329 {513, &Hid::WritePalmaActivityEntry, "WritePalmaActivityEntry"},
328 {514, nullptr, "WritePalmaRgbLedPatternEntry"}, 330 {514, &Hid::WritePalmaRgbLedPatternEntry, "WritePalmaRgbLedPatternEntry"},
329 {515, nullptr, "WritePalmaWaveEntry"}, 331 {515, &Hid::WritePalmaWaveEntry, "WritePalmaWaveEntry"},
330 {516, nullptr, "SetPalmaDataBaseIdentificationVersion"}, 332 {516, &Hid::SetPalmaDataBaseIdentificationVersion, "SetPalmaDataBaseIdentificationVersion"},
331 {517, nullptr, "GetPalmaDataBaseIdentificationVersion"}, 333 {517, &Hid::GetPalmaDataBaseIdentificationVersion, "GetPalmaDataBaseIdentificationVersion"},
332 {518, nullptr, "SuspendPalmaFeature"}, 334 {518, &Hid::SuspendPalmaFeature, "SuspendPalmaFeature"},
333 {519, nullptr, "GetPalmaOperationResult"}, 335 {519, &Hid::GetPalmaOperationResult, "GetPalmaOperationResult"},
334 {520, nullptr, "ReadPalmaPlayLog"}, 336 {520, &Hid::ReadPalmaPlayLog, "ReadPalmaPlayLog"},
335 {521, nullptr, "ResetPalmaPlayLog"}, 337 {521, &Hid::ResetPalmaPlayLog, "ResetPalmaPlayLog"},
336 {522, &Hid::SetIsPalmaAllConnectable, "SetIsPalmaAllConnectable"}, 338 {522, &Hid::SetIsPalmaAllConnectable, "SetIsPalmaAllConnectable"},
337 {523, nullptr, "SetIsPalmaPairedConnectable"}, 339 {523, &Hid::SetIsPalmaPairedConnectable, "SetIsPalmaPairedConnectable"},
338 {524, nullptr, "PairPalma"}, 340 {524, &Hid::PairPalma, "PairPalma"},
339 {525, &Hid::SetPalmaBoostMode, "SetPalmaBoostMode"}, 341 {525, &Hid::SetPalmaBoostMode, "SetPalmaBoostMode"},
340 {526, nullptr, "CancelWritePalmaWaveEntry"}, 342 {526, &Hid::CancelWritePalmaWaveEntry, "CancelWritePalmaWaveEntry"},
341 {527, nullptr, "EnablePalmaBoostMode"}, 343 {527, &Hid::EnablePalmaBoostMode, "EnablePalmaBoostMode"},
342 {528, nullptr, "GetPalmaBluetoothAddress"}, 344 {528, &Hid::GetPalmaBluetoothAddress, "GetPalmaBluetoothAddress"},
343 {529, nullptr, "SetDisallowedPalmaConnection"}, 345 {529, &Hid::SetDisallowedPalmaConnection, "SetDisallowedPalmaConnection"},
344 {1000, &Hid::SetNpadCommunicationMode, "SetNpadCommunicationMode"}, 346 {1000, &Hid::SetNpadCommunicationMode, "SetNpadCommunicationMode"},
345 {1001, &Hid::GetNpadCommunicationMode, "GetNpadCommunicationMode"}, 347 {1001, &Hid::GetNpadCommunicationMode, "GetNpadCommunicationMode"},
346 {1002, &Hid::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"}, 348 {1002, &Hid::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"},
@@ -1879,14 +1881,361 @@ void Hid::IsUsbFullKeyControllerEnabled(Kernel::HLERequestContext& ctx) {
1879 rb.Push(false); 1881 rb.Push(false);
1880} 1882}
1881 1883
1884void Hid::GetPalmaConnectionHandle(Kernel::HLERequestContext& ctx) {
1885 IPC::RequestParser rp{ctx};
1886 struct Parameters {
1887 Core::HID::NpadIdType npad_id;
1888 INSERT_PADDING_WORDS_NOINIT(1);
1889 u64 applet_resource_user_id;
1890 };
1891 static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
1892
1893 const auto parameters{rp.PopRaw<Parameters>()};
1894
1895 LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
1896 parameters.npad_id, parameters.applet_resource_user_id);
1897
1898 Controller_Palma::PalmaConnectionHandle handle;
1899 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
1900 const auto result = controller.GetPalmaConnectionHandle(parameters.npad_id, handle);
1901
1902 IPC::ResponseBuilder rb{ctx, 4};
1903 rb.Push(result);
1904 rb.PushRaw(handle);
1905}
1906
1907void Hid::InitializePalma(Kernel::HLERequestContext& ctx) {
1908 IPC::RequestParser rp{ctx};
1909 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
1910
1911 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
1912
1913 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
1914 const auto result = controller.InitializePalma(connection_handle);
1915
1916 IPC::ResponseBuilder rb{ctx, 2};
1917 rb.Push(result);
1918}
1919
1920void Hid::AcquirePalmaOperationCompleteEvent(Kernel::HLERequestContext& ctx) {
1921 IPC::RequestParser rp{ctx};
1922 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
1923
1924 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
1925
1926 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
1927
1928 IPC::ResponseBuilder rb{ctx, 2, 1};
1929 rb.Push(ResultSuccess);
1930 rb.PushCopyObjects(controller.AcquirePalmaOperationCompleteEvent(connection_handle));
1931}
1932
1933void Hid::GetPalmaOperationInfo(Kernel::HLERequestContext& ctx) {
1934 IPC::RequestParser rp{ctx};
1935 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
1936
1937 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
1938
1939 Controller_Palma::PalmaOperationType operation_type;
1940 Controller_Palma::PalmaOperationData data;
1941 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
1942 const auto result = controller.GetPalmaOperationInfo(connection_handle, operation_type, data);
1943
1944 if (result.IsError()) {
1945 IPC::ResponseBuilder rb{ctx, 2};
1946 rb.Push(result);
1947 }
1948
1949 ctx.WriteBuffer(data);
1950 IPC::ResponseBuilder rb{ctx, 4};
1951 rb.Push(result);
1952 rb.Push(static_cast<u64>(operation_type));
1953}
1954
1955void Hid::PlayPalmaActivity(Kernel::HLERequestContext& ctx) {
1956 IPC::RequestParser rp{ctx};
1957 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
1958 const auto palma_activity{rp.Pop<u64>()};
1959
1960 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, palma_activity={}",
1961 connection_handle.npad_id, palma_activity);
1962
1963 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
1964 const auto result = controller.PlayPalmaActivity(connection_handle, palma_activity);
1965
1966 IPC::ResponseBuilder rb{ctx, 2};
1967 rb.Push(result);
1968}
1969
1970void Hid::SetPalmaFrModeType(Kernel::HLERequestContext& ctx) {
1971 IPC::RequestParser rp{ctx};
1972 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
1973 const auto fr_mode{rp.PopEnum<Controller_Palma::PalmaFrModeType>()};
1974
1975 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, fr_mode={}",
1976 connection_handle.npad_id, fr_mode);
1977
1978 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
1979 const auto result = controller.SetPalmaFrModeType(connection_handle, fr_mode);
1980
1981 IPC::ResponseBuilder rb{ctx, 2};
1982 rb.Push(result);
1983}
1984
1985void Hid::ReadPalmaStep(Kernel::HLERequestContext& ctx) {
1986 IPC::RequestParser rp{ctx};
1987 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
1988
1989 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
1990
1991 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
1992 const auto result = controller.ReadPalmaStep(connection_handle);
1993
1994 IPC::ResponseBuilder rb{ctx, 2};
1995 rb.Push(result);
1996}
1997
1998void Hid::EnablePalmaStep(Kernel::HLERequestContext& ctx) {
1999 IPC::RequestParser rp{ctx};
2000 struct Parameters {
2001 bool is_enabled;
2002 INSERT_PADDING_WORDS_NOINIT(1);
2003 Controller_Palma::PalmaConnectionHandle connection_handle;
2004 };
2005 static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
2006
2007 const auto parameters{rp.PopRaw<Parameters>()};
2008
2009 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, is_enabled={}",
2010 parameters.connection_handle.npad_id, parameters.is_enabled);
2011
2012 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
2013 const auto result =
2014 controller.EnablePalmaStep(parameters.connection_handle, parameters.is_enabled);
2015
2016 IPC::ResponseBuilder rb{ctx, 2};
2017 rb.Push(result);
2018}
2019
2020void Hid::ResetPalmaStep(Kernel::HLERequestContext& ctx) {
2021 IPC::RequestParser rp{ctx};
2022 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
2023
2024 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
2025
2026 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
2027 const auto result = controller.ResetPalmaStep(connection_handle);
2028
2029 IPC::ResponseBuilder rb{ctx, 2};
2030 rb.Push(result);
2031}
2032
2033void Hid::ReadPalmaApplicationSection(Kernel::HLERequestContext& ctx) {
2034 LOG_WARNING(Service_HID, "(STUBBED) called");
2035
2036 IPC::ResponseBuilder rb{ctx, 2};
2037 rb.Push(ResultSuccess);
2038}
2039
2040void Hid::WritePalmaApplicationSection(Kernel::HLERequestContext& ctx) {
2041 LOG_WARNING(Service_HID, "(STUBBED) called");
2042
2043 IPC::ResponseBuilder rb{ctx, 2};
2044 rb.Push(ResultSuccess);
2045}
2046
2047void Hid::ReadPalmaUniqueCode(Kernel::HLERequestContext& ctx) {
2048 IPC::RequestParser rp{ctx};
2049 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
2050
2051 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
2052
2053 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2054 .ReadPalmaUniqueCode(connection_handle);
2055
2056 IPC::ResponseBuilder rb{ctx, 2};
2057 rb.Push(ResultSuccess);
2058}
2059
2060void Hid::SetPalmaUniqueCodeInvalid(Kernel::HLERequestContext& ctx) {
2061 IPC::RequestParser rp{ctx};
2062 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
2063
2064 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
2065
2066 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2067 .SetPalmaUniqueCodeInvalid(connection_handle);
2068
2069 IPC::ResponseBuilder rb{ctx, 2};
2070 rb.Push(ResultSuccess);
2071}
2072
2073void Hid::WritePalmaActivityEntry(Kernel::HLERequestContext& ctx) {
2074 LOG_CRITICAL(Service_HID, "(STUBBED) called");
2075
2076 IPC::ResponseBuilder rb{ctx, 2};
2077 rb.Push(ResultSuccess);
2078}
2079
2080void Hid::WritePalmaRgbLedPatternEntry(Kernel::HLERequestContext& ctx) {
2081 IPC::RequestParser rp{ctx};
2082 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
2083 const auto unknown{rp.Pop<u64>()};
2084
2085 const auto buffer = ctx.ReadBuffer();
2086
2087 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, unknown={}",
2088 connection_handle.npad_id, unknown);
2089
2090 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2091 .WritePalmaRgbLedPatternEntry(connection_handle, unknown);
2092
2093 IPC::ResponseBuilder rb{ctx, 2};
2094 rb.Push(ResultSuccess);
2095}
2096
2097void Hid::WritePalmaWaveEntry(Kernel::HLERequestContext& ctx) {
2098 IPC::RequestParser rp{ctx};
2099 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
2100 const auto wave_set{rp.PopEnum<Controller_Palma::PalmaWaveSet>()};
2101 const auto unknown{rp.Pop<u64>()};
2102 const auto t_mem_size{rp.Pop<u64>()};
2103 const auto t_mem_handle{ctx.GetCopyHandle(0)};
2104 const auto size{rp.Pop<u64>()};
2105
2106 ASSERT_MSG(t_mem_size == 0x3000, "t_mem_size is not 0x3000 bytes");
2107
2108 auto t_mem =
2109 system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle);
2110
2111 if (t_mem.IsNull()) {
2112 LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);
2113 IPC::ResponseBuilder rb{ctx, 2};
2114 rb.Push(ResultUnknown);
2115 return;
2116 }
2117
2118 ASSERT_MSG(t_mem->GetSize() == 0x3000, "t_mem has incorrect size");
2119
2120 LOG_WARNING(Service_HID,
2121 "(STUBBED) called, connection_handle={}, wave_set={}, unkown={}, "
2122 "t_mem_handle=0x{:08X}, t_mem_size={}, size={}",
2123 connection_handle.npad_id, wave_set, unknown, t_mem_handle, t_mem_size, size);
2124
2125 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2126 .WritePalmaWaveEntry(connection_handle, wave_set,
2127 system.Memory().GetPointer(t_mem->GetSourceAddress()), t_mem_size);
2128
2129 IPC::ResponseBuilder rb{ctx, 2};
2130 rb.Push(ResultSuccess);
2131}
2132
2133void Hid::SetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx) {
2134 IPC::RequestParser rp{ctx};
2135 struct Parameters {
2136 s32 database_id_version;
2137 INSERT_PADDING_WORDS_NOINIT(1);
2138 Controller_Palma::PalmaConnectionHandle connection_handle;
2139 };
2140 static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
2141
2142 const auto parameters{rp.PopRaw<Parameters>()};
2143
2144 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, database_id_version={}",
2145 parameters.connection_handle.npad_id, parameters.database_id_version);
2146
2147 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2148 .SetPalmaDataBaseIdentificationVersion(parameters.connection_handle,
2149 parameters.database_id_version);
2150
2151 IPC::ResponseBuilder rb{ctx, 2};
2152 rb.Push(ResultSuccess);
2153}
2154
2155void Hid::GetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx) {
2156 IPC::RequestParser rp{ctx};
2157 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
2158
2159 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
2160
2161 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2162 .GetPalmaDataBaseIdentificationVersion(connection_handle);
2163
2164 IPC::ResponseBuilder rb{ctx, 2};
2165 rb.Push(ResultSuccess);
2166}
2167
2168void Hid::SuspendPalmaFeature(Kernel::HLERequestContext& ctx) {
2169 LOG_WARNING(Service_HID, "(STUBBED) called");
2170
2171 IPC::ResponseBuilder rb{ctx, 2};
2172 rb.Push(ResultSuccess);
2173}
2174
2175void Hid::GetPalmaOperationResult(Kernel::HLERequestContext& ctx) {
2176 IPC::RequestParser rp{ctx};
2177 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
2178
2179 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
2180
2181 const auto result = applet_resource->GetController<Controller_Palma>(HidController::Palma)
2182 .GetPalmaOperationResult(connection_handle);
2183
2184 IPC::ResponseBuilder rb{ctx, 2};
2185 rb.Push(result);
2186}
2187
2188void Hid::ReadPalmaPlayLog(Kernel::HLERequestContext& ctx) {
2189 LOG_WARNING(Service_HID, "(STUBBED) called");
2190
2191 IPC::ResponseBuilder rb{ctx, 2};
2192 rb.Push(ResultSuccess);
2193}
2194
2195void Hid::ResetPalmaPlayLog(Kernel::HLERequestContext& ctx) {
2196 LOG_WARNING(Service_HID, "(STUBBED) called");
2197
2198 IPC::ResponseBuilder rb{ctx, 2};
2199 rb.Push(ResultSuccess);
2200}
2201
1882void Hid::SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) { 2202void Hid::SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) {
1883 IPC::RequestParser rp{ctx}; 2203 IPC::RequestParser rp{ctx};
1884 const auto applet_resource_user_id{rp.Pop<u64>()}; 2204 struct Parameters {
1885 const auto is_palma_all_connectable{rp.Pop<bool>()}; 2205 bool is_palma_all_connectable;
2206 INSERT_PADDING_BYTES_NOINIT(7);
2207 u64 applet_resource_user_id;
2208 };
2209 static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
2210
2211 const auto parameters{rp.PopRaw<Parameters>()};
1886 2212
1887 LOG_WARNING(Service_HID, 2213 LOG_WARNING(Service_HID,
1888 "(STUBBED) called, applet_resource_user_id={}, is_palma_all_connectable={}", 2214 "(STUBBED) called, is_palma_all_connectable={},applet_resource_user_id={}",
1889 applet_resource_user_id, is_palma_all_connectable); 2215 parameters.is_palma_all_connectable, parameters.applet_resource_user_id);
2216
2217 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2218 .SetIsPalmaAllConnectable(parameters.is_palma_all_connectable);
2219
2220 IPC::ResponseBuilder rb{ctx, 2};
2221 rb.Push(ResultSuccess);
2222}
2223
2224void Hid::SetIsPalmaPairedConnectable(Kernel::HLERequestContext& ctx) {
2225 LOG_WARNING(Service_HID, "(STUBBED) called");
2226
2227 IPC::ResponseBuilder rb{ctx, 2};
2228 rb.Push(ResultSuccess);
2229}
2230
2231void Hid::PairPalma(Kernel::HLERequestContext& ctx) {
2232 IPC::RequestParser rp{ctx};
2233 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
2234
2235 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
2236
2237 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2238 .PairPalma(connection_handle);
1890 2239
1891 IPC::ResponseBuilder rb{ctx, 2}; 2240 IPC::ResponseBuilder rb{ctx, 2};
1892 rb.Push(ResultSuccess); 2241 rb.Push(ResultSuccess);
@@ -1898,6 +2247,37 @@ void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) {
1898 2247
1899 LOG_WARNING(Service_HID, "(STUBBED) called, palma_boost_mode={}", palma_boost_mode); 2248 LOG_WARNING(Service_HID, "(STUBBED) called, palma_boost_mode={}", palma_boost_mode);
1900 2249
2250 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2251 .SetPalmaBoostMode(palma_boost_mode);
2252
2253 IPC::ResponseBuilder rb{ctx, 2};
2254 rb.Push(ResultSuccess);
2255}
2256
2257void Hid::CancelWritePalmaWaveEntry(Kernel::HLERequestContext& ctx) {
2258 LOG_WARNING(Service_HID, "(STUBBED) called");
2259
2260 IPC::ResponseBuilder rb{ctx, 2};
2261 rb.Push(ResultSuccess);
2262}
2263
2264void Hid::EnablePalmaBoostMode(Kernel::HLERequestContext& ctx) {
2265 LOG_WARNING(Service_HID, "(STUBBED) called");
2266
2267 IPC::ResponseBuilder rb{ctx, 2};
2268 rb.Push(ResultSuccess);
2269}
2270
2271void Hid::GetPalmaBluetoothAddress(Kernel::HLERequestContext& ctx) {
2272 LOG_WARNING(Service_HID, "(STUBBED) called");
2273
2274 IPC::ResponseBuilder rb{ctx, 2};
2275 rb.Push(ResultSuccess);
2276}
2277
2278void Hid::SetDisallowedPalmaConnection(Kernel::HLERequestContext& ctx) {
2279 LOG_WARNING(Service_HID, "(STUBBED) called");
2280
1901 IPC::ResponseBuilder rb{ctx, 2}; 2281 IPC::ResponseBuilder rb{ctx, 2};
1902 rb.Push(ResultSuccess); 2282 rb.Push(ResultSuccess);
1903} 2283}
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index ac4333022..340d26fdc 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -33,6 +33,7 @@ enum class HidController : std::size_t {
33 NPad, 33 NPad,
34 Gesture, 34 Gesture,
35 ConsoleSixAxisSensor, 35 ConsoleSixAxisSensor,
36 Palma,
36 37
37 MaxControllers, 38 MaxControllers,
38}; 39};
@@ -166,8 +167,36 @@ private:
166 void FinalizeSevenSixAxisSensor(Kernel::HLERequestContext& ctx); 167 void FinalizeSevenSixAxisSensor(Kernel::HLERequestContext& ctx);
167 void ResetSevenSixAxisSensorTimestamp(Kernel::HLERequestContext& ctx); 168 void ResetSevenSixAxisSensorTimestamp(Kernel::HLERequestContext& ctx);
168 void IsUsbFullKeyControllerEnabled(Kernel::HLERequestContext& ctx); 169 void IsUsbFullKeyControllerEnabled(Kernel::HLERequestContext& ctx);
170 void GetPalmaConnectionHandle(Kernel::HLERequestContext& ctx);
171 void InitializePalma(Kernel::HLERequestContext& ctx);
172 void AcquirePalmaOperationCompleteEvent(Kernel::HLERequestContext& ctx);
173 void GetPalmaOperationInfo(Kernel::HLERequestContext& ctx);
174 void PlayPalmaActivity(Kernel::HLERequestContext& ctx);
175 void SetPalmaFrModeType(Kernel::HLERequestContext& ctx);
176 void ReadPalmaStep(Kernel::HLERequestContext& ctx);
177 void EnablePalmaStep(Kernel::HLERequestContext& ctx);
178 void ResetPalmaStep(Kernel::HLERequestContext& ctx);
179 void ReadPalmaApplicationSection(Kernel::HLERequestContext& ctx);
180 void WritePalmaApplicationSection(Kernel::HLERequestContext& ctx);
181 void ReadPalmaUniqueCode(Kernel::HLERequestContext& ctx);
182 void SetPalmaUniqueCodeInvalid(Kernel::HLERequestContext& ctx);
183 void WritePalmaActivityEntry(Kernel::HLERequestContext& ctx);
184 void WritePalmaRgbLedPatternEntry(Kernel::HLERequestContext& ctx);
185 void WritePalmaWaveEntry(Kernel::HLERequestContext& ctx);
186 void SetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx);
187 void GetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx);
188 void SuspendPalmaFeature(Kernel::HLERequestContext& ctx);
189 void GetPalmaOperationResult(Kernel::HLERequestContext& ctx);
190 void ReadPalmaPlayLog(Kernel::HLERequestContext& ctx);
191 void ResetPalmaPlayLog(Kernel::HLERequestContext& ctx);
169 void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx); 192 void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx);
193 void SetIsPalmaPairedConnectable(Kernel::HLERequestContext& ctx);
194 void PairPalma(Kernel::HLERequestContext& ctx);
170 void SetPalmaBoostMode(Kernel::HLERequestContext& ctx); 195 void SetPalmaBoostMode(Kernel::HLERequestContext& ctx);
196 void CancelWritePalmaWaveEntry(Kernel::HLERequestContext& ctx);
197 void EnablePalmaBoostMode(Kernel::HLERequestContext& ctx);
198 void GetPalmaBluetoothAddress(Kernel::HLERequestContext& ctx);
199 void SetDisallowedPalmaConnection(Kernel::HLERequestContext& ctx);
171 void SetNpadCommunicationMode(Kernel::HLERequestContext& ctx); 200 void SetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
172 void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx); 201 void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
173 void SetTouchScreenConfiguration(Kernel::HLERequestContext& ctx); 202 void SetTouchScreenConfiguration(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index c4b44cbf9..6a3453457 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -542,7 +542,8 @@ Result IRS::IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_h
542 542
543Core::IrSensor::DeviceFormat& IRS::GetIrCameraSharedMemoryDeviceEntry( 543Core::IrSensor::DeviceFormat& IRS::GetIrCameraSharedMemoryDeviceEntry(
544 const Core::IrSensor::IrCameraHandle& camera_handle) { 544 const Core::IrSensor::IrCameraHandle& camera_handle) {
545 ASSERT_MSG(sizeof(StatusManager::device) > camera_handle.npad_id, "invalid npad_id"); 545 const auto npad_id_max_index = static_cast<u8>(sizeof(StatusManager::device));
546 ASSERT_MSG(camera_handle.npad_id < npad_id_max_index, "invalid npad_id");
546 return shared_memory->device[camera_handle.npad_id]; 547 return shared_memory->device[camera_handle.npad_id];
547} 548}
548 549
diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp
index efb569993..390514fdc 100644
--- a/src/core/hle/service/mii/mii.cpp
+++ b/src/core/hle/service/mii/mii.cpp
@@ -43,7 +43,7 @@ public:
43 {20, nullptr, "IsBrokenDatabaseWithClearFlag"}, 43 {20, nullptr, "IsBrokenDatabaseWithClearFlag"},
44 {21, &IDatabaseService::GetIndex, "GetIndex"}, 44 {21, &IDatabaseService::GetIndex, "GetIndex"},
45 {22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"}, 45 {22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"},
46 {23, nullptr, "Convert"}, 46 {23, &IDatabaseService::Convert, "Convert"},
47 {24, nullptr, "ConvertCoreDataToCharInfo"}, 47 {24, nullptr, "ConvertCoreDataToCharInfo"},
48 {25, nullptr, "ConvertCharInfoToCoreData"}, 48 {25, nullptr, "ConvertCharInfoToCoreData"},
49 {26, nullptr, "Append"}, 49 {26, nullptr, "Append"},
@@ -130,7 +130,7 @@ private:
130 return; 130 return;
131 } 131 }
132 132
133 std::vector<MiiInfo> values; 133 std::vector<CharInfo> values;
134 for (const auto& element : *result) { 134 for (const auto& element : *result) {
135 values.emplace_back(element.info); 135 values.emplace_back(element.info);
136 } 136 }
@@ -144,7 +144,7 @@ private:
144 144
145 void UpdateLatest(Kernel::HLERequestContext& ctx) { 145 void UpdateLatest(Kernel::HLERequestContext& ctx) {
146 IPC::RequestParser rp{ctx}; 146 IPC::RequestParser rp{ctx};
147 const auto info{rp.PopRaw<MiiInfo>()}; 147 const auto info{rp.PopRaw<CharInfo>()};
148 const auto source_flag{rp.PopRaw<SourceFlag>()}; 148 const auto source_flag{rp.PopRaw<SourceFlag>()};
149 149
150 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); 150 LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
@@ -156,9 +156,9 @@ private:
156 return; 156 return;
157 } 157 }
158 158
159 IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)}; 159 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
160 rb.Push(ResultSuccess); 160 rb.Push(ResultSuccess);
161 rb.PushRaw<MiiInfo>(*result); 161 rb.PushRaw<CharInfo>(*result);
162 } 162 }
163 163
164 void BuildRandom(Kernel::HLERequestContext& ctx) { 164 void BuildRandom(Kernel::HLERequestContext& ctx) {
@@ -191,9 +191,9 @@ private:
191 return; 191 return;
192 } 192 }
193 193
194 IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)}; 194 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
195 rb.Push(ResultSuccess); 195 rb.Push(ResultSuccess);
196 rb.PushRaw<MiiInfo>(manager.BuildRandom(age, gender, race)); 196 rb.PushRaw<CharInfo>(manager.BuildRandom(age, gender, race));
197 } 197 }
198 198
199 void BuildDefault(Kernel::HLERequestContext& ctx) { 199 void BuildDefault(Kernel::HLERequestContext& ctx) {
@@ -210,14 +210,14 @@ private:
210 return; 210 return;
211 } 211 }
212 212
213 IPC::ResponseBuilder rb{ctx, 2 + sizeof(MiiInfo) / sizeof(u32)}; 213 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
214 rb.Push(ResultSuccess); 214 rb.Push(ResultSuccess);
215 rb.PushRaw<MiiInfo>(manager.BuildDefault(index)); 215 rb.PushRaw<CharInfo>(manager.BuildDefault(index));
216 } 216 }
217 217
218 void GetIndex(Kernel::HLERequestContext& ctx) { 218 void GetIndex(Kernel::HLERequestContext& ctx) {
219 IPC::RequestParser rp{ctx}; 219 IPC::RequestParser rp{ctx};
220 const auto info{rp.PopRaw<MiiInfo>()}; 220 const auto info{rp.PopRaw<CharInfo>()};
221 221
222 LOG_DEBUG(Service_Mii, "called"); 222 LOG_DEBUG(Service_Mii, "called");
223 223
@@ -239,6 +239,18 @@ private:
239 rb.Push(ResultSuccess); 239 rb.Push(ResultSuccess);
240 } 240 }
241 241
242 void Convert(Kernel::HLERequestContext& ctx) {
243 IPC::RequestParser rp{ctx};
244
245 const auto mii_v3{rp.PopRaw<Ver3StoreData>()};
246
247 LOG_INFO(Service_Mii, "called");
248
249 IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
250 rb.Push(ResultSuccess);
251 rb.PushRaw<CharInfo>(manager.ConvertV3ToCharInfo(mii_v3));
252 }
253
242 constexpr bool IsInterfaceVersionSupported(u32 interface_version) const { 254 constexpr bool IsInterfaceVersionSupported(u32 interface_version) const {
243 return current_interface_version >= interface_version; 255 return current_interface_version >= interface_version;
244 } 256 }
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
index 544c92a00..c484a9c8d 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -42,7 +42,7 @@ std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& i
42 return out; 42 return out;
43} 43}
44 44
45MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) { 45CharInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
46 MiiStoreBitFields bf; 46 MiiStoreBitFields bf;
47 std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields)); 47 std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields));
48 48
@@ -409,8 +409,8 @@ u32 MiiManager::GetCount(SourceFlag source_flag) const {
409 return static_cast<u32>(count); 409 return static_cast<u32>(count);
410} 410}
411 411
412ResultVal<MiiInfo> MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info, 412ResultVal<CharInfo> MiiManager::UpdateLatest([[maybe_unused]] const CharInfo& info,
413 SourceFlag source_flag) { 413 SourceFlag source_flag) {
414 if ((source_flag & SourceFlag::Database) == SourceFlag::None) { 414 if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
415 return ERROR_CANNOT_FIND_ENTRY; 415 return ERROR_CANNOT_FIND_ENTRY;
416 } 416 }
@@ -419,14 +419,91 @@ ResultVal<MiiInfo> MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info
419 return ERROR_CANNOT_FIND_ENTRY; 419 return ERROR_CANNOT_FIND_ENTRY;
420} 420}
421 421
422MiiInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { 422CharInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) {
423 return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id)); 423 return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id));
424} 424}
425 425
426MiiInfo MiiManager::BuildDefault(std::size_t index) { 426CharInfo MiiManager::BuildDefault(std::size_t index) {
427 return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id)); 427 return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id));
428} 428}
429 429
430CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const {
431 Service::Mii::MiiManager manager;
432 auto mii = manager.BuildDefault(0);
433
434 // Check if mii data exist
435 if (mii_v3.mii_name[0] == 0) {
436 return mii;
437 }
438
439 // TODO: We are ignoring a bunch of data from the mii_v3
440
441 mii.gender = static_cast<u8>(mii_v3.mii_information.gender);
442 mii.favorite_color = static_cast<u8>(mii_v3.mii_information.favorite_color);
443 mii.height = mii_v3.height;
444 mii.build = mii_v3.build;
445
446 memset(mii.name.data(), 0, sizeof(mii.name));
447 memcpy(mii.name.data(), mii_v3.mii_name.data(), sizeof(mii_v3.mii_name));
448 mii.font_region = mii_v3.region_information.character_set;
449
450 mii.faceline_type = mii_v3.appearance_bits1.face_shape;
451 mii.faceline_color = mii_v3.appearance_bits1.skin_color;
452 mii.faceline_wrinkle = mii_v3.appearance_bits2.wrinkles;
453 mii.faceline_make = mii_v3.appearance_bits2.makeup;
454
455 mii.hair_type = mii_v3.hair_style;
456 mii.hair_color = mii_v3.appearance_bits3.hair_color;
457 mii.hair_flip = mii_v3.appearance_bits3.flip_hair;
458
459 mii.eye_type = static_cast<u8>(mii_v3.appearance_bits4.eye_type);
460 mii.eye_color = static_cast<u8>(mii_v3.appearance_bits4.eye_color);
461 mii.eye_scale = static_cast<u8>(mii_v3.appearance_bits4.eye_scale);
462 mii.eye_aspect = static_cast<u8>(mii_v3.appearance_bits4.eye_vertical_stretch);
463 mii.eye_rotate = static_cast<u8>(mii_v3.appearance_bits4.eye_rotation);
464 mii.eye_x = static_cast<u8>(mii_v3.appearance_bits4.eye_spacing);
465 mii.eye_y = static_cast<u8>(mii_v3.appearance_bits4.eye_y_position);
466
467 mii.eyebrow_type = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_style);
468 mii.eyebrow_color = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_color);
469 mii.eyebrow_scale = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_scale);
470 mii.eyebrow_aspect = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_yscale);
471 mii.eyebrow_rotate = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_rotation);
472 mii.eyebrow_x = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_spacing);
473 mii.eyebrow_y = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_y_position);
474
475 mii.nose_type = static_cast<u8>(mii_v3.appearance_bits6.nose_type);
476 mii.nose_scale = static_cast<u8>(mii_v3.appearance_bits6.nose_scale);
477 mii.nose_y = static_cast<u8>(mii_v3.appearance_bits6.nose_y_position);
478
479 mii.mouth_type = static_cast<u8>(mii_v3.appearance_bits7.mouth_type);
480 mii.mouth_color = static_cast<u8>(mii_v3.appearance_bits7.mouth_color);
481 mii.mouth_scale = static_cast<u8>(mii_v3.appearance_bits7.mouth_scale);
482 mii.mouth_aspect = static_cast<u8>(mii_v3.appearance_bits7.mouth_horizontal_stretch);
483 mii.mouth_y = static_cast<u8>(mii_v3.appearance_bits8.mouth_y_position);
484
485 mii.mustache_type = static_cast<u8>(mii_v3.appearance_bits8.mustache_type);
486 mii.mustache_scale = static_cast<u8>(mii_v3.appearance_bits9.mustache_scale);
487 mii.mustache_y = static_cast<u8>(mii_v3.appearance_bits9.mustache_y_position);
488
489 mii.beard_type = static_cast<u8>(mii_v3.appearance_bits9.bear_type);
490 mii.beard_color = static_cast<u8>(mii_v3.appearance_bits9.facial_hair_color);
491
492 mii.glasses_type = static_cast<u8>(mii_v3.appearance_bits10.glasses_type);
493 mii.glasses_color = static_cast<u8>(mii_v3.appearance_bits10.glasses_color);
494 mii.glasses_scale = static_cast<u8>(mii_v3.appearance_bits10.glasses_scale);
495 mii.glasses_y = static_cast<u8>(mii_v3.appearance_bits10.glasses_y_position);
496
497 mii.mole_type = static_cast<u8>(mii_v3.appearance_bits11.mole_enabled);
498 mii.mole_scale = static_cast<u8>(mii_v3.appearance_bits11.mole_scale);
499 mii.mole_x = static_cast<u8>(mii_v3.appearance_bits11.mole_x_position);
500 mii.mole_y = static_cast<u8>(mii_v3.appearance_bits11.mole_y_position);
501
502 // TODO: Validate mii data
503
504 return mii;
505}
506
430ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) { 507ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) {
431 std::vector<MiiInfoElement> result; 508 std::vector<MiiInfoElement> result;
432 509
@@ -441,7 +518,7 @@ ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_
441 return result; 518 return result;
442} 519}
443 520
444Result MiiManager::GetIndex([[maybe_unused]] const MiiInfo& info, u32& index) { 521Result MiiManager::GetIndex([[maybe_unused]] const CharInfo& info, u32& index) {
445 constexpr u32 INVALID_INDEX{0xFFFFFFFF}; 522 constexpr u32 INVALID_INDEX{0xFFFFFFFF};
446 523
447 index = INVALID_INDEX; 524 index = INVALID_INDEX;
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h
index 6a286bd96..d847de0bd 100644
--- a/src/core/hle/service/mii/mii_manager.h
+++ b/src/core/hle/service/mii/mii_manager.h
@@ -19,11 +19,12 @@ public:
19 bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter); 19 bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter);
20 bool IsFullDatabase() const; 20 bool IsFullDatabase() const;
21 u32 GetCount(SourceFlag source_flag) const; 21 u32 GetCount(SourceFlag source_flag) const;
22 ResultVal<MiiInfo> UpdateLatest(const MiiInfo& info, SourceFlag source_flag); 22 ResultVal<CharInfo> UpdateLatest(const CharInfo& info, SourceFlag source_flag);
23 MiiInfo BuildRandom(Age age, Gender gender, Race race); 23 CharInfo BuildRandom(Age age, Gender gender, Race race);
24 MiiInfo BuildDefault(std::size_t index); 24 CharInfo BuildDefault(std::size_t index);
25 CharInfo ConvertV3ToCharInfo(Ver3StoreData mii_v3) const;
25 ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag); 26 ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag);
26 Result GetIndex(const MiiInfo& info, u32& index); 27 Result GetIndex(const CharInfo& info, u32& index);
27 28
28private: 29private:
29 const Common::UUID user_id{}; 30 const Common::UUID user_id{};
diff --git a/src/core/hle/service/mii/types.h b/src/core/hle/service/mii/types.h
index 45edbfeae..9e3247397 100644
--- a/src/core/hle/service/mii/types.h
+++ b/src/core/hle/service/mii/types.h
@@ -86,7 +86,8 @@ enum class SourceFlag : u32 {
86}; 86};
87DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); 87DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
88 88
89struct MiiInfo { 89// nn::mii::CharInfo
90struct CharInfo {
90 Common::UUID uuid; 91 Common::UUID uuid;
91 std::array<char16_t, 11> name; 92 std::array<char16_t, 11> name;
92 u8 font_region; 93 u8 font_region;
@@ -140,16 +141,16 @@ struct MiiInfo {
140 u8 mole_y; 141 u8 mole_y;
141 u8 padding; 142 u8 padding;
142}; 143};
143static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size."); 144static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
144static_assert(std::has_unique_object_representations_v<MiiInfo>, 145static_assert(std::has_unique_object_representations_v<CharInfo>,
145 "All bits of MiiInfo must contribute to its value."); 146 "All bits of CharInfo must contribute to its value.");
146 147
147#pragma pack(push, 4) 148#pragma pack(push, 4)
148 149
149struct MiiInfoElement { 150struct MiiInfoElement {
150 MiiInfoElement(const MiiInfo& info_, Source source_) : info{info_}, source{source_} {} 151 MiiInfoElement(const CharInfo& info_, Source source_) : info{info_}, source{source_} {}
151 152
152 MiiInfo info{}; 153 CharInfo info{};
153 Source source{}; 154 Source source{};
154}; 155};
155static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size."); 156static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size.");
@@ -243,6 +244,131 @@ static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrec
243static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>, 244static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>,
244 "MiiStoreBitFields is not trivially copyable."); 245 "MiiStoreBitFields is not trivially copyable.");
245 246
247// This is nn::mii::Ver3StoreData
248// Based on citra HLE::Applets::MiiData and PretendoNetwork.
249// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48
250// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299
251struct Ver3StoreData {
252 u8 version;
253 union {
254 u8 raw;
255
256 BitField<0, 1, u8> allow_copying;
257 BitField<1, 1, u8> profanity_flag;
258 BitField<2, 2, u8> region_lock;
259 BitField<4, 2, u8> character_set;
260 } region_information;
261 u16_be mii_id;
262 u64_be system_id;
263 u32_be specialness_and_creation_date;
264 std::array<u8, 0x6> creator_mac;
265 u16_be padding;
266 union {
267 u16 raw;
268
269 BitField<0, 1, u16> gender;
270 BitField<1, 4, u16> birth_month;
271 BitField<5, 5, u16> birth_day;
272 BitField<10, 4, u16> favorite_color;
273 BitField<14, 1, u16> favorite;
274 } mii_information;
275 std::array<char16_t, 0xA> mii_name;
276 u8 height;
277 u8 build;
278 union {
279 u8 raw;
280
281 BitField<0, 1, u8> disable_sharing;
282 BitField<1, 4, u8> face_shape;
283 BitField<5, 3, u8> skin_color;
284 } appearance_bits1;
285 union {
286 u8 raw;
287
288 BitField<0, 4, u8> wrinkles;
289 BitField<4, 4, u8> makeup;
290 } appearance_bits2;
291 u8 hair_style;
292 union {
293 u8 raw;
294
295 BitField<0, 3, u8> hair_color;
296 BitField<3, 1, u8> flip_hair;
297 } appearance_bits3;
298 union {
299 u32 raw;
300
301 BitField<0, 6, u32> eye_type;
302 BitField<6, 3, u32> eye_color;
303 BitField<9, 4, u32> eye_scale;
304 BitField<13, 3, u32> eye_vertical_stretch;
305 BitField<16, 5, u32> eye_rotation;
306 BitField<21, 4, u32> eye_spacing;
307 BitField<25, 5, u32> eye_y_position;
308 } appearance_bits4;
309 union {
310 u32 raw;
311
312 BitField<0, 5, u32> eyebrow_style;
313 BitField<5, 3, u32> eyebrow_color;
314 BitField<8, 4, u32> eyebrow_scale;
315 BitField<12, 3, u32> eyebrow_yscale;
316 BitField<16, 4, u32> eyebrow_rotation;
317 BitField<21, 4, u32> eyebrow_spacing;
318 BitField<25, 5, u32> eyebrow_y_position;
319 } appearance_bits5;
320 union {
321 u16 raw;
322
323 BitField<0, 5, u16> nose_type;
324 BitField<5, 4, u16> nose_scale;
325 BitField<9, 5, u16> nose_y_position;
326 } appearance_bits6;
327 union {
328 u16 raw;
329
330 BitField<0, 6, u16> mouth_type;
331 BitField<6, 3, u16> mouth_color;
332 BitField<9, 4, u16> mouth_scale;
333 BitField<13, 3, u16> mouth_horizontal_stretch;
334 } appearance_bits7;
335 union {
336 u8 raw;
337
338 BitField<0, 5, u8> mouth_y_position;
339 BitField<5, 3, u8> mustache_type;
340 } appearance_bits8;
341 u8 allow_copying;
342 union {
343 u16 raw;
344
345 BitField<0, 3, u16> bear_type;
346 BitField<3, 3, u16> facial_hair_color;
347 BitField<6, 4, u16> mustache_scale;
348 BitField<10, 5, u16> mustache_y_position;
349 } appearance_bits9;
350 union {
351 u16 raw;
352
353 BitField<0, 4, u16> glasses_type;
354 BitField<4, 3, u16> glasses_color;
355 BitField<7, 4, u16> glasses_scale;
356 BitField<11, 5, u16> glasses_y_position;
357 } appearance_bits10;
358 union {
359 u16 raw;
360
361 BitField<0, 1, u16> mole_enabled;
362 BitField<1, 4, u16> mole_scale;
363 BitField<5, 5, u16> mole_x_position;
364 BitField<10, 5, u16> mole_y_position;
365 } appearance_bits11;
366
367 std::array<u16_le, 0xA> author_name;
368 INSERT_PADDING_BYTES(0x4);
369};
370static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size");
371
246struct MiiStoreData { 372struct MiiStoreData {
247 using Name = std::array<char16_t, 10>; 373 using Name = std::array<char16_t, 10>;
248 374
diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfp/amiibo_crypto.cpp
new file mode 100644
index 000000000..31dd3a307
--- /dev/null
+++ b/src/core/hle/service/nfp/amiibo_crypto.cpp
@@ -0,0 +1,383 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4// SPDX-FileCopyrightText: Copyright 2017 socram8888/amiitool
5// SPDX-License-Identifier: MIT
6
7#include <array>
8#include <mbedtls/aes.h>
9#include <mbedtls/hmac_drbg.h>
10
11#include "common/fs/file.h"
12#include "common/fs/path_util.h"
13#include "common/logging/log.h"
14#include "core/hle/service/mii/mii_manager.h"
15#include "core/hle/service/nfp/amiibo_crypto.h"
16
17namespace Service::NFP::AmiiboCrypto {
18
19bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
20 const auto& amiibo_data = ntag_file.user_memory;
21 LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", ntag_file.static_lock);
22 LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", ntag_file.compability_container);
23 LOG_INFO(Service_NFP, "write_count={}", amiibo_data.write_counter);
24
25 LOG_INFO(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
26 LOG_INFO(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
27 LOG_INFO(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
28 LOG_INFO(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number);
29 LOG_INFO(Service_NFP, "series={}", amiibo_data.model_info.series);
30 LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.constant_value);
31
32 LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock);
33 LOG_DEBUG(Service_NFP, "tag_CFG0=0x{0:x}", ntag_file.CFG0);
34 LOG_DEBUG(Service_NFP, "tag_CFG1=0x{0:x}", ntag_file.CFG1);
35
36 // Validate UUID
37 constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3`
38 if ((CT ^ ntag_file.uuid[0] ^ ntag_file.uuid[1] ^ ntag_file.uuid[2]) != ntag_file.uuid[3]) {
39 return false;
40 }
41 if ((ntag_file.uuid[4] ^ ntag_file.uuid[5] ^ ntag_file.uuid[6] ^ ntag_file.uuid[7]) !=
42 ntag_file.uuid[8]) {
43 return false;
44 }
45
46 // Check against all know constants on an amiibo binary
47 if (ntag_file.static_lock != 0xE00F) {
48 return false;
49 }
50 if (ntag_file.compability_container != 0xEEFF10F1U) {
51 return false;
52 }
53 if (amiibo_data.constant_value != 0xA5) {
54 return false;
55 }
56 if (amiibo_data.model_info.constant_value != 0x02) {
57 return false;
58 }
59 // dynamic_lock value apparently is not constant
60 // ntag_file.dynamic_lock == 0x0F0001
61 if (ntag_file.CFG0 != 0x04000000U) {
62 return false;
63 }
64 if (ntag_file.CFG1 != 0x5F) {
65 return false;
66 }
67 return true;
68}
69
70NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
71 NTAG215File encoded_data{};
72
73 memcpy(encoded_data.uuid2.data(), nfc_data.uuid.data() + 0x8, sizeof(encoded_data.uuid2));
74 encoded_data.static_lock = nfc_data.static_lock;
75 encoded_data.compability_container = nfc_data.compability_container;
76 encoded_data.hmac_data = nfc_data.user_memory.hmac_data;
77 encoded_data.constant_value = nfc_data.user_memory.constant_value;
78 encoded_data.write_counter = nfc_data.user_memory.write_counter;
79 encoded_data.settings = nfc_data.user_memory.settings;
80 encoded_data.owner_mii = nfc_data.user_memory.owner_mii;
81 encoded_data.title_id = nfc_data.user_memory.title_id;
82 encoded_data.applicaton_write_counter = nfc_data.user_memory.applicaton_write_counter;
83 encoded_data.application_area_id = nfc_data.user_memory.application_area_id;
84 encoded_data.unknown = nfc_data.user_memory.unknown;
85 encoded_data.hash = nfc_data.user_memory.hash;
86 encoded_data.application_area = nfc_data.user_memory.application_area;
87 encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag;
88 memcpy(encoded_data.uuid.data(), nfc_data.uuid.data(), sizeof(encoded_data.uuid));
89 encoded_data.model_info = nfc_data.user_memory.model_info;
90 encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt;
91 encoded_data.dynamic_lock = nfc_data.dynamic_lock;
92 encoded_data.CFG0 = nfc_data.CFG0;
93 encoded_data.CFG1 = nfc_data.CFG1;
94 encoded_data.password = nfc_data.password;
95
96 return encoded_data;
97}
98
99EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
100 EncryptedNTAG215File nfc_data{};
101
102 memcpy(nfc_data.uuid.data() + 0x8, encoded_data.uuid2.data(), sizeof(encoded_data.uuid2));
103 memcpy(nfc_data.uuid.data(), encoded_data.uuid.data(), sizeof(encoded_data.uuid));
104 nfc_data.static_lock = encoded_data.static_lock;
105 nfc_data.compability_container = encoded_data.compability_container;
106 nfc_data.user_memory.hmac_data = encoded_data.hmac_data;
107 nfc_data.user_memory.constant_value = encoded_data.constant_value;
108 nfc_data.user_memory.write_counter = encoded_data.write_counter;
109 nfc_data.user_memory.settings = encoded_data.settings;
110 nfc_data.user_memory.owner_mii = encoded_data.owner_mii;
111 nfc_data.user_memory.title_id = encoded_data.title_id;
112 nfc_data.user_memory.applicaton_write_counter = encoded_data.applicaton_write_counter;
113 nfc_data.user_memory.application_area_id = encoded_data.application_area_id;
114 nfc_data.user_memory.unknown = encoded_data.unknown;
115 nfc_data.user_memory.hash = encoded_data.hash;
116 nfc_data.user_memory.application_area = encoded_data.application_area;
117 nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag;
118 nfc_data.user_memory.model_info = encoded_data.model_info;
119 nfc_data.user_memory.keygen_salt = encoded_data.keygen_salt;
120 nfc_data.dynamic_lock = encoded_data.dynamic_lock;
121 nfc_data.CFG0 = encoded_data.CFG0;
122 nfc_data.CFG1 = encoded_data.CFG1;
123 nfc_data.password = encoded_data.password;
124
125 return nfc_data;
126}
127
128u32 GetTagPassword(const TagUuid& uuid) {
129 // Verifiy that the generated password is correct
130 u32 password = 0xAA ^ (uuid[1] ^ uuid[3]);
131 password &= (0x55 ^ (uuid[2] ^ uuid[4])) << 8;
132 password &= (0xAA ^ (uuid[3] ^ uuid[5])) << 16;
133 password &= (0x55 ^ (uuid[4] ^ uuid[6])) << 24;
134 return password;
135}
136
137HashSeed GetSeed(const NTAG215File& data) {
138 HashSeed seed{
139 .magic = data.write_counter,
140 .padding = {},
141 .uuid1 = {},
142 .uuid2 = {},
143 .keygen_salt = data.keygen_salt,
144 };
145
146 // Copy the first 8 bytes of uuid
147 memcpy(seed.uuid1.data(), data.uuid.data(), sizeof(seed.uuid1));
148 memcpy(seed.uuid2.data(), data.uuid.data(), sizeof(seed.uuid2));
149
150 return seed;
151}
152
153std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed) {
154 const std::size_t seedPart1Len = sizeof(key.magic_bytes) - key.magic_length;
155 const std::size_t string_size = key.type_string.size();
156 std::vector<u8> output(string_size + seedPart1Len);
157
158 // Copy whole type string
159 memccpy(output.data(), key.type_string.data(), '\0', string_size);
160
161 // Append (16 - magic_length) from the input seed
162 memcpy(output.data() + string_size, &seed, seedPart1Len);
163
164 // Append all bytes from magicBytes
165 output.insert(output.end(), key.magic_bytes.begin(),
166 key.magic_bytes.begin() + key.magic_length);
167
168 output.insert(output.end(), seed.uuid1.begin(), seed.uuid1.end());
169 output.insert(output.end(), seed.uuid2.begin(), seed.uuid2.end());
170
171 for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) {
172 output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i]));
173 }
174
175 return output;
176}
177
178void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
179 const std::vector<u8>& seed) {
180
181 // Initialize context
182 ctx.used = false;
183 ctx.counter = 0;
184 ctx.buffer_size = sizeof(ctx.counter) + seed.size();
185 memcpy(ctx.buffer.data() + sizeof(u16), seed.data(), seed.size());
186
187 // Initialize HMAC context
188 mbedtls_md_init(&hmac_ctx);
189 mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
190 mbedtls_md_hmac_starts(&hmac_ctx, hmac_key.data(), hmac_key.size());
191}
192
193void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output) {
194 // If used at least once, reinitialize the HMAC
195 if (ctx.used) {
196 mbedtls_md_hmac_reset(&hmac_ctx);
197 }
198
199 ctx.used = true;
200
201 // Store counter in big endian, and increment it
202 ctx.buffer[0] = static_cast<u8>(ctx.counter >> 8);
203 ctx.buffer[1] = static_cast<u8>(ctx.counter >> 0);
204 ctx.counter++;
205
206 // Do HMAC magic
207 mbedtls_md_hmac_update(&hmac_ctx, reinterpret_cast<const unsigned char*>(ctx.buffer.data()),
208 ctx.buffer_size);
209 mbedtls_md_hmac_finish(&hmac_ctx, output.data());
210}
211
212DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) {
213 const auto seed = GetSeed(data);
214
215 // Generate internal seed
216 const std::vector<u8> internal_key = GenerateInternalKey(key, seed);
217
218 // Initialize context
219 CryptoCtx ctx{};
220 mbedtls_md_context_t hmac_ctx;
221 CryptoInit(ctx, hmac_ctx, key.hmac_key, internal_key);
222
223 // Generate derived keys
224 DerivedKeys derived_keys{};
225 std::array<DrgbOutput, 2> temp{};
226 CryptoStep(ctx, hmac_ctx, temp[0]);
227 CryptoStep(ctx, hmac_ctx, temp[1]);
228 memcpy(&derived_keys, temp.data(), sizeof(DerivedKeys));
229
230 // Cleanup context
231 mbedtls_md_free(&hmac_ctx);
232
233 return derived_keys;
234}
235
236void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data) {
237 mbedtls_aes_context aes;
238 std::size_t nc_off = 0;
239 std::array<u8, sizeof(keys.aes_iv)> nonce_counter{};
240 std::array<u8, sizeof(keys.aes_iv)> stream_block{};
241
242 const auto aes_key_size = static_cast<u32>(keys.aes_key.size() * 8);
243 mbedtls_aes_setkey_enc(&aes, keys.aes_key.data(), aes_key_size);
244 memcpy(nonce_counter.data(), keys.aes_iv.data(), sizeof(keys.aes_iv));
245
246 constexpr std::size_t encrypted_data_size = HMAC_TAG_START - SETTINGS_START;
247 mbedtls_aes_crypt_ctr(&aes, encrypted_data_size, &nc_off, nonce_counter.data(),
248 stream_block.data(),
249 reinterpret_cast<const unsigned char*>(&in_data.settings),
250 reinterpret_cast<unsigned char*>(&out_data.settings));
251
252 // Copy the rest of the data directly
253 out_data.uuid2 = in_data.uuid2;
254 out_data.static_lock = in_data.static_lock;
255 out_data.compability_container = in_data.compability_container;
256
257 out_data.constant_value = in_data.constant_value;
258 out_data.write_counter = in_data.write_counter;
259
260 out_data.uuid = in_data.uuid;
261 out_data.model_info = in_data.model_info;
262 out_data.keygen_salt = in_data.keygen_salt;
263 out_data.dynamic_lock = in_data.dynamic_lock;
264 out_data.CFG0 = in_data.CFG0;
265 out_data.CFG1 = in_data.CFG1;
266 out_data.password = in_data.password;
267}
268
269bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) {
270 const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
271
272 const Common::FS::IOFile keys_file{yuzu_keys_dir / "key_retail.bin",
273 Common::FS::FileAccessMode::Read,
274 Common::FS::FileType::BinaryFile};
275
276 if (!keys_file.IsOpen()) {
277 LOG_ERROR(Service_NFP, "No keys detected");
278 return false;
279 }
280
281 if (keys_file.Read(unfixed_info) != 1) {
282 LOG_ERROR(Service_NFP, "Failed to read unfixed_info");
283 return false;
284 }
285 if (keys_file.Read(locked_secret) != 1) {
286 LOG_ERROR(Service_NFP, "Failed to read locked-secret");
287 return false;
288 }
289
290 return true;
291}
292
293bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) {
294 InternalKey locked_secret{};
295 InternalKey unfixed_info{};
296
297 if (!LoadKeys(locked_secret, unfixed_info)) {
298 return false;
299 }
300
301 // Generate keys
302 NTAG215File encoded_data = NfcDataToEncodedData(encrypted_tag_data);
303 const auto data_keys = GenerateKey(unfixed_info, encoded_data);
304 const auto tag_keys = GenerateKey(locked_secret, encoded_data);
305
306 // Decrypt
307 Cipher(data_keys, encoded_data, tag_data);
308
309 // Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC!
310 constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
311 mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
312 sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uuid),
313 input_length, reinterpret_cast<unsigned char*>(&tag_data.hmac_tag));
314
315 // Regenerate data HMAC
316 constexpr std::size_t input_length2 = DYNAMIC_LOCK_START - WRITE_COUNTER_START;
317 mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), data_keys.hmac_key.data(),
318 sizeof(HmacKey),
319 reinterpret_cast<const unsigned char*>(&tag_data.write_counter), input_length2,
320 reinterpret_cast<unsigned char*>(&tag_data.hmac_data));
321
322 if (tag_data.hmac_data != encrypted_tag_data.user_memory.hmac_data) {
323 LOG_ERROR(Service_NFP, "hmac_data doesn't match");
324 return false;
325 }
326
327 if (tag_data.hmac_tag != encrypted_tag_data.user_memory.hmac_tag) {
328 LOG_ERROR(Service_NFP, "hmac_tag doesn't match");
329 return false;
330 }
331
332 return true;
333}
334
335bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data) {
336 InternalKey locked_secret{};
337 InternalKey unfixed_info{};
338
339 if (!LoadKeys(locked_secret, unfixed_info)) {
340 return false;
341 }
342
343 // Generate keys
344 const auto data_keys = GenerateKey(unfixed_info, tag_data);
345 const auto tag_keys = GenerateKey(locked_secret, tag_data);
346
347 NTAG215File encoded_tag_data{};
348
349 // Generate tag HMAC
350 constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
351 constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START;
352 mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
353 sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uuid),
354 input_length, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag));
355
356 // Init mbedtls HMAC context
357 mbedtls_md_context_t ctx;
358 mbedtls_md_init(&ctx);
359 mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
360
361 // Generate data HMAC
362 mbedtls_md_hmac_starts(&ctx, data_keys.hmac_key.data(), sizeof(HmacKey));
363 mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.write_counter),
364 input_length2); // Data
365 mbedtls_md_hmac_update(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag),
366 sizeof(HashData)); // Tag HMAC
367 mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.uuid),
368 input_length);
369 mbedtls_md_hmac_finish(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data));
370
371 // HMAC cleanup
372 mbedtls_md_free(&ctx);
373
374 // Encrypt
375 Cipher(data_keys, tag_data, encoded_tag_data);
376
377 // Convert back to hardware
378 encrypted_tag_data = EncodedDataToNfcData(encoded_tag_data);
379
380 return true;
381}
382
383} // namespace Service::NFP::AmiiboCrypto
diff --git a/src/core/hle/service/nfp/amiibo_crypto.h b/src/core/hle/service/nfp/amiibo_crypto.h
new file mode 100644
index 000000000..af7335912
--- /dev/null
+++ b/src/core/hle/service/nfp/amiibo_crypto.h
@@ -0,0 +1,98 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "core/hle/service/nfp/amiibo_types.h"
9
10struct mbedtls_md_context_t;
11
12namespace Service::NFP::AmiiboCrypto {
13// Byte locations in Service::NFP::NTAG215File
14constexpr std::size_t HMAC_DATA_START = 0x8;
15constexpr std::size_t SETTINGS_START = 0x2c;
16constexpr std::size_t WRITE_COUNTER_START = 0x29;
17constexpr std::size_t HMAC_TAG_START = 0x1B4;
18constexpr std::size_t UUID_START = 0x1D4;
19constexpr std::size_t DYNAMIC_LOCK_START = 0x208;
20
21using HmacKey = std::array<u8, 0x10>;
22using DrgbOutput = std::array<u8, 0x20>;
23
24struct HashSeed {
25 u16 magic;
26 std::array<u8, 0xE> padding;
27 std::array<u8, 0x8> uuid1;
28 std::array<u8, 0x8> uuid2;
29 std::array<u8, 0x20> keygen_salt;
30};
31static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size");
32
33struct InternalKey {
34 HmacKey hmac_key;
35 std::array<char, 0xE> type_string;
36 u8 reserved;
37 u8 magic_length;
38 std::array<u8, 0x10> magic_bytes;
39 std::array<u8, 0x20> xor_pad;
40};
41static_assert(sizeof(InternalKey) == 0x50, "InternalKey is an invalid size");
42static_assert(std::is_trivially_copyable_v<InternalKey>, "InternalKey must be trivially copyable.");
43
44struct CryptoCtx {
45 std::array<char, 480> buffer;
46 bool used;
47 std::size_t buffer_size;
48 s16 counter;
49};
50
51struct DerivedKeys {
52 std::array<u8, 0x10> aes_key;
53 std::array<u8, 0x10> aes_iv;
54 std::array<u8, 0x10> hmac_key;
55};
56static_assert(sizeof(DerivedKeys) == 0x30, "DerivedKeys is an invalid size");
57
58/// Validates that the amiibo file is not corrupted
59bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file);
60
61/// Converts from encrypted file format to encoded file format
62NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data);
63
64/// Converts from encoded file format to encrypted file format
65EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data);
66
67/// Returns password needed to allow write access to protected memory
68u32 GetTagPassword(const TagUuid& uuid);
69
70// Generates Seed needed for key derivation
71HashSeed GetSeed(const NTAG215File& data);
72
73// Middle step on the generation of derived keys
74std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed);
75
76// Initializes mbedtls context
77void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
78 const std::vector<u8>& seed);
79
80// Feeds data to mbedtls context to generate the derived key
81void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output);
82
83// Generates the derived key from amiibo data
84DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data);
85
86// Encodes or decodes amiibo data
87void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data);
88
89/// Loads both amiibo keys from key_retail.bin
90bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info);
91
92/// Decodes encripted amiibo data returns true if output is valid
93bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data);
94
95/// Encodes plain amiibo data returns true if output is valid
96bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data);
97
98} // namespace Service::NFP::AmiiboCrypto
diff --git a/src/core/hle/service/nfp/amiibo_types.h b/src/core/hle/service/nfp/amiibo_types.h
new file mode 100644
index 000000000..bf2de811a
--- /dev/null
+++ b/src/core/hle/service/nfp/amiibo_types.h
@@ -0,0 +1,197 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <array>
7
8#include "core/hle/service/mii/types.h"
9
10namespace Service::NFP {
11static constexpr std::size_t amiibo_name_length = 0xA;
12
13enum class ServiceType : u32 {
14 User,
15 Debug,
16 System,
17};
18
19enum class State : u32 {
20 NonInitialized,
21 Initialized,
22};
23
24enum class DeviceState : u32 {
25 Initialized,
26 SearchingForTag,
27 TagFound,
28 TagRemoved,
29 TagMounted,
30 Unaviable,
31 Finalized,
32};
33
34enum class ModelType : u32 {
35 Amiibo,
36};
37
38enum class MountTarget : u32 {
39 Rom,
40 Ram,
41 All,
42};
43
44enum class AmiiboType : u8 {
45 Figure,
46 Card,
47 Yarn,
48};
49
50enum class AmiiboSeries : u8 {
51 SuperSmashBros,
52 SuperMario,
53 ChibiRobo,
54 YoshiWoollyWorld,
55 Splatoon,
56 AnimalCrossing,
57 EightBitMario,
58 Skylanders,
59 Unknown8,
60 TheLegendOfZelda,
61 ShovelKnight,
62 Unknown11,
63 Kiby,
64 Pokemon,
65 MarioSportsSuperstars,
66 MonsterHunter,
67 BoxBoy,
68 Pikmin,
69 FireEmblem,
70 Metroid,
71 Others,
72 MegaMan,
73 Diablo,
74};
75
76using TagUuid = std::array<u8, 10>;
77using HashData = std::array<u8, 0x20>;
78using ApplicationArea = std::array<u8, 0xD8>;
79
80struct AmiiboDate {
81 u16 raw_date{};
82
83 u16 GetYear() const {
84 return static_cast<u16>(((raw_date & 0xFE00) >> 9) + 2000);
85 }
86 u8 GetMonth() const {
87 return static_cast<u8>(((raw_date & 0x01E0) >> 5) - 1);
88 }
89 u8 GetDay() const {
90 return static_cast<u8>(raw_date & 0x001F);
91 }
92};
93static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size");
94
95struct Settings {
96 union {
97 u8 raw{};
98
99 BitField<4, 1, u8> amiibo_initialized;
100 BitField<5, 1, u8> appdata_initialized;
101 };
102};
103static_assert(sizeof(Settings) == 1, "AmiiboDate is an invalid size");
104
105struct AmiiboSettings {
106 Settings settings;
107 u8 country_code_id;
108 u16_be crc_counter; // Incremented each time crc is changed
109 AmiiboDate init_date;
110 AmiiboDate write_date;
111 u32_be crc;
112 std::array<u16_be, amiibo_name_length> amiibo_name; // UTF-16 text
113};
114static_assert(sizeof(AmiiboSettings) == 0x20, "AmiiboSettings is an invalid size");
115
116struct AmiiboModelInfo {
117 u16 character_id;
118 u8 character_variant;
119 AmiiboType amiibo_type;
120 u16 model_number;
121 AmiiboSeries series;
122 u8 constant_value; // Must be 02
123 INSERT_PADDING_BYTES(0x4); // Unknown
124};
125static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size");
126
127struct NTAG215Password {
128 u32 PWD; // Password to allow write access
129 u16 PACK; // Password acknowledge reply
130 u16 RFUI; // Reserved for future use
131};
132static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size");
133
134#pragma pack(1)
135struct EncryptedAmiiboFile {
136 u8 constant_value; // Must be A5
137 u16 write_counter; // Number of times the amiibo has been written?
138 INSERT_PADDING_BYTES(0x1); // Unknown 1
139 AmiiboSettings settings; // Encrypted amiibo settings
140 HashData hmac_tag; // Hash
141 AmiiboModelInfo model_info; // Encrypted amiibo model info
142 HashData keygen_salt; // Salt
143 HashData hmac_data; // Hash
144 Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
145 u64_be title_id; // Encrypted Game id
146 u16_be applicaton_write_counter; // Encrypted Counter
147 u32_be application_area_id; // Encrypted Game id
148 std::array<u8, 0x2> unknown;
149 HashData hash; // Probably a SHA256-HMAC hash?
150 ApplicationArea application_area; // Encrypted Game data
151};
152static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
153
154struct NTAG215File {
155 std::array<u8, 0x2> uuid2;
156 u16 static_lock; // Set defined pages as read only
157 u32 compability_container; // Defines available memory
158 HashData hmac_data; // Hash
159 u8 constant_value; // Must be A5
160 u16 write_counter; // Number of times the amiibo has been written?
161 INSERT_PADDING_BYTES(0x1); // Unknown 1
162 AmiiboSettings settings;
163 Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
164 u64_be title_id;
165 u16_be applicaton_write_counter; // Encrypted Counter
166 u32_be application_area_id;
167 std::array<u8, 0x2> unknown;
168 HashData hash; // Probably a SHA256-HMAC hash?
169 ApplicationArea application_area; // Encrypted Game data
170 HashData hmac_tag; // Hash
171 std::array<u8, 0x8> uuid;
172 AmiiboModelInfo model_info;
173 HashData keygen_salt; // Salt
174 u32 dynamic_lock; // Dynamic lock
175 u32 CFG0; // Defines memory protected by password
176 u32 CFG1; // Defines number of verification attempts
177 NTAG215Password password; // Password data
178};
179static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size");
180static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be trivially copyable.");
181#pragma pack()
182
183struct EncryptedNTAG215File {
184 TagUuid uuid; // Unique serial number
185 u16 static_lock; // Set defined pages as read only
186 u32 compability_container; // Defines available memory
187 EncryptedAmiiboFile user_memory; // Writable data
188 u32 dynamic_lock; // Dynamic lock
189 u32 CFG0; // Defines memory protected by password
190 u32 CFG1; // Defines number of verification attempts
191 NTAG215Password password; // Password data
192};
193static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an invalid size");
194static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>,
195 "EncryptedNTAG215File must be trivially copyable.");
196
197} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index 6c5b41dd1..037b86653 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -4,7 +4,10 @@
4#include <array> 4#include <array>
5#include <atomic> 5#include <atomic>
6 6
7#include "common/fs/file.h"
8#include "common/fs/path_util.h"
7#include "common/logging/log.h" 9#include "common/logging/log.h"
10#include "common/string_util.h"
8#include "core/core.h" 11#include "core/core.h"
9#include "core/hid/emulated_controller.h" 12#include "core/hid/emulated_controller.h"
10#include "core/hid/hid_core.h" 13#include "core/hid/hid_core.h"
@@ -12,6 +15,7 @@
12#include "core/hle/ipc_helpers.h" 15#include "core/hle/ipc_helpers.h"
13#include "core/hle/kernel/k_event.h" 16#include "core/hle/kernel/k_event.h"
14#include "core/hle/service/mii/mii_manager.h" 17#include "core/hle/service/mii/mii_manager.h"
18#include "core/hle/service/nfp/amiibo_crypto.h"
15#include "core/hle/service/nfp/nfp.h" 19#include "core/hle/service/nfp/nfp.h"
16#include "core/hle/service/nfp/nfp_user.h" 20#include "core/hle/service/nfp/nfp_user.h"
17 21
@@ -19,12 +23,14 @@ namespace Service::NFP {
19namespace ErrCodes { 23namespace ErrCodes {
20constexpr Result DeviceNotFound(ErrorModule::NFP, 64); 24constexpr Result DeviceNotFound(ErrorModule::NFP, 64);
21constexpr Result WrongDeviceState(ErrorModule::NFP, 73); 25constexpr Result WrongDeviceState(ErrorModule::NFP, 73);
26constexpr Result NfcDisabled(ErrorModule::NFP, 80);
27constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88);
28constexpr Result TagRemoved(ErrorModule::NFP, 97);
22constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); 29constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
30constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152);
23constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168); 31constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168);
24} // namespace ErrCodes 32} // namespace ErrCodes
25 33
26constexpr u32 ApplicationAreaSize = 0xD8;
27
28IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_) 34IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_)
29 : ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name}, 35 : ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name},
30 nfp_interface{nfp_interface_} { 36 nfp_interface{nfp_interface_} {
@@ -39,7 +45,7 @@ IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_)
39 {7, &IUser::OpenApplicationArea, "OpenApplicationArea"}, 45 {7, &IUser::OpenApplicationArea, "OpenApplicationArea"},
40 {8, &IUser::GetApplicationArea, "GetApplicationArea"}, 46 {8, &IUser::GetApplicationArea, "GetApplicationArea"},
41 {9, &IUser::SetApplicationArea, "SetApplicationArea"}, 47 {9, &IUser::SetApplicationArea, "SetApplicationArea"},
42 {10, nullptr, "Flush"}, 48 {10, &IUser::Flush, "Flush"},
43 {11, nullptr, "Restore"}, 49 {11, nullptr, "Restore"},
44 {12, &IUser::CreateApplicationArea, "CreateApplicationArea"}, 50 {12, &IUser::CreateApplicationArea, "CreateApplicationArea"},
45 {13, &IUser::GetTagInfo, "GetTagInfo"}, 51 {13, &IUser::GetTagInfo, "GetTagInfo"},
@@ -53,7 +59,7 @@ IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_)
53 {21, &IUser::GetNpadId, "GetNpadId"}, 59 {21, &IUser::GetNpadId, "GetNpadId"},
54 {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"}, 60 {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
55 {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, 61 {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
56 {24, nullptr, "RecreateApplicationArea"}, 62 {24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"},
57 }; 63 };
58 RegisterHandlers(functions); 64 RegisterHandlers(functions);
59 65
@@ -87,11 +93,23 @@ void IUser::Finalize(Kernel::HLERequestContext& ctx) {
87void IUser::ListDevices(Kernel::HLERequestContext& ctx) { 93void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
88 LOG_INFO(Service_NFP, "called"); 94 LOG_INFO(Service_NFP, "called");
89 95
96 if (state == State::NonInitialized) {
97 IPC::ResponseBuilder rb{ctx, 2};
98 rb.Push(ErrCodes::NfcDisabled);
99 return;
100 }
101
90 std::vector<u64> devices; 102 std::vector<u64> devices;
91 103
92 // TODO(german77): Loop through all interfaces 104 // TODO(german77): Loop through all interfaces
93 devices.push_back(nfp_interface.GetHandle()); 105 devices.push_back(nfp_interface.GetHandle());
94 106
107 if (devices.size() == 0) {
108 IPC::ResponseBuilder rb{ctx, 2};
109 rb.Push(ErrCodes::DeviceNotFound);
110 return;
111 }
112
95 ctx.WriteBuffer(devices); 113 ctx.WriteBuffer(devices);
96 114
97 IPC::ResponseBuilder rb{ctx, 3}; 115 IPC::ResponseBuilder rb{ctx, 3};
@@ -105,6 +123,12 @@ void IUser::StartDetection(Kernel::HLERequestContext& ctx) {
105 const auto nfp_protocol{rp.Pop<s32>()}; 123 const auto nfp_protocol{rp.Pop<s32>()};
106 LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol); 124 LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
107 125
126 if (state == State::NonInitialized) {
127 IPC::ResponseBuilder rb{ctx, 2};
128 rb.Push(ErrCodes::NfcDisabled);
129 return;
130 }
131
108 // TODO(german77): Loop through all interfaces 132 // TODO(german77): Loop through all interfaces
109 if (device_handle == nfp_interface.GetHandle()) { 133 if (device_handle == nfp_interface.GetHandle()) {
110 const auto result = nfp_interface.StartDetection(nfp_protocol); 134 const auto result = nfp_interface.StartDetection(nfp_protocol);
@@ -124,6 +148,12 @@ void IUser::StopDetection(Kernel::HLERequestContext& ctx) {
124 const auto device_handle{rp.Pop<u64>()}; 148 const auto device_handle{rp.Pop<u64>()};
125 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); 149 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
126 150
151 if (state == State::NonInitialized) {
152 IPC::ResponseBuilder rb{ctx, 2};
153 rb.Push(ErrCodes::NfcDisabled);
154 return;
155 }
156
127 // TODO(german77): Loop through all interfaces 157 // TODO(german77): Loop through all interfaces
128 if (device_handle == nfp_interface.GetHandle()) { 158 if (device_handle == nfp_interface.GetHandle()) {
129 const auto result = nfp_interface.StopDetection(); 159 const auto result = nfp_interface.StopDetection();
@@ -146,6 +176,12 @@ void IUser::Mount(Kernel::HLERequestContext& ctx) {
146 LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle, 176 LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle,
147 model_type, mount_target); 177 model_type, mount_target);
148 178
179 if (state == State::NonInitialized) {
180 IPC::ResponseBuilder rb{ctx, 2};
181 rb.Push(ErrCodes::NfcDisabled);
182 return;
183 }
184
149 // TODO(german77): Loop through all interfaces 185 // TODO(german77): Loop through all interfaces
150 if (device_handle == nfp_interface.GetHandle()) { 186 if (device_handle == nfp_interface.GetHandle()) {
151 const auto result = nfp_interface.Mount(); 187 const auto result = nfp_interface.Mount();
@@ -165,6 +201,12 @@ void IUser::Unmount(Kernel::HLERequestContext& ctx) {
165 const auto device_handle{rp.Pop<u64>()}; 201 const auto device_handle{rp.Pop<u64>()};
166 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); 202 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
167 203
204 if (state == State::NonInitialized) {
205 IPC::ResponseBuilder rb{ctx, 2};
206 rb.Push(ErrCodes::NfcDisabled);
207 return;
208 }
209
168 // TODO(german77): Loop through all interfaces 210 // TODO(german77): Loop through all interfaces
169 if (device_handle == nfp_interface.GetHandle()) { 211 if (device_handle == nfp_interface.GetHandle()) {
170 const auto result = nfp_interface.Unmount(); 212 const auto result = nfp_interface.Unmount();
@@ -186,6 +228,12 @@ void IUser::OpenApplicationArea(Kernel::HLERequestContext& ctx) {
186 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, access_id={}", device_handle, 228 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, access_id={}", device_handle,
187 access_id); 229 access_id);
188 230
231 if (state == State::NonInitialized) {
232 IPC::ResponseBuilder rb{ctx, 2};
233 rb.Push(ErrCodes::NfcDisabled);
234 return;
235 }
236
189 // TODO(german77): Loop through all interfaces 237 // TODO(german77): Loop through all interfaces
190 if (device_handle == nfp_interface.GetHandle()) { 238 if (device_handle == nfp_interface.GetHandle()) {
191 const auto result = nfp_interface.OpenApplicationArea(access_id); 239 const auto result = nfp_interface.OpenApplicationArea(access_id);
@@ -205,9 +253,15 @@ void IUser::GetApplicationArea(Kernel::HLERequestContext& ctx) {
205 const auto device_handle{rp.Pop<u64>()}; 253 const auto device_handle{rp.Pop<u64>()};
206 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); 254 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
207 255
256 if (state == State::NonInitialized) {
257 IPC::ResponseBuilder rb{ctx, 2};
258 rb.Push(ErrCodes::NfcDisabled);
259 return;
260 }
261
208 // TODO(german77): Loop through all interfaces 262 // TODO(german77): Loop through all interfaces
209 if (device_handle == nfp_interface.GetHandle()) { 263 if (device_handle == nfp_interface.GetHandle()) {
210 std::vector<u8> data{}; 264 ApplicationArea data{};
211 const auto result = nfp_interface.GetApplicationArea(data); 265 const auto result = nfp_interface.GetApplicationArea(data);
212 ctx.WriteBuffer(data); 266 ctx.WriteBuffer(data);
213 IPC::ResponseBuilder rb{ctx, 3}; 267 IPC::ResponseBuilder rb{ctx, 3};
@@ -229,6 +283,12 @@ void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) {
229 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}", device_handle, 283 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}", device_handle,
230 data.size()); 284 data.size());
231 285
286 if (state == State::NonInitialized) {
287 IPC::ResponseBuilder rb{ctx, 2};
288 rb.Push(ErrCodes::NfcDisabled);
289 return;
290 }
291
232 // TODO(german77): Loop through all interfaces 292 // TODO(german77): Loop through all interfaces
233 if (device_handle == nfp_interface.GetHandle()) { 293 if (device_handle == nfp_interface.GetHandle()) {
234 const auto result = nfp_interface.SetApplicationArea(data); 294 const auto result = nfp_interface.SetApplicationArea(data);
@@ -243,6 +303,31 @@ void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) {
243 rb.Push(ErrCodes::DeviceNotFound); 303 rb.Push(ErrCodes::DeviceNotFound);
244} 304}
245 305
306void IUser::Flush(Kernel::HLERequestContext& ctx) {
307 IPC::RequestParser rp{ctx};
308 const auto device_handle{rp.Pop<u64>()};
309 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
310
311 if (state == State::NonInitialized) {
312 IPC::ResponseBuilder rb{ctx, 2};
313 rb.Push(ErrCodes::NfcDisabled);
314 return;
315 }
316
317 // TODO(german77): Loop through all interfaces
318 if (device_handle == nfp_interface.GetHandle()) {
319 const auto result = nfp_interface.Flush();
320 IPC::ResponseBuilder rb{ctx, 2};
321 rb.Push(result);
322 return;
323 }
324
325 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
326
327 IPC::ResponseBuilder rb{ctx, 2};
328 rb.Push(ErrCodes::DeviceNotFound);
329}
330
246void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) { 331void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
247 IPC::RequestParser rp{ctx}; 332 IPC::RequestParser rp{ctx};
248 const auto device_handle{rp.Pop<u64>()}; 333 const auto device_handle{rp.Pop<u64>()};
@@ -251,6 +336,12 @@ void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
251 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}", 336 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}",
252 device_handle, access_id, data.size()); 337 device_handle, access_id, data.size());
253 338
339 if (state == State::NonInitialized) {
340 IPC::ResponseBuilder rb{ctx, 2};
341 rb.Push(ErrCodes::NfcDisabled);
342 return;
343 }
344
254 // TODO(german77): Loop through all interfaces 345 // TODO(german77): Loop through all interfaces
255 if (device_handle == nfp_interface.GetHandle()) { 346 if (device_handle == nfp_interface.GetHandle()) {
256 const auto result = nfp_interface.CreateApplicationArea(access_id, data); 347 const auto result = nfp_interface.CreateApplicationArea(access_id, data);
@@ -270,6 +361,12 @@ void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) {
270 const auto device_handle{rp.Pop<u64>()}; 361 const auto device_handle{rp.Pop<u64>()};
271 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); 362 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
272 363
364 if (state == State::NonInitialized) {
365 IPC::ResponseBuilder rb{ctx, 2};
366 rb.Push(ErrCodes::NfcDisabled);
367 return;
368 }
369
273 // TODO(german77): Loop through all interfaces 370 // TODO(german77): Loop through all interfaces
274 if (device_handle == nfp_interface.GetHandle()) { 371 if (device_handle == nfp_interface.GetHandle()) {
275 TagInfo tag_info{}; 372 TagInfo tag_info{};
@@ -291,6 +388,12 @@ void IUser::GetRegisterInfo(Kernel::HLERequestContext& ctx) {
291 const auto device_handle{rp.Pop<u64>()}; 388 const auto device_handle{rp.Pop<u64>()};
292 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); 389 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
293 390
391 if (state == State::NonInitialized) {
392 IPC::ResponseBuilder rb{ctx, 2};
393 rb.Push(ErrCodes::NfcDisabled);
394 return;
395 }
396
294 // TODO(german77): Loop through all interfaces 397 // TODO(german77): Loop through all interfaces
295 if (device_handle == nfp_interface.GetHandle()) { 398 if (device_handle == nfp_interface.GetHandle()) {
296 RegisterInfo register_info{}; 399 RegisterInfo register_info{};
@@ -312,6 +415,12 @@ void IUser::GetCommonInfo(Kernel::HLERequestContext& ctx) {
312 const auto device_handle{rp.Pop<u64>()}; 415 const auto device_handle{rp.Pop<u64>()};
313 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); 416 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
314 417
418 if (state == State::NonInitialized) {
419 IPC::ResponseBuilder rb{ctx, 2};
420 rb.Push(ErrCodes::NfcDisabled);
421 return;
422 }
423
315 // TODO(german77): Loop through all interfaces 424 // TODO(german77): Loop through all interfaces
316 if (device_handle == nfp_interface.GetHandle()) { 425 if (device_handle == nfp_interface.GetHandle()) {
317 CommonInfo common_info{}; 426 CommonInfo common_info{};
@@ -333,6 +442,12 @@ void IUser::GetModelInfo(Kernel::HLERequestContext& ctx) {
333 const auto device_handle{rp.Pop<u64>()}; 442 const auto device_handle{rp.Pop<u64>()};
334 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); 443 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
335 444
445 if (state == State::NonInitialized) {
446 IPC::ResponseBuilder rb{ctx, 2};
447 rb.Push(ErrCodes::NfcDisabled);
448 return;
449 }
450
336 // TODO(german77): Loop through all interfaces 451 // TODO(german77): Loop through all interfaces
337 if (device_handle == nfp_interface.GetHandle()) { 452 if (device_handle == nfp_interface.GetHandle()) {
338 ModelInfo model_info{}; 453 ModelInfo model_info{};
@@ -354,6 +469,12 @@ void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) {
354 const auto device_handle{rp.Pop<u64>()}; 469 const auto device_handle{rp.Pop<u64>()};
355 LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); 470 LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
356 471
472 if (state == State::NonInitialized) {
473 IPC::ResponseBuilder rb{ctx, 2};
474 rb.Push(ErrCodes::NfcDisabled);
475 return;
476 }
477
357 // TODO(german77): Loop through all interfaces 478 // TODO(german77): Loop through all interfaces
358 if (device_handle == nfp_interface.GetHandle()) { 479 if (device_handle == nfp_interface.GetHandle()) {
359 IPC::ResponseBuilder rb{ctx, 2, 1}; 480 IPC::ResponseBuilder rb{ctx, 2, 1};
@@ -373,6 +494,12 @@ void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
373 const auto device_handle{rp.Pop<u64>()}; 494 const auto device_handle{rp.Pop<u64>()};
374 LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); 495 LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
375 496
497 if (state == State::NonInitialized) {
498 IPC::ResponseBuilder rb{ctx, 2};
499 rb.Push(ErrCodes::NfcDisabled);
500 return;
501 }
502
376 // TODO(german77): Loop through all interfaces 503 // TODO(german77): Loop through all interfaces
377 if (device_handle == nfp_interface.GetHandle()) { 504 if (device_handle == nfp_interface.GetHandle()) {
378 IPC::ResponseBuilder rb{ctx, 2, 1}; 505 IPC::ResponseBuilder rb{ctx, 2, 1};
@@ -419,6 +546,12 @@ void IUser::GetNpadId(Kernel::HLERequestContext& ctx) {
419 const auto device_handle{rp.Pop<u64>()}; 546 const auto device_handle{rp.Pop<u64>()};
420 LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); 547 LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
421 548
549 if (state == State::NonInitialized) {
550 IPC::ResponseBuilder rb{ctx, 2};
551 rb.Push(ErrCodes::NfcDisabled);
552 return;
553 }
554
422 // TODO(german77): Loop through all interfaces 555 // TODO(german77): Loop through all interfaces
423 if (device_handle == nfp_interface.GetHandle()) { 556 if (device_handle == nfp_interface.GetHandle()) {
424 IPC::ResponseBuilder rb{ctx, 3}; 557 IPC::ResponseBuilder rb{ctx, 3};
@@ -442,7 +575,7 @@ void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
442 if (device_handle == nfp_interface.GetHandle()) { 575 if (device_handle == nfp_interface.GetHandle()) {
443 IPC::ResponseBuilder rb{ctx, 3}; 576 IPC::ResponseBuilder rb{ctx, 3};
444 rb.Push(ResultSuccess); 577 rb.Push(ResultSuccess);
445 rb.Push(ApplicationAreaSize); 578 rb.Push(sizeof(ApplicationArea));
446 return; 579 return;
447 } 580 }
448 581
@@ -455,11 +588,45 @@ void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
455void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) { 588void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
456 LOG_DEBUG(Service_NFP, "(STUBBED) called"); 589 LOG_DEBUG(Service_NFP, "(STUBBED) called");
457 590
591 if (state == State::NonInitialized) {
592 IPC::ResponseBuilder rb{ctx, 2};
593 rb.Push(ErrCodes::NfcDisabled);
594 return;
595 }
596
458 IPC::ResponseBuilder rb{ctx, 2, 1}; 597 IPC::ResponseBuilder rb{ctx, 2, 1};
459 rb.Push(ResultSuccess); 598 rb.Push(ResultSuccess);
460 rb.PushCopyObjects(availability_change_event->GetReadableEvent()); 599 rb.PushCopyObjects(availability_change_event->GetReadableEvent());
461} 600}
462 601
602void IUser::RecreateApplicationArea(Kernel::HLERequestContext& ctx) {
603 IPC::RequestParser rp{ctx};
604 const auto device_handle{rp.Pop<u64>()};
605 const auto access_id{rp.Pop<u32>()};
606 const auto data{ctx.ReadBuffer()};
607 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}",
608 device_handle, access_id, data.size());
609
610 if (state == State::NonInitialized) {
611 IPC::ResponseBuilder rb{ctx, 2};
612 rb.Push(ErrCodes::NfcDisabled);
613 return;
614 }
615
616 // TODO(german77): Loop through all interfaces
617 if (device_handle == nfp_interface.GetHandle()) {
618 const auto result = nfp_interface.RecreateApplicationArea(access_id, data);
619 IPC::ResponseBuilder rb{ctx, 2};
620 rb.Push(result);
621 return;
622 }
623
624 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
625
626 IPC::ResponseBuilder rb{ctx, 2};
627 rb.Push(ErrCodes::DeviceNotFound);
628}
629
463Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_, 630Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_,
464 const char* name) 631 const char* name)
465 : ServiceFramework{system_, name}, module{std::move(module_)}, 632 : ServiceFramework{system_, name}, module{std::move(module_)},
@@ -478,36 +645,42 @@ void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
478 rb.PushIpcInterface<IUser>(*this, system); 645 rb.PushIpcInterface<IUser>(*this, system);
479} 646}
480 647
481bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) { 648bool Module::Interface::LoadAmiiboFile(const std::string& filename) {
482 if (device_state != DeviceState::SearchingForTag) {
483 LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state);
484 return false;
485 }
486
487 constexpr auto tag_size = sizeof(NTAG215File);
488 constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password); 649 constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password);
650 const Common::FS::IOFile amiibo_file{filename, Common::FS::FileAccessMode::Read,
651 Common::FS::FileType::BinaryFile};
489 652
490 std::vector<u8> amiibo_buffer = buffer; 653 if (!amiibo_file.IsOpen()) {
654 LOG_ERROR(Service_NFP, "Amiibo is already on use");
655 return false;
656 }
491 657
492 if (amiibo_buffer.size() < tag_size_without_password) { 658 // Workaround for files with missing password data
493 LOG_ERROR(Service_NFP, "Wrong file size {}", buffer.size()); 659 std::array<u8, sizeof(EncryptedNTAG215File)> buffer{};
660 if (amiibo_file.Read(buffer) < tag_size_without_password) {
661 LOG_ERROR(Service_NFP, "Failed to read amiibo file");
494 return false; 662 return false;
495 } 663 }
664 memcpy(&encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File));
496 665
497 // Ensure it has the correct size 666 if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
498 if (amiibo_buffer.size() != tag_size) { 667 LOG_INFO(Service_NFP, "Invalid amiibo");
499 amiibo_buffer.resize(tag_size, 0); 668 return false;
500 } 669 }
501 670
502 LOG_INFO(Service_NFP, "Amiibo detected"); 671 file_path = filename;
503 std::memcpy(&tag_data, buffer.data(), tag_size); 672 return true;
673}
504 674
505 if (!IsAmiiboValid()) { 675bool Module::Interface::LoadAmiibo(const std::string& filename) {
676 if (device_state != DeviceState::SearchingForTag) {
677 LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state);
506 return false; 678 return false;
507 } 679 }
508 680
509 // This value can't be dumped from a tag. Generate it 681 if (!LoadAmiiboFile(filename)) {
510 tag_data.password.PWD = GetTagPassword(tag_data.uuid); 682 return false;
683 }
511 684
512 device_state = DeviceState::TagFound; 685 device_state = DeviceState::TagFound;
513 activate_event->GetWritableEvent().Signal(); 686 activate_event->GetWritableEvent().Signal();
@@ -517,55 +690,13 @@ bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) {
517void Module::Interface::CloseAmiibo() { 690void Module::Interface::CloseAmiibo() {
518 LOG_INFO(Service_NFP, "Remove amiibo"); 691 LOG_INFO(Service_NFP, "Remove amiibo");
519 device_state = DeviceState::TagRemoved; 692 device_state = DeviceState::TagRemoved;
693 is_data_decoded = false;
520 is_application_area_initialized = false; 694 is_application_area_initialized = false;
521 application_area_id = 0; 695 encrypted_tag_data = {};
522 application_area_data.clear(); 696 tag_data = {};
523 deactivate_event->GetWritableEvent().Signal(); 697 deactivate_event->GetWritableEvent().Signal();
524} 698}
525 699
526bool Module::Interface::IsAmiiboValid() const {
527 const auto& amiibo_data = tag_data.user_memory;
528 LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", tag_data.lock_bytes);
529 LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", tag_data.compability_container);
530 LOG_DEBUG(Service_NFP, "crypto_init=0x{0:x}", amiibo_data.crypto_init);
531 LOG_DEBUG(Service_NFP, "write_count={}", amiibo_data.write_count);
532
533 LOG_DEBUG(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
534 LOG_DEBUG(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
535 LOG_DEBUG(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
536 LOG_DEBUG(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number);
537 LOG_DEBUG(Service_NFP, "series={}", amiibo_data.model_info.series);
538 LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.fixed);
539
540 LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", tag_data.dynamic_lock);
541 LOG_DEBUG(Service_NFP, "tag_CFG0=0x{0:x}", tag_data.CFG0);
542 LOG_DEBUG(Service_NFP, "tag_CFG1=0x{0:x}", tag_data.CFG1);
543
544 // Check against all know constants on an amiibo binary
545 if (tag_data.lock_bytes != 0xE00F) {
546 return false;
547 }
548 if (tag_data.compability_container != 0xEEFF10F1U) {
549 return false;
550 }
551 if ((amiibo_data.crypto_init & 0xFF) != 0xA5) {
552 return false;
553 }
554 if (amiibo_data.model_info.fixed != 0x02) {
555 return false;
556 }
557 if ((tag_data.dynamic_lock & 0xFFFFFF) != 0x0F0001) {
558 return false;
559 }
560 if (tag_data.CFG0 != 0x04000000U) {
561 return false;
562 }
563 if (tag_data.CFG1 != 0x5F) {
564 return false;
565 }
566 return true;
567}
568
569Kernel::KReadableEvent& Module::Interface::GetActivateEvent() const { 700Kernel::KReadableEvent& Module::Interface::GetActivateEvent() const {
570 return activate_event->GetReadableEvent(); 701 return activate_event->GetReadableEvent();
571} 702}
@@ -576,13 +707,20 @@ Kernel::KReadableEvent& Module::Interface::GetDeactivateEvent() const {
576 707
577void Module::Interface::Initialize() { 708void Module::Interface::Initialize() {
578 device_state = DeviceState::Initialized; 709 device_state = DeviceState::Initialized;
710 is_data_decoded = false;
711 is_application_area_initialized = false;
712 encrypted_tag_data = {};
713 tag_data = {};
579} 714}
580 715
581void Module::Interface::Finalize() { 716void Module::Interface::Finalize() {
717 if (device_state == DeviceState::TagMounted) {
718 Unmount();
719 }
720 if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
721 StopDetection();
722 }
582 device_state = DeviceState::Unaviable; 723 device_state = DeviceState::Unaviable;
583 is_application_area_initialized = false;
584 application_area_id = 0;
585 application_area_data.clear();
586} 724}
587 725
588Result Module::Interface::StartDetection(s32 protocol_) { 726Result Module::Interface::StartDetection(s32 protocol_) {
@@ -618,42 +756,102 @@ Result Module::Interface::StopDetection() {
618 return ErrCodes::WrongDeviceState; 756 return ErrCodes::WrongDeviceState;
619} 757}
620 758
621Result Module::Interface::Mount() { 759Result Module::Interface::Flush() {
622 if (device_state == DeviceState::TagFound) { 760 // Ignore write command if we can't encrypt the data
623 device_state = DeviceState::TagMounted; 761 if (!is_data_decoded) {
624 return ResultSuccess; 762 return ResultSuccess;
625 } 763 }
626 764
627 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); 765 constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password);
628 return ErrCodes::WrongDeviceState; 766 EncryptedNTAG215File tmp_encrypted_tag_data{};
767 const Common::FS::IOFile amiibo_file{file_path, Common::FS::FileAccessMode::ReadWrite,
768 Common::FS::FileType::BinaryFile};
769
770 if (!amiibo_file.IsOpen()) {
771 LOG_ERROR(Core, "Amiibo is already on use");
772 return ErrCodes::WriteAmiiboFailed;
773 }
774
775 // Workaround for files with missing password data
776 std::array<u8, sizeof(EncryptedNTAG215File)> buffer{};
777 if (amiibo_file.Read(buffer) < tag_size_without_password) {
778 LOG_ERROR(Core, "Failed to read amiibo file");
779 return ErrCodes::WriteAmiiboFailed;
780 }
781 memcpy(&tmp_encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File));
782
783 if (!AmiiboCrypto::IsAmiiboValid(tmp_encrypted_tag_data)) {
784 LOG_INFO(Service_NFP, "Invalid amiibo");
785 return ErrCodes::WriteAmiiboFailed;
786 }
787
788 bool is_uuid_equal = memcmp(tmp_encrypted_tag_data.uuid.data(), tag_data.uuid.data(), 8) == 0;
789 bool is_character_equal = tmp_encrypted_tag_data.user_memory.model_info.character_id ==
790 tag_data.model_info.character_id;
791 if (!is_uuid_equal || !is_character_equal) {
792 LOG_ERROR(Service_NFP, "Not the same amiibo");
793 return ErrCodes::WriteAmiiboFailed;
794 }
795
796 if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
797 LOG_ERROR(Service_NFP, "Failed to encode data");
798 return ErrCodes::WriteAmiiboFailed;
799 }
800
801 // Return to the start of the file
802 if (!amiibo_file.Seek(0)) {
803 LOG_ERROR(Service_NFP, "Error writing to file");
804 return ErrCodes::WriteAmiiboFailed;
805 }
806
807 if (!amiibo_file.Write(encrypted_tag_data)) {
808 LOG_ERROR(Service_NFP, "Error writing to file");
809 return ErrCodes::WriteAmiiboFailed;
810 }
811
812 return ResultSuccess;
813}
814
815Result Module::Interface::Mount() {
816 if (device_state != DeviceState::TagFound) {
817 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
818 return ErrCodes::WrongDeviceState;
819 }
820
821 is_data_decoded = AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data);
822 LOG_INFO(Service_NFP, "Is amiibo decoded {}", is_data_decoded);
823
824 is_application_area_initialized = false;
825 device_state = DeviceState::TagMounted;
826 return ResultSuccess;
629} 827}
630 828
631Result Module::Interface::Unmount() { 829Result Module::Interface::Unmount() {
632 if (device_state == DeviceState::TagMounted) { 830 if (device_state != DeviceState::TagMounted) {
633 is_application_area_initialized = false; 831 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
634 application_area_id = 0; 832 return ErrCodes::WrongDeviceState;
635 application_area_data.clear();
636 device_state = DeviceState::TagFound;
637 return ResultSuccess;
638 } 833 }
639 834
640 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); 835 is_data_decoded = false;
641 return ErrCodes::WrongDeviceState; 836 is_application_area_initialized = false;
837 device_state = DeviceState::TagFound;
838 return ResultSuccess;
642} 839}
643 840
644Result Module::Interface::GetTagInfo(TagInfo& tag_info) const { 841Result Module::Interface::GetTagInfo(TagInfo& tag_info) const {
645 if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) { 842 if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
646 tag_info = { 843 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
647 .uuid = tag_data.uuid, 844 return ErrCodes::WrongDeviceState;
648 .uuid_length = static_cast<u8>(tag_data.uuid.size()),
649 .protocol = protocol,
650 .tag_type = static_cast<u32>(tag_data.user_memory.model_info.amiibo_type),
651 };
652 return ResultSuccess;
653 } 845 }
654 846
655 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); 847 tag_info = {
656 return ErrCodes::WrongDeviceState; 848 .uuid = encrypted_tag_data.uuid,
849 .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.size()),
850 .protocol = protocol,
851 .tag_type = static_cast<u32>(encrypted_tag_data.user_memory.model_info.amiibo_type),
852 };
853
854 return ResultSuccess;
657} 855}
658 856
659Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const { 857Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const {
@@ -662,14 +860,28 @@ Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const {
662 return ErrCodes::WrongDeviceState; 860 return ErrCodes::WrongDeviceState;
663 } 861 }
664 862
665 // Read this data from the amiibo save file 863 if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) {
864 const auto& settings = tag_data.settings;
865 // TODO: Validate this data
866 common_info = {
867 .last_write_year = settings.write_date.GetYear(),
868 .last_write_month = settings.write_date.GetMonth(),
869 .last_write_day = settings.write_date.GetDay(),
870 .write_counter = settings.crc_counter,
871 .version = 1,
872 .application_area_size = sizeof(ApplicationArea),
873 };
874 return ResultSuccess;
875 }
876
877 // Generate a generic answer
666 common_info = { 878 common_info = {
667 .last_write_year = 2022, 879 .last_write_year = 2022,
668 .last_write_month = 2, 880 .last_write_month = 2,
669 .last_write_day = 7, 881 .last_write_day = 7,
670 .write_counter = tag_data.user_memory.write_count, 882 .write_counter = 0,
671 .version = 1, 883 .version = 1,
672 .application_area_size = ApplicationAreaSize, 884 .application_area_size = sizeof(ApplicationArea),
673 }; 885 };
674 return ResultSuccess; 886 return ResultSuccess;
675} 887}
@@ -680,26 +892,53 @@ Result Module::Interface::GetModelInfo(ModelInfo& model_info) const {
680 return ErrCodes::WrongDeviceState; 892 return ErrCodes::WrongDeviceState;
681 } 893 }
682 894
683 model_info = tag_data.user_memory.model_info; 895 const auto& model_info_data = encrypted_tag_data.user_memory.model_info;
896 model_info = {
897 .character_id = model_info_data.character_id,
898 .character_variant = model_info_data.character_variant,
899 .amiibo_type = model_info_data.amiibo_type,
900 .model_number = model_info_data.model_number,
901 .series = model_info_data.series,
902 .constant_value = model_info_data.constant_value,
903 };
684 return ResultSuccess; 904 return ResultSuccess;
685} 905}
686 906
687Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const { 907Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const {
688 if (device_state != DeviceState::TagMounted) { 908 if (device_state != DeviceState::TagMounted) {
689 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); 909 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
910 if (device_state == DeviceState::TagRemoved) {
911 return ErrCodes::TagRemoved;
912 }
690 return ErrCodes::WrongDeviceState; 913 return ErrCodes::WrongDeviceState;
691 } 914 }
692 915
693 Service::Mii::MiiManager manager; 916 Service::Mii::MiiManager manager;
694 917
695 // Read this data from the amiibo save file 918 if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) {
919 const auto& settings = tag_data.settings;
920
921 // TODO: Validate this data
922 register_info = {
923 .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii),
924 .first_write_year = settings.init_date.GetYear(),
925 .first_write_month = settings.init_date.GetMonth(),
926 .first_write_day = settings.init_date.GetDay(),
927 .amiibo_name = GetAmiiboName(settings),
928 .font_region = {},
929 };
930
931 return ResultSuccess;
932 }
933
934 // Generate a generic answer
696 register_info = { 935 register_info = {
697 .mii_char_info = manager.BuildDefault(0), 936 .mii_char_info = manager.BuildDefault(0),
698 .first_write_year = 2022, 937 .first_write_year = 2022,
699 .first_write_month = 2, 938 .first_write_month = 2,
700 .first_write_day = 7, 939 .first_write_day = 7,
701 .amiibo_name = {'Y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o', 0}, 940 .amiibo_name = {'Y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o', 0},
702 .unknown = {}, 941 .font_region = {},
703 }; 942 };
704 return ResultSuccess; 943 return ResultSuccess;
705} 944}
@@ -707,31 +946,47 @@ Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const {
707Result Module::Interface::OpenApplicationArea(u32 access_id) { 946Result Module::Interface::OpenApplicationArea(u32 access_id) {
708 if (device_state != DeviceState::TagMounted) { 947 if (device_state != DeviceState::TagMounted) {
709 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); 948 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
949 if (device_state == DeviceState::TagRemoved) {
950 return ErrCodes::TagRemoved;
951 }
710 return ErrCodes::WrongDeviceState; 952 return ErrCodes::WrongDeviceState;
711 } 953 }
712 if (AmiiboApplicationDataExist(access_id)) { 954
713 application_area_data = LoadAmiiboApplicationData(access_id); 955 // Fallback for lack of amiibo keys
714 application_area_id = access_id; 956 if (!is_data_decoded) {
715 is_application_area_initialized = true; 957 LOG_WARNING(Service_NFP, "Application area is not initialized");
958 return ErrCodes::ApplicationAreaIsNotInitialized;
716 } 959 }
717 if (!is_application_area_initialized) { 960
961 if (tag_data.settings.settings.appdata_initialized == 0) {
718 LOG_WARNING(Service_NFP, "Application area is not initialized"); 962 LOG_WARNING(Service_NFP, "Application area is not initialized");
719 return ErrCodes::ApplicationAreaIsNotInitialized; 963 return ErrCodes::ApplicationAreaIsNotInitialized;
720 } 964 }
965
966 if (tag_data.application_area_id != access_id) {
967 LOG_WARNING(Service_NFP, "Wrong application area id");
968 return ErrCodes::WrongApplicationAreaId;
969 }
970
971 is_application_area_initialized = true;
721 return ResultSuccess; 972 return ResultSuccess;
722} 973}
723 974
724Result Module::Interface::GetApplicationArea(std::vector<u8>& data) const { 975Result Module::Interface::GetApplicationArea(ApplicationArea& data) const {
725 if (device_state != DeviceState::TagMounted) { 976 if (device_state != DeviceState::TagMounted) {
726 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); 977 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
978 if (device_state == DeviceState::TagRemoved) {
979 return ErrCodes::TagRemoved;
980 }
727 return ErrCodes::WrongDeviceState; 981 return ErrCodes::WrongDeviceState;
728 } 982 }
983
729 if (!is_application_area_initialized) { 984 if (!is_application_area_initialized) {
730 LOG_ERROR(Service_NFP, "Application area is not initialized"); 985 LOG_ERROR(Service_NFP, "Application area is not initialized");
731 return ErrCodes::ApplicationAreaIsNotInitialized; 986 return ErrCodes::ApplicationAreaIsNotInitialized;
732 } 987 }
733 988
734 data = application_area_data; 989 data = tag_data.application_area;
735 990
736 return ResultSuccess; 991 return ResultSuccess;
737} 992}
@@ -739,46 +994,69 @@ Result Module::Interface::GetApplicationArea(std::vector<u8>& data) const {
739Result Module::Interface::SetApplicationArea(const std::vector<u8>& data) { 994Result Module::Interface::SetApplicationArea(const std::vector<u8>& data) {
740 if (device_state != DeviceState::TagMounted) { 995 if (device_state != DeviceState::TagMounted) {
741 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); 996 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
997 if (device_state == DeviceState::TagRemoved) {
998 return ErrCodes::TagRemoved;
999 }
742 return ErrCodes::WrongDeviceState; 1000 return ErrCodes::WrongDeviceState;
743 } 1001 }
1002
744 if (!is_application_area_initialized) { 1003 if (!is_application_area_initialized) {
745 LOG_ERROR(Service_NFP, "Application area is not initialized"); 1004 LOG_ERROR(Service_NFP, "Application area is not initialized");
746 return ErrCodes::ApplicationAreaIsNotInitialized; 1005 return ErrCodes::ApplicationAreaIsNotInitialized;
747 } 1006 }
748 application_area_data = data; 1007
749 SaveAmiiboApplicationData(application_area_id, application_area_data); 1008 if (data.size() != sizeof(ApplicationArea)) {
1009 LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
1010 return ResultUnknown;
1011 }
1012
1013 std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
750 return ResultSuccess; 1014 return ResultSuccess;
751} 1015}
752 1016
753Result Module::Interface::CreateApplicationArea(u32 access_id, const std::vector<u8>& data) { 1017Result Module::Interface::CreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
754 if (device_state != DeviceState::TagMounted) { 1018 if (device_state != DeviceState::TagMounted) {
755 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); 1019 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
1020 if (device_state == DeviceState::TagRemoved) {
1021 return ErrCodes::TagRemoved;
1022 }
756 return ErrCodes::WrongDeviceState; 1023 return ErrCodes::WrongDeviceState;
757 } 1024 }
758 if (AmiiboApplicationDataExist(access_id)) { 1025
1026 if (tag_data.settings.settings.appdata_initialized != 0) {
759 LOG_ERROR(Service_NFP, "Application area already exist"); 1027 LOG_ERROR(Service_NFP, "Application area already exist");
760 return ErrCodes::ApplicationAreaExist; 1028 return ErrCodes::ApplicationAreaExist;
761 } 1029 }
762 application_area_data = data; 1030
763 application_area_id = access_id; 1031 if (data.size() != sizeof(ApplicationArea)) {
764 SaveAmiiboApplicationData(application_area_id, application_area_data); 1032 LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
1033 return ResultUnknown;
1034 }
1035
1036 std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
1037 tag_data.application_area_id = access_id;
1038
765 return ResultSuccess; 1039 return ResultSuccess;
766} 1040}
767 1041
768bool Module::Interface::AmiiboApplicationDataExist(u32 access_id) const { 1042Result Module::Interface::RecreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
769 // TODO(german77): Check if file exist 1043 if (device_state != DeviceState::TagMounted) {
770 return false; 1044 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
771} 1045 if (device_state == DeviceState::TagRemoved) {
1046 return ErrCodes::TagRemoved;
1047 }
1048 return ErrCodes::WrongDeviceState;
1049 }
772 1050
773std::vector<u8> Module::Interface::LoadAmiiboApplicationData(u32 access_id) const { 1051 if (data.size() != sizeof(ApplicationArea)) {
774 // TODO(german77): Read file 1052 LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
775 std::vector<u8> data(ApplicationAreaSize); 1053 return ResultUnknown;
776 return data; 1054 }
777} 1055
1056 std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
1057 tag_data.application_area_id = access_id;
778 1058
779void Module::Interface::SaveAmiiboApplicationData(u32 access_id, 1059 return ResultSuccess;
780 const std::vector<u8>& data) const {
781 // TODO(german77): Save file
782} 1060}
783 1061
784u64 Module::Interface::GetHandle() const { 1062u64 Module::Interface::GetHandle() const {
@@ -791,16 +1069,25 @@ DeviceState Module::Interface::GetCurrentState() const {
791} 1069}
792 1070
793Core::HID::NpadIdType Module::Interface::GetNpadId() const { 1071Core::HID::NpadIdType Module::Interface::GetNpadId() const {
794 return npad_id; 1072 // Return first connected npad id as a workaround for lack of a single nfc interface per
1073 // controller
1074 return system.HIDCore().GetFirstNpadId();
795} 1075}
796 1076
797u32 Module::Interface::GetTagPassword(const TagUuid& uuid) const { 1077AmiiboName Module::Interface::GetAmiiboName(const AmiiboSettings& settings) const {
798 // Verifiy that the generated password is correct 1078 std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
799 u32 password = 0xAA ^ (uuid[1] ^ uuid[3]); 1079 AmiiboName amiibo_name{};
800 password &= (0x55 ^ (uuid[2] ^ uuid[4])) << 8; 1080
801 password &= (0xAA ^ (uuid[3] ^ uuid[5])) << 16; 1081 // Convert from big endian to little endian
802 password &= (0x55 ^ (uuid[4] ^ uuid[6])) << 24; 1082 for (std::size_t i = 0; i < amiibo_name_length; i++) {
803 return password; 1083 settings_amiibo_name[i] = static_cast<u16>(settings.amiibo_name[i]);
1084 }
1085
1086 // Convert from utf16 to utf8
1087 const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data());
1088 memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size());
1089
1090 return amiibo_name;
804} 1091}
805 1092
806void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { 1093void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h
index 0fc808781..0de0b48e7 100644
--- a/src/core/hle/service/nfp/nfp.h
+++ b/src/core/hle/service/nfp/nfp.h
@@ -9,6 +9,7 @@
9#include "common/common_funcs.h" 9#include "common/common_funcs.h"
10#include "core/hle/service/kernel_helpers.h" 10#include "core/hle/service/kernel_helpers.h"
11#include "core/hle/service/mii/types.h" 11#include "core/hle/service/mii/types.h"
12#include "core/hle/service/nfp/amiibo_types.h"
12#include "core/hle/service/service.h" 13#include "core/hle/service/service.h"
13 14
14namespace Kernel { 15namespace Kernel {
@@ -21,71 +22,7 @@ enum class NpadIdType : u32;
21} // namespace Core::HID 22} // namespace Core::HID
22 23
23namespace Service::NFP { 24namespace Service::NFP {
24 25using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
25enum class ServiceType : u32 {
26 User,
27 Debug,
28 System,
29};
30
31enum class State : u32 {
32 NonInitialized,
33 Initialized,
34};
35
36enum class DeviceState : u32 {
37 Initialized,
38 SearchingForTag,
39 TagFound,
40 TagRemoved,
41 TagMounted,
42 Unaviable,
43 Finalized,
44};
45
46enum class ModelType : u32 {
47 Amiibo,
48};
49
50enum class MountTarget : u32 {
51 Rom,
52 Ram,
53 All,
54};
55
56enum class AmiiboType : u8 {
57 Figure,
58 Card,
59 Yarn,
60};
61
62enum class AmiiboSeries : u8 {
63 SuperSmashBros,
64 SuperMario,
65 ChibiRobo,
66 YoshiWoollyWorld,
67 Splatoon,
68 AnimalCrossing,
69 EightBitMario,
70 Skylanders,
71 Unknown8,
72 TheLegendOfZelda,
73 ShovelKnight,
74 Unknown11,
75 Kiby,
76 Pokemon,
77 MarioSportsSuperstars,
78 MonsterHunter,
79 BoxBoy,
80 Pikmin,
81 FireEmblem,
82 Metroid,
83 Others,
84 MegaMan,
85 Diablo
86};
87
88using TagUuid = std::array<u8, 10>;
89 26
90struct TagInfo { 27struct TagInfo {
91 TagUuid uuid; 28 TagUuid uuid;
@@ -114,21 +51,19 @@ struct ModelInfo {
114 AmiiboType amiibo_type; 51 AmiiboType amiibo_type;
115 u16 model_number; 52 u16 model_number;
116 AmiiboSeries series; 53 AmiiboSeries series;
117 u8 fixed; // Must be 02 54 u8 constant_value; // Must be 02
118 INSERT_PADDING_BYTES(0x4); // Unknown 55 INSERT_PADDING_BYTES(0x38); // Unknown
119 INSERT_PADDING_BYTES(0x20); // Probably a SHA256-(HMAC?) hash
120 INSERT_PADDING_BYTES(0x14); // SHA256-HMAC
121}; 56};
122static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size"); 57static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
123 58
124struct RegisterInfo { 59struct RegisterInfo {
125 Service::Mii::MiiInfo mii_char_info; 60 Service::Mii::CharInfo mii_char_info;
126 u16 first_write_year; 61 u16 first_write_year;
127 u8 first_write_month; 62 u8 first_write_month;
128 u8 first_write_day; 63 u8 first_write_day;
129 std::array<u8, 11> amiibo_name; 64 AmiiboName amiibo_name;
130 u8 unknown; 65 u8 font_region;
131 INSERT_PADDING_BYTES(0x98); 66 INSERT_PADDING_BYTES(0x7A);
132}; 67};
133static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size"); 68static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
134 69
@@ -140,39 +75,9 @@ public:
140 const char* name); 75 const char* name);
141 ~Interface() override; 76 ~Interface() override;
142 77
143 struct EncryptedAmiiboFile {
144 u16 crypto_init; // Must be A5 XX
145 u16 write_count; // Number of times the amiibo has been written?
146 INSERT_PADDING_BYTES(0x20); // System crypts
147 INSERT_PADDING_BYTES(0x20); // SHA256-(HMAC?) hash
148 ModelInfo model_info; // This struct is bigger than documentation
149 INSERT_PADDING_BYTES(0xC); // SHA256-HMAC
150 INSERT_PADDING_BYTES(0x114); // section 1 encrypted buffer
151 INSERT_PADDING_BYTES(0x54); // section 2 encrypted buffer
152 };
153 static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
154
155 struct NTAG215Password {
156 u32 PWD; // Password to allow write access
157 u16 PACK; // Password acknowledge reply
158 u16 RFUI; // Reserved for future use
159 };
160 static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size");
161
162 struct NTAG215File {
163 TagUuid uuid; // Unique serial number
164 u16 lock_bytes; // Set defined pages as read only
165 u32 compability_container; // Defines available memory
166 EncryptedAmiiboFile user_memory; // Writable data
167 u32 dynamic_lock; // Dynamic lock
168 u32 CFG0; // Defines memory protected by password
169 u32 CFG1; // Defines number of verification attempts
170 NTAG215Password password; // Password data
171 };
172 static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size");
173
174 void CreateUserInterface(Kernel::HLERequestContext& ctx); 78 void CreateUserInterface(Kernel::HLERequestContext& ctx);
175 bool LoadAmiibo(const std::vector<u8>& buffer); 79 bool LoadAmiibo(const std::string& filename);
80 bool LoadAmiiboFile(const std::string& filename);
176 void CloseAmiibo(); 81 void CloseAmiibo();
177 82
178 void Initialize(); 83 void Initialize();
@@ -182,6 +87,7 @@ public:
182 Result StopDetection(); 87 Result StopDetection();
183 Result Mount(); 88 Result Mount();
184 Result Unmount(); 89 Result Unmount();
90 Result Flush();
185 91
186 Result GetTagInfo(TagInfo& tag_info) const; 92 Result GetTagInfo(TagInfo& tag_info) const;
187 Result GetCommonInfo(CommonInfo& common_info) const; 93 Result GetCommonInfo(CommonInfo& common_info) const;
@@ -189,9 +95,10 @@ public:
189 Result GetRegisterInfo(RegisterInfo& register_info) const; 95 Result GetRegisterInfo(RegisterInfo& register_info) const;
190 96
191 Result OpenApplicationArea(u32 access_id); 97 Result OpenApplicationArea(u32 access_id);
192 Result GetApplicationArea(std::vector<u8>& data) const; 98 Result GetApplicationArea(ApplicationArea& data) const;
193 Result SetApplicationArea(const std::vector<u8>& data); 99 Result SetApplicationArea(const std::vector<u8>& data);
194 Result CreateApplicationArea(u32 access_id, const std::vector<u8>& data); 100 Result CreateApplicationArea(u32 access_id, const std::vector<u8>& data);
101 Result RecreateApplicationArea(u32 access_id, const std::vector<u8>& data);
195 102
196 u64 GetHandle() const; 103 u64 GetHandle() const;
197 DeviceState GetCurrentState() const; 104 DeviceState GetCurrentState() const;
@@ -204,27 +111,21 @@ public:
204 std::shared_ptr<Module> module; 111 std::shared_ptr<Module> module;
205 112
206 private: 113 private:
207 /// Validates that the amiibo file is not corrupted 114 AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
208 bool IsAmiiboValid() const;
209
210 bool AmiiboApplicationDataExist(u32 access_id) const;
211 std::vector<u8> LoadAmiiboApplicationData(u32 access_id) const;
212 void SaveAmiiboApplicationData(u32 access_id, const std::vector<u8>& data) const;
213
214 /// return password needed to allow write access to protected memory
215 u32 GetTagPassword(const TagUuid& uuid) const;
216 115
217 const Core::HID::NpadIdType npad_id; 116 const Core::HID::NpadIdType npad_id;
218 117
219 DeviceState device_state{DeviceState::Unaviable}; 118 bool is_data_decoded{};
220 KernelHelpers::ServiceContext service_context; 119 bool is_application_area_initialized{};
120 s32 protocol;
121 std::string file_path{};
221 Kernel::KEvent* activate_event; 122 Kernel::KEvent* activate_event;
222 Kernel::KEvent* deactivate_event; 123 Kernel::KEvent* deactivate_event;
124 DeviceState device_state{DeviceState::Unaviable};
125 KernelHelpers::ServiceContext service_context;
126
223 NTAG215File tag_data{}; 127 NTAG215File tag_data{};
224 s32 protocol; 128 EncryptedNTAG215File encrypted_tag_data{};
225 bool is_application_area_initialized{};
226 u32 application_area_id;
227 std::vector<u8> application_area_data;
228 }; 129 };
229}; 130};
230 131
@@ -243,6 +144,7 @@ private:
243 void OpenApplicationArea(Kernel::HLERequestContext& ctx); 144 void OpenApplicationArea(Kernel::HLERequestContext& ctx);
244 void GetApplicationArea(Kernel::HLERequestContext& ctx); 145 void GetApplicationArea(Kernel::HLERequestContext& ctx);
245 void SetApplicationArea(Kernel::HLERequestContext& ctx); 146 void SetApplicationArea(Kernel::HLERequestContext& ctx);
147 void Flush(Kernel::HLERequestContext& ctx);
246 void CreateApplicationArea(Kernel::HLERequestContext& ctx); 148 void CreateApplicationArea(Kernel::HLERequestContext& ctx);
247 void GetTagInfo(Kernel::HLERequestContext& ctx); 149 void GetTagInfo(Kernel::HLERequestContext& ctx);
248 void GetRegisterInfo(Kernel::HLERequestContext& ctx); 150 void GetRegisterInfo(Kernel::HLERequestContext& ctx);
@@ -255,6 +157,7 @@ private:
255 void GetNpadId(Kernel::HLERequestContext& ctx); 157 void GetNpadId(Kernel::HLERequestContext& ctx);
256 void GetApplicationAreaSize(Kernel::HLERequestContext& ctx); 158 void GetApplicationAreaSize(Kernel::HLERequestContext& ctx);
257 void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx); 159 void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
160 void RecreateApplicationArea(Kernel::HLERequestContext& ctx);
258 161
259 KernelHelpers::ServiceContext service_context; 162 KernelHelpers::ServiceContext service_context;
260 163
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
index 2a5128c60..a7385fce8 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
@@ -1,6 +1,7 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "audio_core/audio_core.h"
4#include "common/assert.h" 5#include "common/assert.h"
5#include "common/logging/log.h" 6#include "common/logging/log.h"
6#include "core/core.h" 7#include "core/core.h"
@@ -65,7 +66,10 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>&
65 return NvResult::NotImplemented; 66 return NvResult::NotImplemented;
66} 67}
67 68
68void nvhost_nvdec::OnOpen(DeviceFD fd) {} 69void nvhost_nvdec::OnOpen(DeviceFD fd) {
70 LOG_INFO(Service_NVDRV, "NVDEC video stream started");
71 system.AudioCore().SetNVDECActive(true);
72}
69 73
70void nvhost_nvdec::OnClose(DeviceFD fd) { 74void nvhost_nvdec::OnClose(DeviceFD fd) {
71 LOG_INFO(Service_NVDRV, "NVDEC video stream ended"); 75 LOG_INFO(Service_NVDRV, "NVDEC video stream ended");
@@ -73,6 +77,7 @@ void nvhost_nvdec::OnClose(DeviceFD fd) {
73 if (iter != fd_to_id.end()) { 77 if (iter != fd_to_id.end()) {
74 system.GPU().ClearCdmaInstance(iter->second); 78 system.GPU().ClearCdmaInstance(iter->second);
75 } 79 }
80 system.AudioCore().SetNVDECActive(false);
76} 81}
77 82
78} // namespace Service::Nvidia::Devices 83} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index 5574269eb..9b382bf56 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -38,20 +38,16 @@ void NVFlinger::SplitVSync(std::stop_token stop_token) {
38 38
39 Common::SetCurrentThreadName(name.c_str()); 39 Common::SetCurrentThreadName(name.c_str());
40 Common::SetCurrentThreadPriority(Common::ThreadPriority::High); 40 Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
41 s64 delay = 0; 41
42 while (!stop_token.stop_requested()) { 42 while (!stop_token.stop_requested()) {
43 vsync_signal.wait(false);
44 vsync_signal.store(false);
45
43 guard->lock(); 46 guard->lock();
44 const s64 time_start = system.CoreTiming().GetGlobalTimeNs().count(); 47
45 Compose(); 48 Compose();
46 const auto ticks = GetNextTicks(); 49
47 const s64 time_end = system.CoreTiming().GetGlobalTimeNs().count();
48 const s64 time_passed = time_end - time_start;
49 const s64 next_time = std::max<s64>(0, ticks - time_passed - delay);
50 guard->unlock(); 50 guard->unlock();
51 if (next_time > 0) {
52 std::this_thread::sleep_for(std::chrono::nanoseconds{next_time});
53 }
54 delay = (system.CoreTiming().GetGlobalTimeNs().count() - time_end) - next_time;
55 } 51 }
56} 52}
57 53
@@ -66,27 +62,41 @@ NVFlinger::NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_dr
66 guard = std::make_shared<std::mutex>(); 62 guard = std::make_shared<std::mutex>();
67 63
68 // Schedule the screen composition events 64 // Schedule the screen composition events
69 composition_event = Core::Timing::CreateEvent( 65 multi_composition_event = Core::Timing::CreateEvent(
66 "ScreenComposition",
67 [this](std::uintptr_t, s64 time,
68 std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
69 vsync_signal.store(true);
70 vsync_signal.notify_all();
71 return std::chrono::nanoseconds(GetNextTicks());
72 });
73
74 single_composition_event = Core::Timing::CreateEvent(
70 "ScreenComposition", 75 "ScreenComposition",
71 [this](std::uintptr_t, s64 time, 76 [this](std::uintptr_t, s64 time,
72 std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { 77 std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
73 const auto lock_guard = Lock(); 78 const auto lock_guard = Lock();
74 Compose(); 79 Compose();
75 80
76 return std::max(std::chrono::nanoseconds::zero(), 81 return std::chrono::nanoseconds(GetNextTicks());
77 std::chrono::nanoseconds(GetNextTicks()) - ns_late);
78 }); 82 });
79 83
80 if (system.IsMulticore()) { 84 if (system.IsMulticore()) {
85 system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, multi_composition_event);
81 vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); }); 86 vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); });
82 } else { 87 } else {
83 system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, composition_event); 88 system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, single_composition_event);
84 } 89 }
85} 90}
86 91
87NVFlinger::~NVFlinger() { 92NVFlinger::~NVFlinger() {
88 if (!system.IsMulticore()) { 93 if (system.IsMulticore()) {
89 system.CoreTiming().UnscheduleEvent(composition_event, 0); 94 system.CoreTiming().UnscheduleEvent(multi_composition_event, {});
95 vsync_thread.request_stop();
96 vsync_signal.store(true);
97 vsync_signal.notify_all();
98 } else {
99 system.CoreTiming().UnscheduleEvent(single_composition_event, {});
90 } 100 }
91 101
92 for (auto& display : displays) { 102 for (auto& display : displays) {
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h
index 4775597cc..044ac6ac8 100644
--- a/src/core/hle/service/nvflinger/nvflinger.h
+++ b/src/core/hle/service/nvflinger/nvflinger.h
@@ -126,12 +126,15 @@ private:
126 u32 swap_interval = 1; 126 u32 swap_interval = 1;
127 127
128 /// Event that handles screen composition. 128 /// Event that handles screen composition.
129 std::shared_ptr<Core::Timing::EventType> composition_event; 129 std::shared_ptr<Core::Timing::EventType> multi_composition_event;
130 std::shared_ptr<Core::Timing::EventType> single_composition_event;
130 131
131 std::shared_ptr<std::mutex> guard; 132 std::shared_ptr<std::mutex> guard;
132 133
133 Core::System& system; 134 Core::System& system;
134 135
136 std::atomic<bool> vsync_signal;
137
135 std::jthread vsync_thread; 138 std::jthread vsync_thread;
136 139
137 KernelHelpers::ServiceContext service_context; 140 KernelHelpers::ServiceContext service_context;
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index cc679cc81..9e94a462f 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -929,7 +929,7 @@ BSD::BSD(Core::System& system_, const char* name)
929 proxy_packet_received = room_member->BindOnProxyPacketReceived( 929 proxy_packet_received = room_member->BindOnProxyPacketReceived(
930 [this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); }); 930 [this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); });
931 } else { 931 } else {
932 LOG_ERROR(Service, "Network isn't initalized"); 932 LOG_ERROR(Service, "Network isn't initialized");
933 } 933 }
934} 934}
935 935
diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp
index cdf38a2a4..447fbffaa 100644
--- a/src/core/internal_network/network.cpp
+++ b/src/core/internal_network/network.cpp
@@ -364,7 +364,7 @@ std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
364 std::vector<WSAPOLLFD> host_pollfds(pollfds.size()); 364 std::vector<WSAPOLLFD> host_pollfds(pollfds.size());
365 std::transform(pollfds.begin(), pollfds.end(), host_pollfds.begin(), [](PollFD fd) { 365 std::transform(pollfds.begin(), pollfds.end(), host_pollfds.begin(), [](PollFD fd) {
366 WSAPOLLFD result; 366 WSAPOLLFD result;
367 result.fd = fd.socket->fd; 367 result.fd = fd.socket->GetFD();
368 result.events = TranslatePollEvents(fd.events); 368 result.events = TranslatePollEvents(fd.events);
369 result.revents = 0; 369 result.revents = 0;
370 return result; 370 return result;
@@ -430,12 +430,12 @@ std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() {
430 return {AcceptResult{}, GetAndLogLastError()}; 430 return {AcceptResult{}, GetAndLogLastError()};
431 } 431 }
432 432
433 AcceptResult result;
434 result.socket = std::make_unique<Socket>();
435 result.socket->fd = new_socket;
436
437 ASSERT(addrlen == sizeof(sockaddr_in)); 433 ASSERT(addrlen == sizeof(sockaddr_in));
438 result.sockaddr_in = TranslateToSockAddrIn(addr); 434
435 AcceptResult result{
436 .socket = std::make_unique<Socket>(new_socket),
437 .sockaddr_in = TranslateToSockAddrIn(addr),
438 };
439 439
440 return {std::move(result), Errno::SUCCESS}; 440 return {std::move(result), Errno::SUCCESS};
441} 441}
diff --git a/src/core/internal_network/sockets.h b/src/core/internal_network/sockets.h
index a70429b19..2e328c645 100644
--- a/src/core/internal_network/sockets.h
+++ b/src/core/internal_network/sockets.h
@@ -32,6 +32,10 @@ public:
32 std::unique_ptr<SocketBase> socket; 32 std::unique_ptr<SocketBase> socket;
33 SockAddrIn sockaddr_in; 33 SockAddrIn sockaddr_in;
34 }; 34 };
35
36 SocketBase() = default;
37 explicit SocketBase(SOCKET fd_) : fd{fd_} {}
38
35 virtual ~SocketBase() = default; 39 virtual ~SocketBase() = default;
36 40
37 virtual SocketBase& operator=(const SocketBase&) = delete; 41 virtual SocketBase& operator=(const SocketBase&) = delete;
@@ -89,12 +93,19 @@ public:
89 93
90 virtual void HandleProxyPacket(const ProxyPacket& packet) = 0; 94 virtual void HandleProxyPacket(const ProxyPacket& packet) = 0;
91 95
96 [[nodiscard]] SOCKET GetFD() const {
97 return fd;
98 }
99
100protected:
92 SOCKET fd = INVALID_SOCKET; 101 SOCKET fd = INVALID_SOCKET;
93}; 102};
94 103
95class Socket : public SocketBase { 104class Socket : public SocketBase {
96public: 105public:
97 Socket() = default; 106 Socket() = default;
107 explicit Socket(SOCKET fd_) : SocketBase{fd_} {}
108
98 ~Socket() override; 109 ~Socket() override;
99 110
100 Socket(const Socket&) = delete; 111 Socket(const Socket&) = delete;
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 104d16efa..f24474ed8 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -244,6 +244,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::V
244 244
245std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file, 245std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file,
246 u64 program_id, std::size_t program_index) { 246 u64 program_id, std::size_t program_index) {
247 if (!file) {
248 return nullptr;
249 }
250
247 FileType type = IdentifyFile(file); 251 FileType type = IdentifyFile(file);
248 const FileType filename_type = GuessFromFilename(file->GetName()); 252 const FileType filename_type = GuessFromFilename(file->GetName());
249 253
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index 133422d5c..ffb9b945e 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -824,6 +824,7 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateAnalogDevice(
824 .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f), 824 .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f),
825 .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f), 825 .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f),
826 .inverted = params.Get("invert", "+") == "-", 826 .inverted = params.Get("invert", "+") == "-",
827 .toggle = static_cast<bool>(params.Get("toggle", false)),
827 }; 828 };
828 input_engine->PreSetController(identifier); 829 input_engine->PreSetController(identifier);
829 input_engine->PreSetAxis(identifier, axis); 830 input_engine->PreSetAxis(identifier, axis);
diff --git a/src/network/network.cpp b/src/network/network.cpp
index 0841e4134..6652a186b 100644
--- a/src/network/network.cpp
+++ b/src/network/network.cpp
@@ -15,7 +15,7 @@ RoomNetwork::RoomNetwork() {
15 15
16bool RoomNetwork::Init() { 16bool RoomNetwork::Init() {
17 if (enet_initialize() != 0) { 17 if (enet_initialize() != 0) {
18 LOG_ERROR(Network, "Error initalizing ENet"); 18 LOG_ERROR(Network, "Error initializing ENet");
19 return false; 19 return false;
20 } 20 }
21 m_room = std::make_shared<Room>(); 21 m_room = std::make_shared<Room>();
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm.cpp b/src/shader_recompiler/backend/glasm/emit_glasm.cpp
index 97a6b383b..01f9abc71 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm.cpp
@@ -175,7 +175,7 @@ bool IsReference(IR::Inst& inst) {
175} 175}
176 176
177void PrecolorInst(IR::Inst& phi) { 177void PrecolorInst(IR::Inst& phi) {
178 // Insert phi moves before references to avoid overwritting other phis 178 // Insert phi moves before references to avoid overwriting other phis
179 const size_t num_args{phi.NumArgs()}; 179 const size_t num_args{phi.NumArgs()};
180 for (size_t i = 0; i < num_args; ++i) { 180 for (size_t i = 0; i < num_args; ++i) {
181 IR::Block& phi_block{*phi.PhiBlock(i)}; 181 IR::Block& phi_block{*phi.PhiBlock(i)};
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl.cpp b/src/shader_recompiler/backend/glsl/emit_glsl.cpp
index 76c18e488..e8a4390f6 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl.cpp
@@ -101,7 +101,7 @@ bool IsReference(IR::Inst& inst) {
101} 101}
102 102
103void PrecolorInst(IR::Inst& phi) { 103void PrecolorInst(IR::Inst& phi) {
104 // Insert phi moves before references to avoid overwritting other phis 104 // Insert phi moves before references to avoid overwriting other phis
105 const size_t num_args{phi.NumArgs()}; 105 const size_t num_args{phi.NumArgs()};
106 for (size_t i = 0; i < num_args; ++i) { 106 for (size_t i = 0; i < num_args; ++i) {
107 IR::Block& phi_block{*phi.PhiBlock(i)}; 107 IR::Block& phi_block{*phi.PhiBlock(i)};
diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp
index 3441a5fe5..d608678a3 100644
--- a/src/video_core/host_shaders/astc_decoder.comp
+++ b/src/video_core/host_shaders/astc_decoder.comp
@@ -1065,7 +1065,7 @@ TexelWeightParams DecodeBlockInfo() {
1065void FillError(ivec3 coord) { 1065void FillError(ivec3 coord) {
1066 for (uint j = 0; j < block_dims.y; j++) { 1066 for (uint j = 0; j < block_dims.y; j++) {
1067 for (uint i = 0; i < block_dims.x; i++) { 1067 for (uint i = 0; i < block_dims.x; i++) {
1068 imageStore(dest_image, coord + ivec3(i, j, 0), vec4(1.0, 1.0, 0.0, 1.0)); 1068 imageStore(dest_image, coord + ivec3(i, j, 0), vec4(0.0, 0.0, 0.0, 0.0));
1069 } 1069 }
1070 } 1070 }
1071} 1071}
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index 32450ee1d..08f4d69ab 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -168,7 +168,7 @@ void BufferCacheRuntime::BindIndexBuffer(Buffer& buffer, u32 offset, u32 size) {
168 if (has_unified_vertex_buffers) { 168 if (has_unified_vertex_buffers) {
169 buffer.MakeResident(GL_READ_ONLY); 169 buffer.MakeResident(GL_READ_ONLY);
170 glBufferAddressRangeNV(GL_ELEMENT_ARRAY_ADDRESS_NV, 0, buffer.HostGpuAddr() + offset, 170 glBufferAddressRangeNV(GL_ELEMENT_ARRAY_ADDRESS_NV, 0, buffer.HostGpuAddr() + offset,
171 static_cast<GLsizeiptr>(size)); 171 static_cast<GLsizeiptr>(Common::AlignUp(size, 4)));
172 } else { 172 } else {
173 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.Handle()); 173 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.Handle());
174 index_buffer_offset = offset; 174 index_buffer_offset = offset;
diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp
index e3f3d3c5d..e3742ddf5 100644
--- a/src/video_core/textures/astc.cpp
+++ b/src/video_core/textures/astc.cpp
@@ -13,7 +13,9 @@
13 13
14#include <boost/container/static_vector.hpp> 14#include <boost/container/static_vector.hpp>
15 15
16#include "common/alignment.h"
16#include "common/common_types.h" 17#include "common/common_types.h"
18#include "common/thread_worker.h"
17#include "video_core/textures/astc.h" 19#include "video_core/textures/astc.h"
18 20
19class InputBitStream { 21class InputBitStream {
@@ -1411,7 +1413,7 @@ static void FillVoidExtentLDR(InputBitStream& strm, std::span<u32> outBuf, u32 b
1411static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) { 1413static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) {
1412 for (u32 j = 0; j < blockHeight; j++) { 1414 for (u32 j = 0; j < blockHeight; j++) {
1413 for (u32 i = 0; i < blockWidth; i++) { 1415 for (u32 i = 0; i < blockWidth; i++) {
1414 outBuf[j * blockWidth + i] = 0xFFFF00FF; 1416 outBuf[j * blockWidth + i] = 0x00000000;
1415 } 1417 }
1416 } 1418 }
1417} 1419}
@@ -1650,29 +1652,41 @@ static void DecompressBlock(std::span<const u8, 16> inBuf, const u32 blockWidth,
1650 1652
1651void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth, 1653void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth,
1652 uint32_t block_width, uint32_t block_height, std::span<uint8_t> output) { 1654 uint32_t block_width, uint32_t block_height, std::span<uint8_t> output) {
1653 u32 block_index = 0; 1655 const u32 rows = Common::DivideUp(height, block_height);
1654 std::size_t depth_offset = 0; 1656 const u32 cols = Common::DivideUp(width, block_width);
1655 for (u32 z = 0; z < depth; z++) { 1657
1656 for (u32 y = 0; y < height; y += block_height) { 1658 Common::ThreadWorker workers{std::max(std::thread::hardware_concurrency(), 2U) / 2,
1657 for (u32 x = 0; x < width; x += block_width) { 1659 "yuzu:ASTCDecompress"};
1658 const std::span<const u8, 16> blockPtr{data.subspan(block_index * 16, 16)}; 1660
1659 1661 for (u32 z = 0; z < depth; ++z) {
1660 // Blocks can be at most 12x12 1662 const u32 depth_offset = z * height * width * 4;
1661 std::array<u32, 12 * 12> uncompData; 1663 for (u32 y_index = 0; y_index < rows; ++y_index) {
1662 DecompressBlock(blockPtr, block_width, block_height, uncompData); 1664 auto decompress_stride = [data, width, height, depth, block_width, block_height, output,
1663 1665 rows, cols, z, depth_offset, y_index] {
1664 u32 decompWidth = std::min(block_width, width - x); 1666 const u32 y = y_index * block_height;
1665 u32 decompHeight = std::min(block_height, height - y); 1667 for (u32 x_index = 0; x_index < cols; ++x_index) {
1666 1668 const u32 block_index = (z * rows * cols) + (y_index * cols) + x_index;
1667 const std::span<u8> outRow = output.subspan(depth_offset + (y * width + x) * 4); 1669 const u32 x = x_index * block_width;
1668 for (u32 jj = 0; jj < decompHeight; jj++) { 1670
1669 std::memcpy(outRow.data() + jj * width * 4, 1671 const std::span<const u8, 16> blockPtr{data.subspan(block_index * 16, 16)};
1670 uncompData.data() + jj * block_width, decompWidth * 4); 1672
1673 // Blocks can be at most 12x12
1674 std::array<u32, 12 * 12> uncompData;
1675 DecompressBlock(blockPtr, block_width, block_height, uncompData);
1676
1677 u32 decompWidth = std::min(block_width, width - x);
1678 u32 decompHeight = std::min(block_height, height - y);
1679
1680 const std::span<u8> outRow = output.subspan(depth_offset + (y * width + x) * 4);
1681 for (u32 h = 0; h < decompHeight; ++h) {
1682 std::memcpy(outRow.data() + h * width * 4,
1683 uncompData.data() + h * block_width, decompWidth * 4);
1684 }
1671 } 1685 }
1672 ++block_index; 1686 };
1673 } 1687 workers.QueueWork(std::move(decompress_stride));
1674 } 1688 }
1675 depth_offset += height * width * 4; 1689 workers.WaitForRequests();
1676 } 1690 }
1677} 1691}
1678 1692
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 50007338f..29d506c47 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -208,6 +208,16 @@ add_executable(yuzu
208 yuzu.rc 208 yuzu.rc
209) 209)
210 210
211if (WIN32 AND YUZU_CRASH_DUMPS)
212 target_sources(yuzu PRIVATE
213 mini_dump.cpp
214 mini_dump.h
215 )
216
217 target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY})
218 target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP)
219endif()
220
211file(GLOB COMPAT_LIST 221file(GLOB COMPAT_LIST
212 ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc 222 ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
213 ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) 223 ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp
index 8be311fcb..12efdc216 100644
--- a/src/yuzu/applets/qt_controller.cpp
+++ b/src/yuzu/applets/qt_controller.cpp
@@ -63,7 +63,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
63 InputCommon::InputSubsystem* input_subsystem_, Core::System& system_) 63 InputCommon::InputSubsystem* input_subsystem_, Core::System& system_)
64 : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()), 64 : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
65 parameters(std::move(parameters_)), input_subsystem{input_subsystem_}, 65 parameters(std::move(parameters_)), input_subsystem{input_subsystem_},
66 input_profiles(std::make_unique<InputProfiles>(system_)), system{system_} { 66 input_profiles(std::make_unique<InputProfiles>()), system{system_} {
67 ui->setupUi(this); 67 ui->setupUi(this);
68 68
69 player_widgets = { 69 player_widgets = {
@@ -291,7 +291,7 @@ bool QtControllerSelectorDialog::CheckIfParametersMet() {
291 // Here, we check and validate the current configuration against all applicable parameters. 291 // Here, we check and validate the current configuration against all applicable parameters.
292 const auto num_connected_players = static_cast<int>( 292 const auto num_connected_players = static_cast<int>(
293 std::count_if(player_groupboxes.begin(), player_groupboxes.end(), 293 std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
294 [this](const QGroupBox* player) { return player->isChecked(); })); 294 [](const QGroupBox* player) { return player->isChecked(); }));
295 295
296 const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; 296 const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
297 const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; 297 const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 8ecd87150..195074bf2 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -15,8 +15,7 @@
15 15
16namespace FS = Common::FS; 16namespace FS = Common::FS;
17 17
18Config::Config(Core::System& system_, const std::string& config_name, ConfigType config_type) 18Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) {
19 : type(config_type), system{system_} {
20 global = config_type == ConfigType::GlobalConfig; 19 global = config_type == ConfigType::GlobalConfig;
21 20
22 Initialize(config_name); 21 Initialize(config_name);
@@ -546,6 +545,8 @@ void Config::ReadDebuggingValues() {
546 ReadBasicSetting(Settings::values.use_debug_asserts); 545 ReadBasicSetting(Settings::values.use_debug_asserts);
547 ReadBasicSetting(Settings::values.use_auto_stub); 546 ReadBasicSetting(Settings::values.use_auto_stub);
548 ReadBasicSetting(Settings::values.enable_all_controllers); 547 ReadBasicSetting(Settings::values.enable_all_controllers);
548 ReadBasicSetting(Settings::values.create_crash_dumps);
549 ReadBasicSetting(Settings::values.perform_vulkan_check);
549 550
550 qt_config->endGroup(); 551 qt_config->endGroup();
551} 552}
@@ -1161,6 +1162,8 @@ void Config::SaveDebuggingValues() {
1161 WriteBasicSetting(Settings::values.use_debug_asserts); 1162 WriteBasicSetting(Settings::values.use_debug_asserts);
1162 WriteBasicSetting(Settings::values.disable_macro_jit); 1163 WriteBasicSetting(Settings::values.disable_macro_jit);
1163 WriteBasicSetting(Settings::values.enable_all_controllers); 1164 WriteBasicSetting(Settings::values.enable_all_controllers);
1165 WriteBasicSetting(Settings::values.create_crash_dumps);
1166 WriteBasicSetting(Settings::values.perform_vulkan_check);
1164 1167
1165 qt_config->endGroup(); 1168 qt_config->endGroup();
1166} 1169}
@@ -1547,7 +1550,6 @@ void Config::Reload() {
1547 ReadValues(); 1550 ReadValues();
1548 // To apply default value changes 1551 // To apply default value changes
1549 SaveValues(); 1552 SaveValues();
1550 system.ApplySettings();
1551} 1553}
1552 1554
1553void Config::Save() { 1555void Config::Save() {
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 486ceea94..06fa7d2d0 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -25,7 +25,7 @@ public:
25 InputProfile, 25 InputProfile,
26 }; 26 };
27 27
28 explicit Config(Core::System& system_, const std::string& config_name = "qt-config", 28 explicit Config(const std::string& config_name = "qt-config",
29 ConfigType config_type = ConfigType::GlobalConfig); 29 ConfigType config_type = ConfigType::GlobalConfig);
30 ~Config(); 30 ~Config();
31 31
@@ -194,8 +194,6 @@ private:
194 std::unique_ptr<QSettings> qt_config; 194 std::unique_ptr<QSettings> qt_config;
195 std::string qt_config_loc; 195 std::string qt_config_loc;
196 bool global; 196 bool global;
197
198 Core::System& system;
199}; 197};
200 198
201// These metatype declarations cannot be in common/settings.h because core is devoid of QT 199// These metatype declarations cannot be in common/settings.h because core is devoid of QT
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index 04d397750..dacc75a20 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -2,6 +2,7 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <QDesktopServices> 4#include <QDesktopServices>
5#include <QMessageBox>
5#include <QUrl> 6#include <QUrl>
6#include "common/fs/path_util.h" 7#include "common/fs/path_util.h"
7#include "common/logging/backend.h" 8#include "common/logging/backend.h"
@@ -26,6 +27,16 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent)
26 27
27 connect(ui->toggle_gdbstub, &QCheckBox::toggled, 28 connect(ui->toggle_gdbstub, &QCheckBox::toggled,
28 [&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); }); 29 [&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); });
30
31 connect(ui->create_crash_dumps, &QCheckBox::stateChanged, [&](int) {
32 if (crash_dump_warning_shown) {
33 return;
34 }
35 QMessageBox::warning(this, tr("Restart Required"),
36 tr("yuzu is required to restart in order to apply this setting."),
37 QMessageBox::Ok, QMessageBox::Ok);
38 crash_dump_warning_shown = true;
39 });
29} 40}
30 41
31ConfigureDebug::~ConfigureDebug() = default; 42ConfigureDebug::~ConfigureDebug() = default;
@@ -66,12 +77,20 @@ void ConfigureDebug::SetConfiguration() {
66 ui->disable_loop_safety_checks->setChecked( 77 ui->disable_loop_safety_checks->setChecked(
67 Settings::values.disable_shader_loop_safety_checks.GetValue()); 78 Settings::values.disable_shader_loop_safety_checks.GetValue());
68 ui->extended_logging->setChecked(Settings::values.extended_logging.GetValue()); 79 ui->extended_logging->setChecked(Settings::values.extended_logging.GetValue());
80 ui->perform_vulkan_check->setChecked(Settings::values.perform_vulkan_check.GetValue());
69 81
70#ifdef YUZU_USE_QT_WEB_ENGINE 82#ifdef YUZU_USE_QT_WEB_ENGINE
71 ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue()); 83 ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue());
72#else 84#else
73 ui->disable_web_applet->setEnabled(false); 85 ui->disable_web_applet->setEnabled(false);
74 ui->disable_web_applet->setText(QString::fromUtf8("Web applet not compiled")); 86 ui->disable_web_applet->setText(tr("Web applet not compiled"));
87#endif
88
89#ifdef YUZU_DBGHELP
90 ui->create_crash_dumps->setChecked(Settings::values.create_crash_dumps.GetValue());
91#else
92 ui->create_crash_dumps->setEnabled(false);
93 ui->create_crash_dumps->setText(tr("MiniDump creation not compiled"));
75#endif 94#endif
76} 95}
77 96
@@ -84,6 +103,7 @@ void ConfigureDebug::ApplyConfiguration() {
84 Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked(); 103 Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked();
85 Settings::values.reporting_services = ui->reporting_services->isChecked(); 104 Settings::values.reporting_services = ui->reporting_services->isChecked();
86 Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked(); 105 Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked();
106 Settings::values.create_crash_dumps = ui->create_crash_dumps->isChecked();
87 Settings::values.quest_flag = ui->quest_flag->isChecked(); 107 Settings::values.quest_flag = ui->quest_flag->isChecked();
88 Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); 108 Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked();
89 Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); 109 Settings::values.use_auto_stub = ui->use_auto_stub->isChecked();
@@ -98,6 +118,7 @@ void ConfigureDebug::ApplyConfiguration() {
98 ui->disable_loop_safety_checks->isChecked(); 118 ui->disable_loop_safety_checks->isChecked();
99 Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked(); 119 Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked();
100 Settings::values.extended_logging = ui->extended_logging->isChecked(); 120 Settings::values.extended_logging = ui->extended_logging->isChecked();
121 Settings::values.perform_vulkan_check = ui->perform_vulkan_check->isChecked();
101 UISettings::values.disable_web_applet = ui->disable_web_applet->isChecked(); 122 UISettings::values.disable_web_applet = ui->disable_web_applet->isChecked();
102 Debugger::ToggleConsole(); 123 Debugger::ToggleConsole();
103 Common::Log::Filter filter; 124 Common::Log::Filter filter;
diff --git a/src/yuzu/configuration/configure_debug.h b/src/yuzu/configuration/configure_debug.h
index 42d30f170..030a0b7f7 100644
--- a/src/yuzu/configuration/configure_debug.h
+++ b/src/yuzu/configuration/configure_debug.h
@@ -32,4 +32,6 @@ private:
32 std::unique_ptr<Ui::ConfigureDebug> ui; 32 std::unique_ptr<Ui::ConfigureDebug> ui;
33 33
34 const Core::System& system; 34 const Core::System& system;
35
36 bool crash_dump_warning_shown{false};
35}; 37};
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 47b8b80f1..102c8c66c 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -7,60 +7,60 @@
7 </property> 7 </property>
8 <widget class="QWidget"> 8 <widget class="QWidget">
9 <layout class="QVBoxLayout" name="verticalLayout_1"> 9 <layout class="QVBoxLayout" name="verticalLayout_1">
10 <item> 10 <item>
11 <layout class="QVBoxLayout" name="verticalLayout_2"> 11 <layout class="QVBoxLayout" name="verticalLayout_2">
12 <item> 12 <item>
13 <widget class="QGroupBox" name="groupBox"> 13 <widget class="QGroupBox" name="groupBox">
14 <property name="title"> 14 <property name="title">
15 <string>Debugger</string> 15 <string>Debugger</string>
16 </property> 16 </property>
17 <layout class="QVBoxLayout" name="verticalLayout_3"> 17 <layout class="QVBoxLayout" name="verticalLayout_3">
18 <item>
19 <layout class="QHBoxLayout" name="horizontalLayout_11">
18 <item> 20 <item>
19 <layout class="QHBoxLayout" name="horizontalLayout_11"> 21 <widget class="QCheckBox" name="toggle_gdbstub">
20 <item> 22 <property name="text">
21 <widget class="QCheckBox" name="toggle_gdbstub"> 23 <string>Enable GDB Stub</string>
22 <property name="text"> 24 </property>
23 <string>Enable GDB Stub</string> 25 </widget>
24 </property> 26 </item>
25 </widget> 27 <item>
26 </item> 28 <spacer name="horizontalSpacer">
27 <item> 29 <property name="orientation">
28 <spacer name="horizontalSpacer"> 30 <enum>Qt::Horizontal</enum>
29 <property name="orientation"> 31 </property>
30 <enum>Qt::Horizontal</enum> 32 <property name="sizeHint" stdset="0">
31 </property> 33 <size>
32 <property name="sizeHint" stdset="0"> 34 <width>40</width>
33 <size> 35 <height>20</height>
34 <width>40</width> 36 </size>
35 <height>20</height> 37 </property>
36 </size> 38 </spacer>
37 </property> 39 </item>
38 </spacer> 40 <item>
39 </item> 41 <widget class="QLabel" name="label_11">
40 <item> 42 <property name="text">
41 <widget class="QLabel" name="label_11"> 43 <string>Port:</string>
42 <property name="text"> 44 </property>
43 <string>Port:</string> 45 </widget>
44 </property> 46 </item>
45 </widget> 47 <item>
46 </item> 48 <widget class="QSpinBox" name="gdbport_spinbox">
47 <item> 49 <property name="minimum">
48 <widget class="QSpinBox" name="gdbport_spinbox"> 50 <number>1024</number>
49 <property name="minimum"> 51 </property>
50 <number>1024</number> 52 <property name="maximum">
51 </property> 53 <number>65535</number>
52 <property name="maximum"> 54 </property>
53 <number>65535</number> 55 </widget>
54 </property>
55 </widget>
56 </item>
57 </layout>
58 </item> 56 </item>
59 </layout> 57 </layout>
60 </widget> 58 </item>
61 </item> 59 </layout>
62 </layout> 60 </widget>
63 </item> 61 </item>
62 </layout>
63 </item>
64 <item> 64 <item>
65 <widget class="QGroupBox" name="groupBox_2"> 65 <widget class="QGroupBox" name="groupBox_2">
66 <property name="title"> 66 <property name="title">
@@ -231,6 +231,13 @@
231 <string>Debugging</string> 231 <string>Debugging</string>
232 </property> 232 </property>
233 <layout class="QGridLayout" name="gridLayout_3"> 233 <layout class="QGridLayout" name="gridLayout_3">
234 <item row="2" column="0">
235 <widget class="QCheckBox" name="reporting_services">
236 <property name="text">
237 <string>Enable Verbose Reporting Services**</string>
238 </property>
239 </widget>
240 </item>
234 <item row="0" column="0"> 241 <item row="0" column="0">
235 <widget class="QCheckBox" name="fs_access_log"> 242 <widget class="QCheckBox" name="fs_access_log">
236 <property name="text"> 243 <property name="text">
@@ -238,20 +245,20 @@
238 </property> 245 </property>
239 </widget> 246 </widget>
240 </item> 247 </item>
241 <item row="1" column="0"> 248 <item row="0" column="1">
242 <widget class="QCheckBox" name="dump_audio_commands"> 249 <widget class="QCheckBox" name="dump_audio_commands">
243 <property name="text">
244 <string>Dump Audio Commands To Console**</string>
245 </property>
246 <property name="toolTip"> 250 <property name="toolTip">
247 <string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string> 251 <string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string>
248 </property> 252 </property>
253 <property name="text">
254 <string>Dump Audio Commands To Console**</string>
255 </property>
249 </widget> 256 </widget>
250 </item> 257 </item>
251 <item row="2" column="0"> 258 <item row="2" column="1">
252 <widget class="QCheckBox" name="reporting_services"> 259 <widget class="QCheckBox" name="create_crash_dumps">
253 <property name="text"> 260 <property name="text">
254 <string>Enable Verbose Reporting Services**</string> 261 <string>Create Minidump After Crash</string>
255 </property> 262 </property>
256 </widget> 263 </widget>
257 </item> 264 </item>
@@ -306,6 +313,16 @@
306 </property> 313 </property>
307 </widget> 314 </widget>
308 </item> 315 </item>
316 <item row="3" column="0">
317 <widget class="QCheckBox" name="perform_vulkan_check">
318 <property name="toolTip">
319 <string>Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu.</string>
320 </property>
321 <property name="text">
322 <string>Perform Startup Vulkan Check</string>
323 </property>
324 </widget>
325 </item>
309 </layout> 326 </layout>
310 </widget> 327 </widget>
311 </item> 328 </item>
@@ -340,7 +357,6 @@
340 <tabstop>disable_loop_safety_checks</tabstop> 357 <tabstop>disable_loop_safety_checks</tabstop>
341 <tabstop>fs_access_log</tabstop> 358 <tabstop>fs_access_log</tabstop>
342 <tabstop>reporting_services</tabstop> 359 <tabstop>reporting_services</tabstop>
343 <tabstop>dump_audio_commands</tabstop>
344 <tabstop>quest_flag</tabstop> 360 <tabstop>quest_flag</tabstop>
345 <tabstop>enable_cpu_debugging</tabstop> 361 <tabstop>enable_cpu_debugging</tabstop>
346 <tabstop>use_debug_asserts</tabstop> 362 <tabstop>use_debug_asserts</tabstop>
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 16fba3deb..1db374d4a 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -65,7 +65,7 @@ void OnDockedModeChanged(bool last_state, bool new_state, Core::System& system)
65 65
66ConfigureInput::ConfigureInput(Core::System& system_, QWidget* parent) 66ConfigureInput::ConfigureInput(Core::System& system_, QWidget* parent)
67 : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), 67 : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
68 profiles(std::make_unique<InputProfiles>(system_)), system{system_} { 68 profiles(std::make_unique<InputProfiles>()), system{system_} {
69 ui->setupUi(this); 69 ui->setupUi(this);
70} 70}
71 71
@@ -163,10 +163,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
163 [this, input_subsystem, &hid_core] { 163 [this, input_subsystem, &hid_core] {
164 CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core); 164 CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core);
165 }); 165 });
166 connect(advanced, &ConfigureInputAdvanced::CallCameraDialog, 166 connect(advanced, &ConfigureInputAdvanced::CallCameraDialog, [this, input_subsystem] {
167 [this, input_subsystem, &hid_core] { 167 CallConfigureDialog<ConfigureCamera>(*this, input_subsystem);
168 CallConfigureDialog<ConfigureCamera>(*this, input_subsystem); 168 });
169 });
170 169
171 connect(ui->vibrationButton, &QPushButton::clicked, 170 connect(ui->vibrationButton, &QPushButton::clicked,
172 [this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); }); 171 [this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); });
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 109689c88..9e5a40fe7 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -161,6 +161,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) {
161 161
162 const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); 162 const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : "");
163 const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : ""); 163 const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : "");
164 const QString invert = QString::fromStdString(param.Get("invert", "+") == "-" ? "-" : "");
164 const auto common_button_name = input_subsystem->GetButtonName(param); 165 const auto common_button_name = input_subsystem->GetButtonName(param);
165 166
166 // Retrieve the names from Qt 167 // Retrieve the names from Qt
@@ -184,7 +185,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) {
184 } 185 }
185 if (param.Has("axis")) { 186 if (param.Has("axis")) {
186 const QString axis = QString::fromStdString(param.Get("axis", "")); 187 const QString axis = QString::fromStdString(param.Get("axis", ""));
187 return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis); 188 return QObject::tr("%1%2Axis %3").arg(toggle, invert, axis);
188 } 189 }
189 if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) { 190 if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) {
190 const QString axis_x = QString::fromStdString(param.Get("axis_x", "")); 191 const QString axis_x = QString::fromStdString(param.Get("axis_x", ""));
@@ -362,18 +363,18 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
362 button_map[button_id]->setText(tr("[not set]")); 363 button_map[button_id]->setText(tr("[not set]"));
363 }); 364 });
364 if (param.Has("code") || param.Has("button") || param.Has("hat")) { 365 if (param.Has("code") || param.Has("button") || param.Has("hat")) {
365 context_menu.addAction(tr("Toggle button"), [&] {
366 const bool toggle_value = !param.Get("toggle", false);
367 param.Set("toggle", toggle_value);
368 button_map[button_id]->setText(ButtonToText(param));
369 emulated_controller->SetButtonParam(button_id, param);
370 });
371 context_menu.addAction(tr("Invert button"), [&] { 366 context_menu.addAction(tr("Invert button"), [&] {
372 const bool invert_value = !param.Get("inverted", false); 367 const bool invert_value = !param.Get("inverted", false);
373 param.Set("inverted", invert_value); 368 param.Set("inverted", invert_value);
374 button_map[button_id]->setText(ButtonToText(param)); 369 button_map[button_id]->setText(ButtonToText(param));
375 emulated_controller->SetButtonParam(button_id, param); 370 emulated_controller->SetButtonParam(button_id, param);
376 }); 371 });
372 context_menu.addAction(tr("Toggle button"), [&] {
373 const bool toggle_value = !param.Get("toggle", false);
374 param.Set("toggle", toggle_value);
375 button_map[button_id]->setText(ButtonToText(param));
376 emulated_controller->SetButtonParam(button_id, param);
377 });
377 } 378 }
378 if (param.Has("axis")) { 379 if (param.Has("axis")) {
379 context_menu.addAction(tr("Invert axis"), [&] { 380 context_menu.addAction(tr("Invert axis"), [&] {
@@ -398,6 +399,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
398 } 399 }
399 emulated_controller->SetButtonParam(button_id, param); 400 emulated_controller->SetButtonParam(button_id, param);
400 }); 401 });
402 context_menu.addAction(tr("Toggle axis"), [&] {
403 const bool toggle_value = !param.Get("toggle", false);
404 param.Set("toggle", toggle_value);
405 button_map[button_id]->setText(ButtonToText(param));
406 emulated_controller->SetButtonParam(button_id, param);
407 });
401 } 408 }
402 context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); 409 context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
403 }); 410 });
@@ -1410,7 +1417,7 @@ void ConfigureInputPlayer::HandleClick(
1410 ui->controllerFrame->BeginMappingAnalog(button_id); 1417 ui->controllerFrame->BeginMappingAnalog(button_id);
1411 } 1418 }
1412 1419
1413 timeout_timer->start(2500); // Cancel after 2.5 seconds 1420 timeout_timer->start(4000); // Cancel after 4 seconds
1414 poll_timer->start(25); // Check for new inputs every 25ms 1421 poll_timer->start(25); // Check for new inputs every 25ms
1415} 1422}
1416 1423
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
index af8343b2e..c3cb8f61d 100644
--- a/src/yuzu/configuration/configure_per_game.cpp
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -42,8 +42,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
42 const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name)); 42 const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name));
43 const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()) 43 const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename())
44 : fmt::format("{:016X}", title_id); 44 : fmt::format("{:016X}", title_id);
45 game_config = 45 game_config = std::make_unique<Config>(config_file_name, Config::ConfigType::PerGameConfig);
46 std::make_unique<Config>(system, config_file_name, Config::ConfigType::PerGameConfig);
47 46
48 addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this); 47 addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this);
49 audio_tab = std::make_unique<ConfigureAudio>(system_, this); 48 audio_tab = std::make_unique<ConfigureAudio>(system_, this);
diff --git a/src/yuzu/configuration/configure_tas.ui b/src/yuzu/configuration/configure_tas.ui
index cf88a5bf0..625af0c89 100644
--- a/src/yuzu/configuration/configure_tas.ui
+++ b/src/yuzu/configuration/configure_tas.ui
@@ -16,6 +16,9 @@
16 <property name="text"> 16 <property name="text">
17 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Reads controller input from scripts in the same format as TAS-nx scripts.&lt;br/&gt;For a more detailed explanation, please consult the &lt;a href=&quot;https://yuzu-emu.org/help/feature/tas/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;help page&lt;/span&gt;&lt;/a&gt; on the yuzu website.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> 17 <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Reads controller input from scripts in the same format as TAS-nx scripts.&lt;br/&gt;For a more detailed explanation, please consult the &lt;a href=&quot;https://yuzu-emu.org/help/feature/tas/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;help page&lt;/span&gt;&lt;/a&gt; on the yuzu website.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
18 </property> 18 </property>
19 <property name="openExternalLinks">
20 <bool>true</bool>
21 </property>
19 </widget> 22 </widget>
20 </item> 23 </item>
21 <item row="1" column="0" colspan="4"> 24 <item row="1" column="0" colspan="4">
diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp
index d668c992b..ab526e4ca 100644
--- a/src/yuzu/configuration/configure_web.cpp
+++ b/src/yuzu/configuration/configure_web.cpp
@@ -128,20 +128,25 @@ void ConfigureWeb::RefreshTelemetryID() {
128void ConfigureWeb::OnLoginChanged() { 128void ConfigureWeb::OnLoginChanged() {
129 if (ui->edit_token->text().isEmpty()) { 129 if (ui->edit_token->text().isEmpty()) {
130 user_verified = true; 130 user_verified = true;
131 131 // Empty = no icon
132 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16); 132 ui->label_token_verified->setPixmap(QPixmap());
133 ui->label_token_verified->setPixmap(pixmap); 133 ui->label_token_verified->setToolTip(QString());
134 } else { 134 } else {
135 user_verified = false; 135 user_verified = false;
136 136
137 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16); 137 // Show an info icon if it's been changed, clearer than showing failure
138 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("info")).pixmap(16);
138 ui->label_token_verified->setPixmap(pixmap); 139 ui->label_token_verified->setPixmap(pixmap);
140 ui->label_token_verified->setToolTip(
141 tr("Unverified, please click Verify before saving configuration", "Tooltip"));
139 } 142 }
140} 143}
141 144
142void ConfigureWeb::VerifyLogin() { 145void ConfigureWeb::VerifyLogin() {
143 ui->button_verify_login->setDisabled(true); 146 ui->button_verify_login->setDisabled(true);
144 ui->button_verify_login->setText(tr("Verifying...")); 147 ui->button_verify_login->setText(tr("Verifying..."));
148 ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("sync")).pixmap(16));
149 ui->label_token_verified->setToolTip(tr("Verifying..."));
145 verify_watcher.setFuture(QtConcurrent::run( 150 verify_watcher.setFuture(QtConcurrent::run(
146 [username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()), 151 [username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()),
147 token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] { 152 token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] {
@@ -155,13 +160,13 @@ void ConfigureWeb::OnLoginVerified() {
155 if (verify_watcher.result()) { 160 if (verify_watcher.result()) {
156 user_verified = true; 161 user_verified = true;
157 162
158 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16); 163 ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("checked")).pixmap(16));
159 ui->label_token_verified->setPixmap(pixmap); 164 ui->label_token_verified->setToolTip(tr("Verified", "Tooltip"));
160 ui->username->setText( 165 ui->username->setText(
161 QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString()))); 166 QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString())));
162 } else { 167 } else {
163 const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16); 168 ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("failed")).pixmap(16));
164 ui->label_token_verified->setPixmap(pixmap); 169 ui->label_token_verified->setToolTip(tr("Verification failed", "Tooltip"));
165 ui->username->setText(tr("Unspecified")); 170 ui->username->setText(tr("Unspecified"));
166 QMessageBox::critical(this, tr("Verification failed"), 171 QMessageBox::critical(this, tr("Verification failed"),
167 tr("Verification failed. Check that you have entered your token " 172 tr("Verification failed. Check that you have entered your token "
diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp
index 20b22e7de..9bb69cab1 100644
--- a/src/yuzu/configuration/input_profiles.cpp
+++ b/src/yuzu/configuration/input_profiles.cpp
@@ -27,7 +27,7 @@ std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) {
27 27
28} // namespace 28} // namespace
29 29
30InputProfiles::InputProfiles(Core::System& system_) : system{system_} { 30InputProfiles::InputProfiles() {
31 const auto input_profile_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input"; 31 const auto input_profile_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input";
32 32
33 if (!FS::IsDir(input_profile_loc)) { 33 if (!FS::IsDir(input_profile_loc)) {
@@ -43,8 +43,8 @@ InputProfiles::InputProfiles(Core::System& system_) : system{system_} {
43 43
44 if (IsINI(filename) && IsProfileNameValid(name_without_ext)) { 44 if (IsINI(filename) && IsProfileNameValid(name_without_ext)) {
45 map_profiles.insert_or_assign( 45 map_profiles.insert_or_assign(
46 name_without_ext, std::make_unique<Config>(system, name_without_ext, 46 name_without_ext,
47 Config::ConfigType::InputProfile)); 47 std::make_unique<Config>(name_without_ext, Config::ConfigType::InputProfile));
48 } 48 }
49 49
50 return true; 50 return true;
@@ -67,6 +67,8 @@ std::vector<std::string> InputProfiles::GetInputProfileNames() {
67 profile_names.push_back(profile_name); 67 profile_names.push_back(profile_name);
68 } 68 }
69 69
70 std::stable_sort(profile_names.begin(), profile_names.end());
71
70 return profile_names; 72 return profile_names;
71} 73}
72 74
@@ -80,8 +82,7 @@ bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t p
80 } 82 }
81 83
82 map_profiles.insert_or_assign( 84 map_profiles.insert_or_assign(
83 profile_name, 85 profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile));
84 std::make_unique<Config>(system, profile_name, Config::ConfigType::InputProfile));
85 86
86 return SaveProfile(profile_name, player_index); 87 return SaveProfile(profile_name, player_index);
87} 88}
diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h
index 65fc9e62c..2bf3e4250 100644
--- a/src/yuzu/configuration/input_profiles.h
+++ b/src/yuzu/configuration/input_profiles.h
@@ -15,7 +15,7 @@ class Config;
15class InputProfiles { 15class InputProfiles {
16 16
17public: 17public:
18 explicit InputProfiles(Core::System& system_); 18 explicit InputProfiles();
19 virtual ~InputProfiles(); 19 virtual ~InputProfiles();
20 20
21 std::vector<std::string> GetInputProfileNames(); 21 std::vector<std::string> GetInputProfileNames();
@@ -31,6 +31,4 @@ private:
31 bool ProfileExistsInMap(const std::string& profile_name) const; 31 bool ProfileExistsInMap(const std::string& profile_name) const;
32 32
33 std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles; 33 std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles;
34
35 Core::System& system;
36}; 34};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index deee1c370..612b8dbb8 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -138,6 +138,10 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
138#include "yuzu/uisettings.h" 138#include "yuzu/uisettings.h"
139#include "yuzu/util/clickable_label.h" 139#include "yuzu/util/clickable_label.h"
140 140
141#ifdef YUZU_DBGHELP
142#include "yuzu/mini_dump.h"
143#endif
144
141using namespace Common::Literals; 145using namespace Common::Literals;
142 146
143#ifdef USE_DISCORD_PRESENCE 147#ifdef USE_DISCORD_PRESENCE
@@ -269,10 +273,9 @@ bool GMainWindow::CheckDarkMode() {
269#endif // __linux__ 273#endif // __linux__
270} 274}
271 275
272GMainWindow::GMainWindow(bool has_broken_vulkan) 276GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan)
273 : ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()}, 277 : ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()},
274 input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, 278 input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::move(config_)},
275 config{std::make_unique<Config>(*system)},
276 vfs{std::make_shared<FileSys::RealVfsFilesystem>()}, 279 vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
277 provider{std::make_unique<FileSys::ManualContentProvider>()} { 280 provider{std::make_unique<FileSys::ManualContentProvider>()} {
278#ifdef __linux__ 281#ifdef __linux__
@@ -1640,7 +1643,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
1640 const auto config_file_name = title_id == 0 1643 const auto config_file_name = title_id == 0
1641 ? Common::FS::PathToUTF8String(file_path.filename()) 1644 ? Common::FS::PathToUTF8String(file_path.filename())
1642 : fmt::format("{:016X}", title_id); 1645 : fmt::format("{:016X}", title_id);
1643 Config per_game_config(*system, config_file_name, Config::ConfigType::PerGameConfig); 1646 Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
1647 system->ApplySettings();
1644 } 1648 }
1645 1649
1646 // Save configurations 1650 // Save configurations
@@ -1999,7 +2003,7 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src
1999} 2003}
2000 2004
2001void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) { 2005void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) {
2002 const QString entry_type = [this, type] { 2006 const QString entry_type = [type] {
2003 switch (type) { 2007 switch (type) {
2004 case InstalledEntryType::Game: 2008 case InstalledEntryType::Game:
2005 return tr("Contents"); 2009 return tr("Contents");
@@ -2096,7 +2100,7 @@ void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type)
2096 2100
2097void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, 2101void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
2098 const std::string& game_path) { 2102 const std::string& game_path) {
2099 const QString question = [this, target] { 2103 const QString question = [target] {
2100 switch (target) { 2104 switch (target) {
2101 case GameListRemoveTarget::GlShaderCache: 2105 case GameListRemoveTarget::GlShaderCache:
2102 return tr("Delete OpenGL Transferable Shader Cache?"); 2106 return tr("Delete OpenGL Transferable Shader Cache?");
@@ -2989,7 +2993,7 @@ void GMainWindow::OnConfigure() {
2989 2993
2990 Settings::values.disabled_addons.clear(); 2994 Settings::values.disabled_addons.clear();
2991 2995
2992 config = std::make_unique<Config>(*system); 2996 config = std::make_unique<Config>();
2993 UISettings::values.reset_to_defaults = false; 2997 UISettings::values.reset_to_defaults = false;
2994 2998
2995 UISettings::values.game_dirs = std::move(old_game_dirs); 2999 UISettings::values.game_dirs = std::move(old_game_dirs);
@@ -3050,6 +3054,7 @@ void GMainWindow::OnConfigure() {
3050 3054
3051 UpdateStatusButtons(); 3055 UpdateStatusButtons();
3052 controller_dialog->refreshConfiguration(); 3056 controller_dialog->refreshConfiguration();
3057 system->ApplySettings();
3053} 3058}
3054 3059
3055void GMainWindow::OnConfigureTas() { 3060void GMainWindow::OnConfigureTas() {
@@ -3262,26 +3267,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) {
3262 return; 3267 return;
3263 } 3268 }
3264 3269
3265 QFile nfc_file{filename}; 3270 if (!nfc->LoadAmiibo(filename.toStdString())) {
3266 if (!nfc_file.open(QIODevice::ReadOnly)) {
3267 QMessageBox::warning(this, tr("Error opening Amiibo data file"),
3268 tr("Unable to open Amiibo file \"%1\" for reading.").arg(filename));
3269 return;
3270 }
3271
3272 const u64 nfc_file_size = nfc_file.size();
3273 std::vector<u8> buffer(nfc_file_size);
3274 const u64 read_size = nfc_file.read(reinterpret_cast<char*>(buffer.data()), nfc_file_size);
3275 if (nfc_file_size != read_size) {
3276 QMessageBox::warning(this, tr("Error reading Amiibo data file"),
3277 tr("Unable to fully read Amiibo data. Expected to read %1 bytes, but "
3278 "was only able to read %2 bytes.")
3279 .arg(nfc_file_size)
3280 .arg(read_size));
3281 return;
3282 }
3283
3284 if (!nfc->LoadAmiibo(buffer)) {
3285 QMessageBox::warning(this, tr("Error loading Amiibo data"), 3271 QMessageBox::warning(this, tr("Error loading Amiibo data"),
3286 tr("Unable to load Amiibo data.")); 3272 tr("Unable to load Amiibo data."));
3287 } 3273 }
@@ -4090,8 +4076,26 @@ void GMainWindow::changeEvent(QEvent* event) {
4090#endif 4076#endif
4091 4077
4092int main(int argc, char* argv[]) { 4078int main(int argc, char* argv[]) {
4079 std::unique_ptr<Config> config = std::make_unique<Config>();
4093 bool has_broken_vulkan = false; 4080 bool has_broken_vulkan = false;
4094 if (StartupChecks(argv[0], &has_broken_vulkan)) { 4081 bool is_child = false;
4082 if (CheckEnvVars(&is_child)) {
4083 return 0;
4084 }
4085
4086#ifdef YUZU_DBGHELP
4087 PROCESS_INFORMATION pi;
4088 if (!is_child && Settings::values.create_crash_dumps.GetValue() &&
4089 MiniDump::SpawnDebuggee(argv[0], pi)) {
4090 // Delete the config object so that it doesn't save when the program exits
4091 config.reset(nullptr);
4092 MiniDump::DebugDebuggee(pi);
4093 return 0;
4094 }
4095#endif
4096
4097 if (StartupChecks(argv[0], &has_broken_vulkan,
4098 Settings::values.perform_vulkan_check.GetValue())) {
4095 return 0; 4099 return 0;
4096 } 4100 }
4097 4101
@@ -4143,7 +4147,7 @@ int main(int argc, char* argv[]) {
4143 // generating shaders 4147 // generating shaders
4144 setlocale(LC_ALL, "C"); 4148 setlocale(LC_ALL, "C");
4145 4149
4146 GMainWindow main_window{has_broken_vulkan}; 4150 GMainWindow main_window{std::move(config), has_broken_vulkan};
4147 // After settings have been loaded by GMainWindow, apply the filter 4151 // After settings have been loaded by GMainWindow, apply the filter
4148 main_window.show(); 4152 main_window.show();
4149 4153
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 1a756b171..f7aa8e417 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -120,7 +120,7 @@ class GMainWindow : public QMainWindow {
120public: 120public:
121 void filterBarSetChecked(bool state); 121 void filterBarSetChecked(bool state);
122 void UpdateUITheme(); 122 void UpdateUITheme();
123 explicit GMainWindow(bool has_broken_vulkan); 123 explicit GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan);
124 ~GMainWindow() override; 124 ~GMainWindow() override;
125 125
126 bool DropAction(QDropEvent* event); 126 bool DropAction(QDropEvent* event);
diff --git a/src/yuzu/mini_dump.cpp b/src/yuzu/mini_dump.cpp
new file mode 100644
index 000000000..a34dc6a9c
--- /dev/null
+++ b/src/yuzu/mini_dump.cpp
@@ -0,0 +1,202 @@
1// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <cstdio>
5#include <cstring>
6#include <ctime>
7#include <filesystem>
8#include <fmt/format.h>
9#include <windows.h>
10#include "yuzu/mini_dump.h"
11#include "yuzu/startup_checks.h"
12
13// dbghelp.h must be included after windows.h
14#include <dbghelp.h>
15
16namespace MiniDump {
17
18void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
19 EXCEPTION_POINTERS* pep) {
20 char file_name[255];
21 const std::time_t the_time = std::time(nullptr);
22 std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time));
23
24 // Open the file
25 HANDLE file_handle = CreateFileA(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr,
26 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
27
28 if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) {
29 fmt::print(stderr, "CreateFileA failed. Error: {}", GetLastError());
30 return;
31 }
32
33 // Create the minidump
34 const MINIDUMP_TYPE dump_type = MiniDumpNormal;
35
36 const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle,
37 dump_type, (pep != 0) ? info : 0, 0, 0);
38
39 if (write_dump_status) {
40 fmt::print(stderr, "MiniDump created: {}", file_name);
41 } else {
42 fmt::print(stderr, "MiniDumpWriteDump failed. Error: {}", GetLastError());
43 }
44
45 // Close the file
46 CloseHandle(file_handle);
47}
48
49void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) {
50 EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
51
52 HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId);
53 if (thread_handle == nullptr) {
54 fmt::print(stderr, "OpenThread failed ({})", GetLastError());
55 return;
56 }
57
58 // Get child process context
59 CONTEXT context = {};
60 context.ContextFlags = CONTEXT_ALL;
61 if (!GetThreadContext(thread_handle, &context)) {
62 fmt::print(stderr, "GetThreadContext failed ({})", GetLastError());
63 return;
64 }
65
66 // Create exception pointers for minidump
67 EXCEPTION_POINTERS ep;
68 ep.ExceptionRecord = &record;
69 ep.ContextRecord = &context;
70
71 MINIDUMP_EXCEPTION_INFORMATION info;
72 info.ThreadId = deb_ev.dwThreadId;
73 info.ExceptionPointers = &ep;
74 info.ClientPointers = false;
75
76 CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep);
77
78 if (CloseHandle(thread_handle) == 0) {
79 fmt::print(stderr, "error: CloseHandle(thread_handle) failed ({})", GetLastError());
80 }
81}
82
83bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) {
84 std::memset(&pi, 0, sizeof(pi));
85
86 // Don't debug if we are already being debugged
87 if (IsDebuggerPresent()) {
88 return false;
89 }
90
91 if (!SpawnChild(arg0, &pi, 0)) {
92 fmt::print(stderr, "warning: continuing without crash dumps");
93 return false;
94 }
95
96 const bool can_debug = DebugActiveProcess(pi.dwProcessId);
97 if (!can_debug) {
98 fmt::print(stderr,
99 "warning: DebugActiveProcess failed ({}), continuing without crash dumps",
100 GetLastError());
101 return false;
102 }
103
104 return true;
105}
106
107static const char* ExceptionName(DWORD exception) {
108 switch (exception) {
109 case EXCEPTION_ACCESS_VIOLATION:
110 return "EXCEPTION_ACCESS_VIOLATION";
111 case EXCEPTION_DATATYPE_MISALIGNMENT:
112 return "EXCEPTION_DATATYPE_MISALIGNMENT";
113 case EXCEPTION_BREAKPOINT:
114 return "EXCEPTION_BREAKPOINT";
115 case EXCEPTION_SINGLE_STEP:
116 return "EXCEPTION_SINGLE_STEP";
117 case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
118 return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
119 case EXCEPTION_FLT_DENORMAL_OPERAND:
120 return "EXCEPTION_FLT_DENORMAL_OPERAND";
121 case EXCEPTION_FLT_DIVIDE_BY_ZERO:
122 return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
123 case EXCEPTION_FLT_INEXACT_RESULT:
124 return "EXCEPTION_FLT_INEXACT_RESULT";
125 case EXCEPTION_FLT_INVALID_OPERATION:
126 return "EXCEPTION_FLT_INVALID_OPERATION";
127 case EXCEPTION_FLT_OVERFLOW:
128 return "EXCEPTION_FLT_OVERFLOW";
129 case EXCEPTION_FLT_STACK_CHECK:
130 return "EXCEPTION_FLT_STACK_CHECK";
131 case EXCEPTION_FLT_UNDERFLOW:
132 return "EXCEPTION_FLT_UNDERFLOW";
133 case EXCEPTION_INT_DIVIDE_BY_ZERO:
134 return "EXCEPTION_INT_DIVIDE_BY_ZERO";
135 case EXCEPTION_INT_OVERFLOW:
136 return "EXCEPTION_INT_OVERFLOW";
137 case EXCEPTION_PRIV_INSTRUCTION:
138 return "EXCEPTION_PRIV_INSTRUCTION";
139 case EXCEPTION_IN_PAGE_ERROR:
140 return "EXCEPTION_IN_PAGE_ERROR";
141 case EXCEPTION_ILLEGAL_INSTRUCTION:
142 return "EXCEPTION_ILLEGAL_INSTRUCTION";
143 case EXCEPTION_NONCONTINUABLE_EXCEPTION:
144 return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
145 case EXCEPTION_STACK_OVERFLOW:
146 return "EXCEPTION_STACK_OVERFLOW";
147 case EXCEPTION_INVALID_DISPOSITION:
148 return "EXCEPTION_INVALID_DISPOSITION";
149 case EXCEPTION_GUARD_PAGE:
150 return "EXCEPTION_GUARD_PAGE";
151 case EXCEPTION_INVALID_HANDLE:
152 return "EXCEPTION_INVALID_HANDLE";
153 default:
154 return "unknown exception type";
155 }
156}
157
158void DebugDebuggee(PROCESS_INFORMATION& pi) {
159 DEBUG_EVENT deb_ev = {};
160
161 while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) {
162 const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE);
163 if (!wait_success) {
164 fmt::print(stderr, "error: WaitForDebugEvent failed ({})", GetLastError());
165 return;
166 }
167
168 switch (deb_ev.dwDebugEventCode) {
169 case OUTPUT_DEBUG_STRING_EVENT:
170 case CREATE_PROCESS_DEBUG_EVENT:
171 case CREATE_THREAD_DEBUG_EVENT:
172 case EXIT_PROCESS_DEBUG_EVENT:
173 case EXIT_THREAD_DEBUG_EVENT:
174 case LOAD_DLL_DEBUG_EVENT:
175 case RIP_EVENT:
176 case UNLOAD_DLL_DEBUG_EVENT:
177 // Continue on all other debug events
178 ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE);
179 break;
180 case EXCEPTION_DEBUG_EVENT:
181 EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord;
182
183 // We want to generate a crash dump if we are seeing the same exception again.
184 if (!deb_ev.u.Exception.dwFirstChance) {
185 fmt::print(stderr, "Creating MiniDump on ExceptionCode: 0x{:08x} {}\n",
186 record.ExceptionCode, ExceptionName(record.ExceptionCode));
187 DumpFromDebugEvent(deb_ev, pi);
188 }
189
190 // Continue without handling the exception.
191 // Lets the debuggee use its own exception handler.
192 // - If one does not exist, we will see the exception once more where we make a minidump
193 // for. Then when it reaches here again, yuzu will probably crash.
194 // - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an
195 // infinite loop of exceptions.
196 ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED);
197 break;
198 }
199 }
200}
201
202} // namespace MiniDump
diff --git a/src/yuzu/mini_dump.h b/src/yuzu/mini_dump.h
new file mode 100644
index 000000000..d6b6cca84
--- /dev/null
+++ b/src/yuzu/mini_dump.h
@@ -0,0 +1,19 @@
1// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <windows.h>
7
8#include <dbghelp.h>
9
10namespace MiniDump {
11
12void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info,
13 EXCEPTION_POINTERS* pep);
14
15void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi);
16bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi);
17void DebugDebuggee(PROCESS_INFORMATION& pi);
18
19} // namespace MiniDump
diff --git a/src/yuzu/startup_checks.cpp b/src/yuzu/startup_checks.cpp
index 8421280bf..fc2693f9d 100644
--- a/src/yuzu/startup_checks.cpp
+++ b/src/yuzu/startup_checks.cpp
@@ -31,48 +31,68 @@ void CheckVulkan() {
31 } 31 }
32} 32}
33 33
34bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { 34bool CheckEnvVars(bool* is_child) {
35#ifdef _WIN32 35#ifdef _WIN32
36 // Check environment variable to see if we are the child 36 // Check environment variable to see if we are the child
37 char variable_contents[8]; 37 char variable_contents[8];
38 const DWORD startup_check_var = 38 const DWORD startup_check_var =
39 GetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, variable_contents, 8); 39 GetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, variable_contents, 8);
40 if (startup_check_var > 0 && std::strncmp(variable_contents, "ON", 8) == 0) { 40 if (startup_check_var > 0 && std::strncmp(variable_contents, ENV_VAR_ENABLED_TEXT, 8) == 0) {
41 CheckVulkan(); 41 CheckVulkan();
42 return true; 42 return true;
43 } 43 }
44 44
45 // Don't perform startup checks if we are a child process
46 char is_child_s[8];
47 const DWORD is_child_len = GetEnvironmentVariableA(IS_CHILD_ENV_VAR, is_child_s, 8);
48 if (is_child_len > 0 && std::strncmp(is_child_s, ENV_VAR_ENABLED_TEXT, 8) == 0) {
49 *is_child = true;
50 return false;
51 } else if (!SetEnvironmentVariableA(IS_CHILD_ENV_VAR, ENV_VAR_ENABLED_TEXT)) {
52 std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n",
53 IS_CHILD_ENV_VAR, GetLastError());
54 return true;
55 }
56#endif
57 return false;
58}
59
60bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulkan_check) {
61#ifdef _WIN32
45 // Set the startup variable for child processes 62 // Set the startup variable for child processes
46 const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, "ON"); 63 const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, ENV_VAR_ENABLED_TEXT);
47 if (!env_var_set) { 64 if (!env_var_set) {
48 std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n", 65 std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n",
49 STARTUP_CHECK_ENV_VAR, GetLastError()); 66 STARTUP_CHECK_ENV_VAR, GetLastError());
50 return false; 67 return false;
51 } 68 }
52 69
53 PROCESS_INFORMATION process_info; 70 if (perform_vulkan_check) {
54 std::memset(&process_info, '\0', sizeof(process_info)); 71 // Spawn child process that performs Vulkan check
55 72 PROCESS_INFORMATION process_info;
56 if (!SpawnChild(arg0, &process_info)) { 73 std::memset(&process_info, '\0', sizeof(process_info));
57 return false; 74
58 } 75 if (!SpawnChild(arg0, &process_info, 0)) {
59 76 return false;
60 // Wait until the processs exits and get exit code from it 77 }
61 WaitForSingleObject(process_info.hProcess, INFINITE); 78
62 DWORD exit_code = STILL_ACTIVE; 79 // Wait until the processs exits and get exit code from it
63 const int err = GetExitCodeProcess(process_info.hProcess, &exit_code); 80 WaitForSingleObject(process_info.hProcess, INFINITE);
64 if (err == 0) { 81 DWORD exit_code = STILL_ACTIVE;
65 std::fprintf(stderr, "GetExitCodeProcess failed with error %d\n", GetLastError()); 82 const int err = GetExitCodeProcess(process_info.hProcess, &exit_code);
66 } 83 if (err == 0) {
67 84 std::fprintf(stderr, "GetExitCodeProcess failed with error %d\n", GetLastError());
68 // Vulkan is broken if the child crashed (return value is not zero) 85 }
69 *has_broken_vulkan = (exit_code != 0); 86
70 87 // Vulkan is broken if the child crashed (return value is not zero)
71 if (CloseHandle(process_info.hProcess) == 0) { 88 *has_broken_vulkan = (exit_code != 0);
72 std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError()); 89
73 } 90 if (CloseHandle(process_info.hProcess) == 0) {
74 if (CloseHandle(process_info.hThread) == 0) { 91 std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError());
75 std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError()); 92 }
93 if (CloseHandle(process_info.hThread) == 0) {
94 std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError());
95 }
76 } 96 }
77 97
78 if (!SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, nullptr)) { 98 if (!SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, nullptr)) {
@@ -81,32 +101,34 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) {
81 } 101 }
82 102
83#elif defined(YUZU_UNIX) 103#elif defined(YUZU_UNIX)
84 const pid_t pid = fork(); 104 if (perform_vulkan_check) {
85 if (pid == 0) { 105 const pid_t pid = fork();
86 CheckVulkan(); 106 if (pid == 0) {
87 return true; 107 CheckVulkan();
88 } else if (pid == -1) { 108 return true;
89 const int err = errno; 109 } else if (pid == -1) {
90 std::fprintf(stderr, "fork failed with error %d\n", err); 110 const int err = errno;
91 return false; 111 std::fprintf(stderr, "fork failed with error %d\n", err);
92 } 112 return false;
93 113 }
94 // Get exit code from child process 114
95 int status; 115 // Get exit code from child process
96 const int r_val = wait(&status); 116 int status;
97 if (r_val == -1) { 117 const int r_val = wait(&status);
98 const int err = errno; 118 if (r_val == -1) {
99 std::fprintf(stderr, "wait failed with error %d\n", err); 119 const int err = errno;
100 return false; 120 std::fprintf(stderr, "wait failed with error %d\n", err);
121 return false;
122 }
123 // Vulkan is broken if the child crashed (return value is not zero)
124 *has_broken_vulkan = (status != 0);
101 } 125 }
102 // Vulkan is broken if the child crashed (return value is not zero)
103 *has_broken_vulkan = (status != 0);
104#endif 126#endif
105 return false; 127 return false;
106} 128}
107 129
108#ifdef _WIN32 130#ifdef _WIN32
109bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) { 131bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags) {
110 STARTUPINFOA startup_info; 132 STARTUPINFOA startup_info;
111 133
112 std::memset(&startup_info, '\0', sizeof(startup_info)); 134 std::memset(&startup_info, '\0', sizeof(startup_info));
@@ -120,7 +142,7 @@ bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) {
120 nullptr, // lpProcessAttributes 142 nullptr, // lpProcessAttributes
121 nullptr, // lpThreadAttributes 143 nullptr, // lpThreadAttributes
122 false, // bInheritHandles 144 false, // bInheritHandles
123 0, // dwCreationFlags 145 flags, // dwCreationFlags
124 nullptr, // lpEnvironment 146 nullptr, // lpEnvironment
125 nullptr, // lpCurrentDirectory 147 nullptr, // lpCurrentDirectory
126 &startup_info, // lpStartupInfo 148 &startup_info, // lpStartupInfo
diff --git a/src/yuzu/startup_checks.h b/src/yuzu/startup_checks.h
index 096dd54a8..d8e563be6 100644
--- a/src/yuzu/startup_checks.h
+++ b/src/yuzu/startup_checks.h
@@ -7,11 +7,14 @@
7#include <windows.h> 7#include <windows.h>
8#endif 8#endif
9 9
10constexpr char IS_CHILD_ENV_VAR[] = "YUZU_IS_CHILD";
10constexpr char STARTUP_CHECK_ENV_VAR[] = "YUZU_DO_STARTUP_CHECKS"; 11constexpr char STARTUP_CHECK_ENV_VAR[] = "YUZU_DO_STARTUP_CHECKS";
12constexpr char ENV_VAR_ENABLED_TEXT[] = "ON";
11 13
12void CheckVulkan(); 14void CheckVulkan();
13bool StartupChecks(const char* arg0, bool* has_broken_vulkan); 15bool CheckEnvVars(bool* is_child);
16bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulkan_check);
14 17
15#ifdef _WIN32 18#ifdef _WIN32
16bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi); 19bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags);
17#endif 20#endif