diff options
Diffstat (limited to 'src')
121 files changed, 3702 insertions, 1799 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 | ||
| 9 | namespace AudioCore { | 9 | namespace AudioCore { |
| 10 | 10 | ||
| 11 | AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>(system)} { | 11 | AudioCore::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 | ||
| 50 | void AudioCore::PauseSinks(const bool pausing) const { | 50 | void 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 | ||
| 60 | u32 AudioCore::GetStreamQueue() const { | 54 | bool AudioCore::IsNVDECActive() const { |
| 61 | return estimated_queue.load(); | 55 | return nvdec_active; |
| 62 | } | ||
| 63 | |||
| 64 | void 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 | ||
| 18 | class AudioManager; | 18 | class 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 | */ |
| 22 | class AudioCore { | 22 | class AudioCore { |
| 23 | public: | 23 | public: |
| @@ -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 | ||
| 82 | private: | 72 | private: |
| 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 | ||
| 83 | private: | 83 | private: |
| 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 | ||
| 9 | namespace AudioCore { | 8 | namespace AudioCore { |
| 10 | 9 | ||
| 11 | AudioManager::AudioManager(Core::System& system_) : system{system_} { | 10 | AudioManager::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 | ||
| 15 | namespace Core { | 14 | union Result; |
| 16 | class System; | ||
| 17 | } | ||
| 18 | 15 | ||
| 19 | namespace AudioCore { | 16 | namespace AudioCore { |
| 20 | 17 | ||
| 21 | namespace AudioOut { | ||
| 22 | class Manager; | ||
| 23 | } | ||
| 24 | |||
| 25 | namespace AudioIn { | ||
| 26 | class 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 | ||
| 45 | public: | 34 | public: |
| 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 | ||
| 77 | private: | 66 | private: |
| 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 | ||
| 75 | u32 Manager::GetAudioOutDeviceNames( | 75 | u32 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 | ||
| 28 | auto Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) | 28 | Result 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 | ||
| 57 | u32 Manager::GetSessionCount() { | 57 | u32 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 @@ | |||
| 8 | namespace AudioCore { | 8 | namespace AudioCore { |
| 9 | 9 | ||
| 10 | struct AudioBuffer { | 10 | struct 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 | |||
| 283 | private: | 296 | private: |
| 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 | ||
| 12 | namespace AudioCore { | 13 | namespace AudioCore { |
| 13 | 14 | ||
| 14 | DeviceSession::DeviceSession(Core::System& system_) : system{system_} {} | 15 | using namespace std::literals; |
| 16 | constexpr auto INCREMENT_TIME{5ms}; | ||
| 17 | |||
| 18 | DeviceSession::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 | ||
| 16 | DeviceSession::~DeviceSession() { | 25 | DeviceSession::~DeviceSession() { |
| 17 | Finalize(); | 26 | Finalize(); |
| @@ -50,25 +59,26 @@ void DeviceSession::Finalize() { | |||
| 50 | } | 59 | } |
| 51 | 60 | ||
| 52 | void DeviceSession::Start() { | 61 | void 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 | ||
| 57 | void DeviceSession::Stop() { | 69 | void 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 | ||
| 64 | void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { | 76 | void 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 | ||
| 86 | void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const { | 96 | void 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 | ||
| 94 | bool DeviceSession::IsBufferConsumed(u64 tag) const { | 103 | bool 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 | ||
| 101 | void DeviceSession::SetVolume(f32 volume) const { | 107 | void DeviceSession::SetVolume(f32 volume) const { |
| @@ -105,10 +111,22 @@ void DeviceSession::SetVolume(f32 volume) const { | |||
| 105 | } | 111 | } |
| 106 | 112 | ||
| 107 | u64 DeviceSession::GetPlayedSampleCount() const { | 113 | u64 DeviceSession::GetPlayedSampleCount() const { |
| 108 | if (stream) { | 114 | return played_sample_count; |
| 109 | return stream->GetPlayedSampleCount(); | 115 | } |
| 116 | |||
| 117 | std::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 | |||
| 128 | void 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 | ||
| 12 | namespace Core { | 15 | namespace Core { |
| 13 | class System; | 16 | class System; |
| 14 | } | 17 | namespace Timing { |
| 18 | struct EventType; | ||
| 19 | } // namespace Timing | ||
| 20 | } // namespace Core | ||
| 15 | 21 | ||
| 16 | namespace AudioCore { | 22 | namespace AudioCore { |
| 23 | |||
| 17 | namespace Sink { | 24 | namespace Sink { |
| 18 | class SinkStream; | 25 | class SinkStream; |
| 19 | struct SinkBuffer; | 26 | struct 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 | |||
| 99 | private: | 117 | private: |
| 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 | ||
| 75 | f32 In::GetVolume() { | 75 | f32 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 | ||
| 85 | bool In::ContainsAudioBuffer(u64 tag) { | 85 | bool 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 | ||
| 90 | u32 In::GetBufferCount() { | 90 | u32 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 | ||
| 95 | u64 In::GetPlayedSampleCount() { | 95 | u64 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 | ||
| 136 | private: | 136 | private: |
| 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 | ||
| 37 | std::string_view System::GetDefaultDeviceName() { | 37 | std::string_view System::GetDefaultDeviceName() const { |
| 38 | return "BuiltInHeadset"; | 38 | return "BuiltInHeadset"; |
| 39 | } | 39 | } |
| 40 | 40 | ||
| 41 | std::string_view System::GetDefaultUacDeviceName() { | 41 | std::string_view System::GetDefaultUacDeviceName() const { |
| 42 | return "Uac"; | 42 | return "Uac"; |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | Result System::IsConfigValid(const std::string_view device_name, | 45 | Result 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 | ||
| 197 | bool System::ContainsAudioBuffer(const u64 tag) { | 205 | bool System::ContainsAudioBuffer(const u64 tag) const { |
| 198 | return buffers.ContainsBuffer(tag); | 206 | return buffers.ContainsBuffer(tag); |
| 199 | } | 207 | } |
| 200 | 208 | ||
| 201 | u32 System::GetBufferCount() { | 209 | u32 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 | ||
| 75 | f32 Out::GetVolume() { | 75 | f32 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 | ||
| 85 | bool Out::ContainsAudioBuffer(const u64 tag) { | 85 | bool 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 | ||
| 90 | u32 Out::GetBufferCount() { | 90 | u32 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 | ||
| 95 | u64 Out::GetPlayedSampleCount() { | 95 | u64 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 | ||
| 136 | private: | 136 | private: |
| 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 | ||
| 30 | std::string_view System::GetDefaultOutputDeviceName() { | 30 | std::string_view System::GetDefaultOutputDeviceName() const { |
| 31 | return "DeviceOut"; | 31 | return "DeviceOut"; |
| 32 | } | 32 | } |
| 33 | 33 | ||
| 34 | Result System::IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) { | 34 | Result 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 | ||
| 195 | bool System::ContainsAudioBuffer(const u64 tag) { | 204 | bool System::ContainsAudioBuffer(const u64 tag) const { |
| 196 | return buffers.ContainsBuffer(tag); | 205 | return buffers.ContainsBuffer(tag); |
| 197 | } | 206 | } |
| 198 | 207 | ||
| 199 | u32 System::GetBufferCount() { | 208 | u32 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 | ||
| 53 | void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) { | 53 | void 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 | ||
| 50 | CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) { | 50 | CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const u32 session_id) { |
| 51 | return command_buffers[session_id]; | 51 | return command_buffers[session_id]; |
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) { | 54 | void 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 | ||
| 10 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::AudioRenderer { |
| 11 | 14 | ||
| 15 | constexpr std::array usb_device_names{ | ||
| 16 | AudioDevice::AudioDeviceName{"AudioStereoJackOutput"}, | ||
| 17 | AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"}, | ||
| 18 | AudioDevice::AudioDeviceName{"AudioTvOutput"}, | ||
| 19 | AudioDevice::AudioDeviceName{"AudioUsbDeviceOutput"}, | ||
| 20 | }; | ||
| 21 | |||
| 22 | constexpr std::array device_names{ | ||
| 23 | AudioDevice::AudioDeviceName{"AudioStereoJackOutput"}, | ||
| 24 | AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"}, | ||
| 25 | AudioDevice::AudioDeviceName{"AudioTvOutput"}, | ||
| 26 | }; | ||
| 27 | |||
| 28 | constexpr std::array output_device_names{ | ||
| 29 | AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"}, | ||
| 30 | AudioDevice::AudioDeviceName{"AudioTvOutput"}, | ||
| 31 | AudioDevice::AudioDeviceName{"AudioExternalOutput"}, | ||
| 32 | }; | ||
| 33 | |||
| 12 | AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_, | 34 | AudioDevice::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 | ||
| 17 | u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, | 39 | u32 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 | ||
| 34 | u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, | 56 | u32 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 | ||
| 48 | f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) { | 70 | f32 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 { | |||
| 23 | class AudioDevice { | 23 | class AudioDevice { |
| 24 | public: | 24 | public: |
| 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 | ||
| 78 | private: | 70 | private: |
| 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 | ||
| 37 | void BehaviorInfo::AppendError(ErrorInfo& error) { | 37 | void 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 | ||
| 45 | void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) { | 45 | void 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 | ||
| 55 | void BehaviorInfo::UpdateFlags(const Flags flags_) { | 57 | void 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 | ||
| 488 | Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) { | 488 | Result 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 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::AudioRenderer { |
| 13 | 13 | ||
| 14 | static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params, | 14 | static 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 | ||
| 34 | static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params, | 34 | static 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 | ||
| 45 | static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params, | 45 | static 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 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace 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 | */ | ||
| 21 | template <size_t Q> | 11 | template <size_t Q> |
| 22 | s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, | 12 | s32 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 | ||
| 43 | template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, const f32, const f32, | 33 | template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32); |
| 44 | const u32); | 34 | template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32); |
| 45 | template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, const f32, const f32, | ||
| 46 | const u32); | ||
| 47 | 35 | ||
| 48 | void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { | 36 | void 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 | */ |
| 69 | template <size_t Q> | 69 | template <size_t Q> |
| 70 | s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, | 70 | s32 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 | ||
| 51 | bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { | 55 | bool 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 { | |||
| 15 | public: | 15 | public: |
| 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 | ||
| 17 | namespace AudioCore::AudioRenderer { | 17 | namespace AudioCore::AudioRenderer { |
| 18 | constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL}; | 18 | constexpr std::chrono::nanoseconds RENDER_TIME{5'000'000UL}; |
| 19 | constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL}; | ||
| 20 | 19 | ||
| 21 | SystemManager::SystemManager(Core::System& core_) | 20 | SystemManager::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 | ||
| 30 | SystemManager::~SystemManager() { | 27 | SystemManager::~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 | ||
| 123 | std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) { | 120 | std::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 | |||
| 155 | void 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 | ||
| 339 | private: | 158 | private: |
| 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 | ||
| 521 | CubebSink::CubebSink(std::string_view target_device_name) { | 208 | CubebSink::CubebSink(std::string_view target_device_name) { |
| @@ -569,15 +256,15 @@ CubebSink::~CubebSink() { | |||
| 569 | #endif | 256 | #endif |
| 570 | } | 257 | } |
| 571 | 258 | ||
| 572 | SinkStream* CubebSink::AcquireSinkStream(Core::System& system, const u32 system_channels, | 259 | SinkStream* 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 | ||
| 580 | void CubebSink::CloseStream(const SinkStream* stream) { | 267 | void 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 | ||
| 594 | void CubebSink::PauseStreams() { | ||
| 595 | for (auto& stream : sink_streams) { | ||
| 596 | stream->Stop(); | ||
| 597 | } | ||
| 598 | } | ||
| 599 | |||
| 600 | void CubebSink::UnpauseStreams() { | ||
| 601 | for (auto& stream : sink_streams) { | ||
| 602 | stream->Start(true); | ||
| 603 | } | ||
| 604 | } | ||
| 605 | |||
| 606 | f32 CubebSink::GetDeviceVolume() const { | 281 | f32 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 | ||
| 614 | void CubebSink::SetDeviceVolume(const f32 volume) { | 289 | void 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 | ||
| 620 | void CubebSink::SetSystemVolume(const f32 volume) { | 295 | void 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 | ||
| 626 | std::vector<std::string> ListCubebSinkDevices(const bool capture) { | 301 | std::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 | ||
| 13 | namespace Core { | ||
| 14 | class System; | ||
| 15 | } // namespace Core | ||
| 16 | |||
| 9 | namespace AudioCore::Sink { | 17 | namespace AudioCore::Sink { |
| 18 | class NullSinkStreamImpl final : public SinkStream { | ||
| 19 | public: | ||
| 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 | ||
| 35 | private: | 53 | private: |
| 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 | ||
| 309 | private: | 131 | private: |
| 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 | ||
| 466 | SDLSink::SDLSink(std::string_view target_device_name) { | 165 | SDLSink::SDLSink(std::string_view target_device_name) { |
| @@ -482,14 +181,14 @@ SDLSink::SDLSink(std::string_view target_device_name) { | |||
| 482 | 181 | ||
| 483 | SDLSink::~SDLSink() = default; | 182 | SDLSink::~SDLSink() = default; |
| 484 | 183 | ||
| 485 | SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels, | 184 | SinkStream* 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 | ||
| 492 | void SDLSink::CloseStream(const SinkStream* stream) { | 191 | void 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 | ||
| 506 | void SDLSink::PauseStreams() { | ||
| 507 | for (auto& stream : sink_streams) { | ||
| 508 | stream->Stop(); | ||
| 509 | } | ||
| 510 | } | ||
| 511 | |||
| 512 | void SDLSink::UnpauseStreams() { | ||
| 513 | for (auto& stream : sink_streams) { | ||
| 514 | stream->Start(); | ||
| 515 | } | ||
| 516 | } | ||
| 517 | |||
| 518 | f32 SDLSink::GetDeviceVolume() const { | 205 | f32 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 | ||
| 526 | void SDLSink::SetDeviceVolume(const f32 volume) { | 213 | void 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 | ||
| 532 | void SDLSink::SetSystemVolume(const f32 volume) { | 219 | void 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 | ||
| 538 | std::vector<std::string> ListSDLSinkDevices(const bool capture) { | 225 | std::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 | ||
| 18 | namespace AudioCore::Sink { | 19 | namespace 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 | |||
| 18 | namespace AudioCore::Sink { | ||
| 19 | |||
| 20 | void 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 | |||
| 108 | std::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 | |||
| 131 | void 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 | |||
| 140 | void 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 | |||
| 196 | void 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 | |||
| 268 | void SinkStream::Stall() { | ||
| 269 | if (stalled) { | ||
| 270 | return; | ||
| 271 | } | ||
| 272 | stalled = true; | ||
| 273 | system.StallProcesses(); | ||
| 274 | } | ||
| 275 | |||
| 276 | void 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 | |||
| 17 | namespace Core { | ||
| 18 | class System; | ||
| 19 | } // namespace Core | ||
| 12 | 20 | ||
| 13 | namespace AudioCore::Sink { | 21 | namespace 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 | */ |
| 43 | class SinkStream { | 52 | class SinkStream { |
| 44 | public: | 53 | public: |
| 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 | |||
| 203 | protected: | 212 | protected: |
| 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 | |||
| 226 | private: | ||
| 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 | ||
| 222 | using SinkStreamPtr = std::unique_ptr<SinkStream>; | 247 | using 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}) |
| 15 | endif () | 15 | endif () |
| 16 | 16 | ||
| 17 | # Pass the path to git to the GenerateSCMRev.cmake as well | 17 | include(GenerateSCMRev) |
| 18 | find_package(Git QUIET) | ||
| 19 | |||
| 20 | add_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 | ||
| 44 | add_library(common STATIC | 19 | add_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) | ||
| 169 | endif() | 145 | endif() |
| 170 | 146 | ||
| 171 | if (MSVC) | 147 | if (MSVC) |
| @@ -189,7 +165,7 @@ endif() | |||
| 189 | create_target_directory_groups(common) | 165 | create_target_directory_groups(common) |
| 190 | 166 | ||
| 191 | target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads) | 167 | target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads) |
| 192 | target_link_libraries(common PRIVATE lz4::lz4 xbyak) | 168 | target_link_libraries(common PRIVATE lz4::lz4) |
| 193 | if (TARGET zstd::zstd) | 169 | if (TARGET zstd::zstd) |
| 194 | target_link_libraries(common PRIVATE zstd::zstd) | 170 | target_link_libraries(common PRIVATE zstd::zstd) |
| 195 | else() | 171 | else() |
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 | |||
| 57 | private: | 61 | private: |
| 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 806e7ff6c..c17662323 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -4,12 +4,6 @@ | |||
| 4 | add_library(core STATIC | 4 | add_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 |
| @@ -525,6 +521,9 @@ add_library(core STATIC | |||
| 525 | hle/service/ncm/ncm.h | 521 | hle/service/ncm/ncm.h |
| 526 | hle/service/nfc/nfc.cpp | 522 | hle/service/nfc/nfc.cpp |
| 527 | hle/service/nfc/nfc.h | 523 | hle/service/nfc/nfc.h |
| 524 | hle/service/nfp/amiibo_crypto.cpp | ||
| 525 | hle/service/nfp/amiibo_crypto.h | ||
| 526 | hle/service/nfp/amiibo_types.h | ||
| 528 | hle/service/nfp/nfp.cpp | 527 | hle/service/nfp/nfp.cpp |
| 529 | hle/service/nfp/nfp.h | 528 | hle/service/nfp/nfp.h |
| 530 | hle/service/nfp/nfp_user.cpp | 529 | 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 | ||
| 95 | void CoreTiming::SyncPause(bool is_paused) { | 90 | void 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 | ||
| 119 | bool CoreTiming::IsRunning() const { | 110 | bool 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 | ||
| 155 | void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, | 150 | void 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 | ||
| 222 | void CoreTiming::RegisterPauseCallback(PauseCallback&& callback) { | ||
| 223 | std::scoped_lock lock{basic_lock}; | ||
| 224 | pause_callbacks.emplace_back(std::move(callback)); | ||
| 225 | } | ||
| 226 | |||
| 227 | std::optional<s64> CoreTiming::Advance() { | 217 | std::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. |
| 23 | using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>( | 23 | using 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)>; |
| 25 | using PauseCallback = std::function<void(bool paused)>; | ||
| 26 | 25 | ||
| 27 | /// Contains the characteristics of a particular event. | 26 | /// Contains the characteristics of a particular event. |
| 28 | struct EventType { | 27 | struct 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 | |||
| 140 | private: | 136 | private: |
| 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/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 | }; |
| 134 | static_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 | ||
| 34 | struct MiiEditCharInfo { | 34 | struct MiiEditCharInfo { |
| 35 | Service::Mii::MiiInfo mii_info{}; | 35 | Service::Mii::CharInfo mii_info{}; |
| 36 | }; | 36 | }; |
| 37 | static_assert(sizeof(MiiEditCharInfo) == 0x58, "MiiEditCharInfo has incorrect size."); | 37 | static_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 | ||
| 258 | void HwOpus::GetWorkBufferSizeForMultiStreamEx(Kernel::HLERequestContext& ctx) { | ||
| 259 | OpusMultiStreamParametersEx param; | ||
| 260 | std::memcpy(¶m, 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 | |||
| 258 | void HwOpus::OpenHardwareOpusDecoder(Kernel::HLERequestContext& ctx) { | 284 | void 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 | ||
| 12 | namespace Service::Audio { | 12 | namespace Service::Audio { |
| 13 | 13 | ||
| 14 | struct 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 | |||
| 14 | class HwOpus final : public ServiceFramework<HwOpus> { | 24 | class HwOpus final : public ServiceFramework<HwOpus> { |
| 15 | public: | 25 | public: |
| 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 | |||
| 13 | namespace Service::HID { | ||
| 14 | |||
| 15 | Controller_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 | |||
| 22 | Controller_Palma::~Controller_Palma() = default; | ||
| 23 | |||
| 24 | void Controller_Palma::OnInit() {} | ||
| 25 | |||
| 26 | void Controller_Palma::OnRelease() {} | ||
| 27 | |||
| 28 | void Controller_Palma::OnUpdate(const Core::Timing::CoreTiming& core_timing) { | ||
| 29 | if (!IsControllerActivated()) { | ||
| 30 | return; | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | Result 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 | |||
| 41 | Result 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 | |||
| 49 | Kernel::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 | |||
| 57 | Result 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 | |||
| 68 | Result 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 | |||
| 80 | Result 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 | |||
| 89 | Result 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 | |||
| 100 | Result 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 | |||
| 107 | Result Controller_Palma::ResetPalmaStep(const PalmaConnectionHandle& handle) { | ||
| 108 | if (handle.npad_id != active_handle.npad_id) { | ||
| 109 | return InvalidPalmaHandle; | ||
| 110 | } | ||
| 111 | return ResultSuccess; | ||
| 112 | } | ||
| 113 | |||
| 114 | void Controller_Palma::ReadPalmaApplicationSection() {} | ||
| 115 | |||
| 116 | void Controller_Palma::WritePalmaApplicationSection() {} | ||
| 117 | |||
| 118 | Result 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 | |||
| 129 | Result 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 | |||
| 140 | void Controller_Palma::WritePalmaActivityEntry() {} | ||
| 141 | |||
| 142 | Result 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 | |||
| 154 | Result 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 | |||
| 166 | Result 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 | |||
| 179 | Result 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 | |||
| 192 | void Controller_Palma::SuspendPalmaFeature() {} | ||
| 193 | |||
| 194 | Result 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 | } | ||
| 200 | void Controller_Palma::ReadPalmaPlayLog() {} | ||
| 201 | |||
| 202 | void Controller_Palma::ResetPalmaPlayLog() {} | ||
| 203 | |||
| 204 | void Controller_Palma::SetIsPalmaAllConnectable(bool is_all_connectable) { | ||
| 205 | // If true controllers are able to be paired | ||
| 206 | is_connectable = is_all_connectable; | ||
| 207 | } | ||
| 208 | |||
| 209 | void Controller_Palma::SetIsPalmaPairedConnectable() {} | ||
| 210 | |||
| 211 | Result 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 | |||
| 219 | void Controller_Palma::SetPalmaBoostMode(bool boost_mode) {} | ||
| 220 | |||
| 221 | void Controller_Palma::CancelWritePalmaWaveEntry() {} | ||
| 222 | |||
| 223 | void Controller_Palma::EnablePalmaBoostMode() {} | ||
| 224 | |||
| 225 | void Controller_Palma::GetPalmaBluetoothAddress() {} | ||
| 226 | |||
| 227 | void 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 | |||
| 12 | namespace Kernel { | ||
| 13 | class KEvent; | ||
| 14 | class KReadableEvent; | ||
| 15 | } // namespace Kernel | ||
| 16 | |||
| 17 | namespace Service::KernelHelpers { | ||
| 18 | class ServiceContext; | ||
| 19 | } | ||
| 20 | |||
| 21 | namespace Core::HID { | ||
| 22 | class EmulatedController; | ||
| 23 | } // namespace Core::HID | ||
| 24 | |||
| 25 | namespace Service::HID { | ||
| 26 | class Controller_Palma final : public ControllerBase { | ||
| 27 | public: | ||
| 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 | |||
| 138 | private: | ||
| 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 | ||
| 8 | namespace Service::HID { | 8 | namespace Service::HID { |
| 9 | 9 | ||
| 10 | constexpr Result PalmaResultSuccess{ErrorModule::HID, 0}; | ||
| 10 | constexpr Result NpadInvalidHandle{ErrorModule::HID, 100}; | 11 | constexpr Result NpadInvalidHandle{ErrorModule::HID, 100}; |
| 11 | constexpr Result NpadDeviceIndexOutOfRange{ErrorModule::HID, 107}; | 12 | constexpr Result NpadDeviceIndexOutOfRange{ErrorModule::HID, 107}; |
| 12 | constexpr Result VibrationInvalidStyleIndex{ErrorModule::HID, 122}; | 13 | constexpr Result VibrationInvalidStyleIndex{ErrorModule::HID, 122}; |
| @@ -17,6 +18,7 @@ constexpr Result NpadIsDualJoycon{ErrorModule::HID, 601}; | |||
| 17 | constexpr Result NpadIsSameType{ErrorModule::HID, 602}; | 18 | constexpr Result NpadIsSameType{ErrorModule::HID, 602}; |
| 18 | constexpr Result InvalidNpadId{ErrorModule::HID, 709}; | 19 | constexpr Result InvalidNpadId{ErrorModule::HID, 709}; |
| 19 | constexpr Result NpadNotConnected{ErrorModule::HID, 710}; | 20 | constexpr Result NpadNotConnected{ErrorModule::HID, 710}; |
| 21 | constexpr 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 3d3457160..de3fae2cb 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" |
| @@ -60,6 +61,7 @@ IAppletResource::IAppletResource(Core::System& system_, | |||
| 60 | MakeControllerWithServiceContext<Controller_NPad>(HidController::NPad, shared_memory); | 61 | MakeControllerWithServiceContext<Controller_NPad>(HidController::NPad, shared_memory); |
| 61 | MakeController<Controller_Gesture>(HidController::Gesture, shared_memory); | 62 | MakeController<Controller_Gesture>(HidController::Gesture, shared_memory); |
| 62 | MakeController<Controller_ConsoleSixAxis>(HidController::ConsoleSixAxisSensor, shared_memory); | 63 | MakeController<Controller_ConsoleSixAxis>(HidController::ConsoleSixAxisSensor, shared_memory); |
| 64 | MakeControllerWithServiceContext<Controller_Palma>(HidController::Palma, shared_memory); | ||
| 63 | 65 | ||
| 64 | // Homebrew doesn't try to activate some controllers, so we activate them by default | 66 | // Homebrew doesn't try to activate some controllers, so we activate them by default |
| 65 | GetController<Controller_NPad>(HidController::NPad).ActivateController(); | 67 | GetController<Controller_NPad>(HidController::NPad).ActivateController(); |
| @@ -310,36 +312,36 @@ Hid::Hid(Core::System& system_) | |||
| 310 | {406, nullptr, "GetNpadLeftRightInterfaceType"}, | 312 | {406, nullptr, "GetNpadLeftRightInterfaceType"}, |
| 311 | {407, nullptr, "GetNpadOfHighestBatteryLevel"}, | 313 | {407, nullptr, "GetNpadOfHighestBatteryLevel"}, |
| 312 | {408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"}, | 314 | {408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"}, |
| 313 | {500, nullptr, "GetPalmaConnectionHandle"}, | 315 | {500, &Hid::GetPalmaConnectionHandle, "GetPalmaConnectionHandle"}, |
| 314 | {501, nullptr, "InitializePalma"}, | 316 | {501, &Hid::InitializePalma, "InitializePalma"}, |
| 315 | {502, nullptr, "AcquirePalmaOperationCompleteEvent"}, | 317 | {502, &Hid::AcquirePalmaOperationCompleteEvent, "AcquirePalmaOperationCompleteEvent"}, |
| 316 | {503, nullptr, "GetPalmaOperationInfo"}, | 318 | {503, &Hid::GetPalmaOperationInfo, "GetPalmaOperationInfo"}, |
| 317 | {504, nullptr, "PlayPalmaActivity"}, | 319 | {504, &Hid::PlayPalmaActivity, "PlayPalmaActivity"}, |
| 318 | {505, nullptr, "SetPalmaFrModeType"}, | 320 | {505, &Hid::SetPalmaFrModeType, "SetPalmaFrModeType"}, |
| 319 | {506, nullptr, "ReadPalmaStep"}, | 321 | {506, &Hid::ReadPalmaStep, "ReadPalmaStep"}, |
| 320 | {507, nullptr, "EnablePalmaStep"}, | 322 | {507, &Hid::EnablePalmaStep, "EnablePalmaStep"}, |
| 321 | {508, nullptr, "ResetPalmaStep"}, | 323 | {508, &Hid::ResetPalmaStep, "ResetPalmaStep"}, |
| 322 | {509, nullptr, "ReadPalmaApplicationSection"}, | 324 | {509, &Hid::ReadPalmaApplicationSection, "ReadPalmaApplicationSection"}, |
| 323 | {510, nullptr, "WritePalmaApplicationSection"}, | 325 | {510, &Hid::WritePalmaApplicationSection, "WritePalmaApplicationSection"}, |
| 324 | {511, nullptr, "ReadPalmaUniqueCode"}, | 326 | {511, &Hid::ReadPalmaUniqueCode, "ReadPalmaUniqueCode"}, |
| 325 | {512, nullptr, "SetPalmaUniqueCodeInvalid"}, | 327 | {512, &Hid::SetPalmaUniqueCodeInvalid, "SetPalmaUniqueCodeInvalid"}, |
| 326 | {513, nullptr, "WritePalmaActivityEntry"}, | 328 | {513, &Hid::WritePalmaActivityEntry, "WritePalmaActivityEntry"}, |
| 327 | {514, nullptr, "WritePalmaRgbLedPatternEntry"}, | 329 | {514, &Hid::WritePalmaRgbLedPatternEntry, "WritePalmaRgbLedPatternEntry"}, |
| 328 | {515, nullptr, "WritePalmaWaveEntry"}, | 330 | {515, &Hid::WritePalmaWaveEntry, "WritePalmaWaveEntry"}, |
| 329 | {516, nullptr, "SetPalmaDataBaseIdentificationVersion"}, | 331 | {516, &Hid::SetPalmaDataBaseIdentificationVersion, "SetPalmaDataBaseIdentificationVersion"}, |
| 330 | {517, nullptr, "GetPalmaDataBaseIdentificationVersion"}, | 332 | {517, &Hid::GetPalmaDataBaseIdentificationVersion, "GetPalmaDataBaseIdentificationVersion"}, |
| 331 | {518, nullptr, "SuspendPalmaFeature"}, | 333 | {518, &Hid::SuspendPalmaFeature, "SuspendPalmaFeature"}, |
| 332 | {519, nullptr, "GetPalmaOperationResult"}, | 334 | {519, &Hid::GetPalmaOperationResult, "GetPalmaOperationResult"}, |
| 333 | {520, nullptr, "ReadPalmaPlayLog"}, | 335 | {520, &Hid::ReadPalmaPlayLog, "ReadPalmaPlayLog"}, |
| 334 | {521, nullptr, "ResetPalmaPlayLog"}, | 336 | {521, &Hid::ResetPalmaPlayLog, "ResetPalmaPlayLog"}, |
| 335 | {522, &Hid::SetIsPalmaAllConnectable, "SetIsPalmaAllConnectable"}, | 337 | {522, &Hid::SetIsPalmaAllConnectable, "SetIsPalmaAllConnectable"}, |
| 336 | {523, nullptr, "SetIsPalmaPairedConnectable"}, | 338 | {523, &Hid::SetIsPalmaPairedConnectable, "SetIsPalmaPairedConnectable"}, |
| 337 | {524, nullptr, "PairPalma"}, | 339 | {524, &Hid::PairPalma, "PairPalma"}, |
| 338 | {525, &Hid::SetPalmaBoostMode, "SetPalmaBoostMode"}, | 340 | {525, &Hid::SetPalmaBoostMode, "SetPalmaBoostMode"}, |
| 339 | {526, nullptr, "CancelWritePalmaWaveEntry"}, | 341 | {526, &Hid::CancelWritePalmaWaveEntry, "CancelWritePalmaWaveEntry"}, |
| 340 | {527, nullptr, "EnablePalmaBoostMode"}, | 342 | {527, &Hid::EnablePalmaBoostMode, "EnablePalmaBoostMode"}, |
| 341 | {528, nullptr, "GetPalmaBluetoothAddress"}, | 343 | {528, &Hid::GetPalmaBluetoothAddress, "GetPalmaBluetoothAddress"}, |
| 342 | {529, nullptr, "SetDisallowedPalmaConnection"}, | 344 | {529, &Hid::SetDisallowedPalmaConnection, "SetDisallowedPalmaConnection"}, |
| 343 | {1000, &Hid::SetNpadCommunicationMode, "SetNpadCommunicationMode"}, | 345 | {1000, &Hid::SetNpadCommunicationMode, "SetNpadCommunicationMode"}, |
| 344 | {1001, &Hid::GetNpadCommunicationMode, "GetNpadCommunicationMode"}, | 346 | {1001, &Hid::GetNpadCommunicationMode, "GetNpadCommunicationMode"}, |
| 345 | {1002, &Hid::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"}, | 347 | {1002, &Hid::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"}, |
| @@ -1878,14 +1880,361 @@ void Hid::IsUsbFullKeyControllerEnabled(Kernel::HLERequestContext& ctx) { | |||
| 1878 | rb.Push(false); | 1880 | rb.Push(false); |
| 1879 | } | 1881 | } |
| 1880 | 1882 | ||
| 1883 | void Hid::GetPalmaConnectionHandle(Kernel::HLERequestContext& ctx) { | ||
| 1884 | IPC::RequestParser rp{ctx}; | ||
| 1885 | struct Parameters { | ||
| 1886 | Core::HID::NpadIdType npad_id; | ||
| 1887 | INSERT_PADDING_WORDS_NOINIT(1); | ||
| 1888 | u64 applet_resource_user_id; | ||
| 1889 | }; | ||
| 1890 | static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); | ||
| 1891 | |||
| 1892 | const auto parameters{rp.PopRaw<Parameters>()}; | ||
| 1893 | |||
| 1894 | LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}", | ||
| 1895 | parameters.npad_id, parameters.applet_resource_user_id); | ||
| 1896 | |||
| 1897 | Controller_Palma::PalmaConnectionHandle handle; | ||
| 1898 | auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma); | ||
| 1899 | const auto result = controller.GetPalmaConnectionHandle(parameters.npad_id, handle); | ||
| 1900 | |||
| 1901 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 1902 | rb.Push(result); | ||
| 1903 | rb.PushRaw(handle); | ||
| 1904 | } | ||
| 1905 | |||
| 1906 | void Hid::InitializePalma(Kernel::HLERequestContext& ctx) { | ||
| 1907 | IPC::RequestParser rp{ctx}; | ||
| 1908 | const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()}; | ||
| 1909 | |||
| 1910 | LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id); | ||
| 1911 | |||
| 1912 | auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma); | ||
| 1913 | const auto result = controller.InitializePalma(connection_handle); | ||
| 1914 | |||
| 1915 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 1916 | rb.Push(result); | ||
| 1917 | } | ||
| 1918 | |||
| 1919 | void Hid::AcquirePalmaOperationCompleteEvent(Kernel::HLERequestContext& ctx) { | ||
| 1920 | IPC::RequestParser rp{ctx}; | ||
| 1921 | const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()}; | ||
| 1922 | |||
| 1923 | LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id); | ||
| 1924 | |||
| 1925 | auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma); | ||
| 1926 | |||
| 1927 | IPC::ResponseBuilder rb{ctx, 2, 1}; | ||
| 1928 | rb.Push(ResultSuccess); | ||
| 1929 | rb.PushCopyObjects(controller.AcquirePalmaOperationCompleteEvent(connection_handle)); | ||
| 1930 | } | ||
| 1931 | |||
| 1932 | void Hid::GetPalmaOperationInfo(Kernel::HLERequestContext& ctx) { | ||
| 1933 | IPC::RequestParser rp{ctx}; | ||
| 1934 | const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()}; | ||
| 1935 | |||
| 1936 | LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id); | ||
| 1937 | |||
| 1938 | Controller_Palma::PalmaOperationType operation_type; | ||
| 1939 | Controller_Palma::PalmaOperationData data; | ||
| 1940 | auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma); | ||
| 1941 | const auto result = controller.GetPalmaOperationInfo(connection_handle, operation_type, data); | ||
| 1942 | |||
| 1943 | if (result.IsError()) { | ||
| 1944 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 1945 | rb.Push(result); | ||
| 1946 | } | ||
| 1947 | |||
| 1948 | ctx.WriteBuffer(data); | ||
| 1949 | IPC::ResponseBuilder rb{ctx, 4}; | ||
| 1950 | rb.Push(result); | ||
| 1951 | rb.Push(static_cast<u64>(operation_type)); | ||
| 1952 | } | ||
| 1953 | |||
| 1954 | void Hid::PlayPalmaActivity(Kernel::HLERequestContext& ctx) { | ||
| 1955 | IPC::RequestParser rp{ctx}; | ||
| 1956 | const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()}; | ||
| 1957 | const auto palma_activity{rp.Pop<u64>()}; | ||
| 1958 | |||
| 1959 | LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, palma_activity={}", | ||
| 1960 | connection_handle.npad_id, palma_activity); | ||
| 1961 | |||
| 1962 | auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma); | ||
| 1963 | const auto result = controller.PlayPalmaActivity(connection_handle, palma_activity); | ||
| 1964 | |||
| 1965 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 1966 | rb.Push(result); | ||
| 1967 | } | ||
| 1968 | |||
| 1969 | void Hid::SetPalmaFrModeType(Kernel::HLERequestContext& ctx) { | ||
| 1970 | IPC::RequestParser rp{ctx}; | ||
| 1971 | const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()}; | ||
| 1972 | const auto fr_mode{rp.PopEnum<Controller_Palma::PalmaFrModeType>()}; | ||
| 1973 | |||
| 1974 | LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, fr_mode={}", | ||
| 1975 | connection_handle.npad_id, fr_mode); | ||
| 1976 | |||
| 1977 | auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma); | ||
| 1978 | const auto result = controller.SetPalmaFrModeType(connection_handle, fr_mode); | ||
| 1979 | |||
| 1980 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 1981 | rb.Push(result); | ||
| 1982 | } | ||
| 1983 | |||
| 1984 | void Hid::ReadPalmaStep(Kernel::HLERequestContext& ctx) { | ||
| 1985 | IPC::RequestParser rp{ctx}; | ||
| 1986 | const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()}; | ||
| 1987 | |||
| 1988 | LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id); | ||
| 1989 | |||
| 1990 | auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma); | ||
| 1991 | const auto result = controller.ReadPalmaStep(connection_handle); | ||
| 1992 | |||
| 1993 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 1994 | rb.Push(result); | ||
| 1995 | } | ||
| 1996 | |||
| 1997 | void Hid::EnablePalmaStep(Kernel::HLERequestContext& ctx) { | ||
| 1998 | IPC::RequestParser rp{ctx}; | ||
| 1999 | struct Parameters { | ||
| 2000 | bool is_enabled; | ||
| 2001 | INSERT_PADDING_WORDS_NOINIT(1); | ||
| 2002 | Controller_Palma::PalmaConnectionHandle connection_handle; | ||
| 2003 | }; | ||
| 2004 | static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); | ||
| 2005 | |||
| 2006 | const auto parameters{rp.PopRaw<Parameters>()}; | ||
| 2007 | |||
| 2008 | LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, is_enabled={}", | ||
| 2009 | parameters.connection_handle.npad_id, parameters.is_enabled); | ||
| 2010 | |||
| 2011 | auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma); | ||
| 2012 | const auto result = | ||
| 2013 | controller.EnablePalmaStep(parameters.connection_handle, parameters.is_enabled); | ||
| 2014 | |||
| 2015 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2016 | rb.Push(result); | ||
| 2017 | } | ||
| 2018 | |||
| 2019 | void Hid::ResetPalmaStep(Kernel::HLERequestContext& ctx) { | ||
| 2020 | IPC::RequestParser rp{ctx}; | ||
| 2021 | const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()}; | ||
| 2022 | |||
| 2023 | LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id); | ||
| 2024 | |||
| 2025 | auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma); | ||
| 2026 | const auto result = controller.ResetPalmaStep(connection_handle); | ||
| 2027 | |||
| 2028 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2029 | rb.Push(result); | ||
| 2030 | } | ||
| 2031 | |||
| 2032 | void Hid::ReadPalmaApplicationSection(Kernel::HLERequestContext& ctx) { | ||
| 2033 | LOG_WARNING(Service_HID, "(STUBBED) called"); | ||
| 2034 | |||
| 2035 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2036 | rb.Push(ResultSuccess); | ||
| 2037 | } | ||
| 2038 | |||
| 2039 | void Hid::WritePalmaApplicationSection(Kernel::HLERequestContext& ctx) { | ||
| 2040 | LOG_WARNING(Service_HID, "(STUBBED) called"); | ||
| 2041 | |||
| 2042 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2043 | rb.Push(ResultSuccess); | ||
| 2044 | } | ||
| 2045 | |||
| 2046 | void Hid::ReadPalmaUniqueCode(Kernel::HLERequestContext& ctx) { | ||
| 2047 | IPC::RequestParser rp{ctx}; | ||
| 2048 | const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()}; | ||
| 2049 | |||
| 2050 | LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id); | ||
| 2051 | |||
| 2052 | applet_resource->GetController<Controller_Palma>(HidController::Palma) | ||
| 2053 | .ReadPalmaUniqueCode(connection_handle); | ||
| 2054 | |||
| 2055 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2056 | rb.Push(ResultSuccess); | ||
| 2057 | } | ||
| 2058 | |||
| 2059 | void Hid::SetPalmaUniqueCodeInvalid(Kernel::HLERequestContext& ctx) { | ||
| 2060 | IPC::RequestParser rp{ctx}; | ||
| 2061 | const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()}; | ||
| 2062 | |||
| 2063 | LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id); | ||
| 2064 | |||
| 2065 | applet_resource->GetController<Controller_Palma>(HidController::Palma) | ||
| 2066 | .SetPalmaUniqueCodeInvalid(connection_handle); | ||
| 2067 | |||
| 2068 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2069 | rb.Push(ResultSuccess); | ||
| 2070 | } | ||
| 2071 | |||
| 2072 | void Hid::WritePalmaActivityEntry(Kernel::HLERequestContext& ctx) { | ||
| 2073 | LOG_CRITICAL(Service_HID, "(STUBBED) called"); | ||
| 2074 | |||
| 2075 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2076 | rb.Push(ResultSuccess); | ||
| 2077 | } | ||
| 2078 | |||
| 2079 | void Hid::WritePalmaRgbLedPatternEntry(Kernel::HLERequestContext& ctx) { | ||
| 2080 | IPC::RequestParser rp{ctx}; | ||
| 2081 | const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()}; | ||
| 2082 | const auto unknown{rp.Pop<u64>()}; | ||
| 2083 | |||
| 2084 | const auto buffer = ctx.ReadBuffer(); | ||
| 2085 | |||
| 2086 | LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, unknown={}", | ||
| 2087 | connection_handle.npad_id, unknown); | ||
| 2088 | |||
| 2089 | applet_resource->GetController<Controller_Palma>(HidController::Palma) | ||
| 2090 | .WritePalmaRgbLedPatternEntry(connection_handle, unknown); | ||
| 2091 | |||
| 2092 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2093 | rb.Push(ResultSuccess); | ||
| 2094 | } | ||
| 2095 | |||
| 2096 | void Hid::WritePalmaWaveEntry(Kernel::HLERequestContext& ctx) { | ||
| 2097 | IPC::RequestParser rp{ctx}; | ||
| 2098 | const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()}; | ||
| 2099 | const auto wave_set{rp.PopEnum<Controller_Palma::PalmaWaveSet>()}; | ||
| 2100 | const auto unknown{rp.Pop<u64>()}; | ||
| 2101 | const auto t_mem_size{rp.Pop<u64>()}; | ||
| 2102 | const auto t_mem_handle{ctx.GetCopyHandle(0)}; | ||
| 2103 | const auto size{rp.Pop<u64>()}; | ||
| 2104 | |||
| 2105 | ASSERT_MSG(t_mem_size == 0x3000, "t_mem_size is not 0x3000 bytes"); | ||
| 2106 | |||
| 2107 | auto t_mem = | ||
| 2108 | system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle); | ||
| 2109 | |||
| 2110 | if (t_mem.IsNull()) { | ||
| 2111 | LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle); | ||
| 2112 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2113 | rb.Push(ResultUnknown); | ||
| 2114 | return; | ||
| 2115 | } | ||
| 2116 | |||
| 2117 | ASSERT_MSG(t_mem->GetSize() == 0x3000, "t_mem has incorrect size"); | ||
| 2118 | |||
| 2119 | LOG_WARNING(Service_HID, | ||
| 2120 | "(STUBBED) called, connection_handle={}, wave_set={}, unkown={}, " | ||
| 2121 | "t_mem_handle=0x{:08X}, t_mem_size={}, size={}", | ||
| 2122 | connection_handle.npad_id, wave_set, unknown, t_mem_handle, t_mem_size, size); | ||
| 2123 | |||
| 2124 | applet_resource->GetController<Controller_Palma>(HidController::Palma) | ||
| 2125 | .WritePalmaWaveEntry(connection_handle, wave_set, | ||
| 2126 | system.Memory().GetPointer(t_mem->GetSourceAddress()), t_mem_size); | ||
| 2127 | |||
| 2128 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2129 | rb.Push(ResultSuccess); | ||
| 2130 | } | ||
| 2131 | |||
| 2132 | void Hid::SetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx) { | ||
| 2133 | IPC::RequestParser rp{ctx}; | ||
| 2134 | struct Parameters { | ||
| 2135 | s32 database_id_version; | ||
| 2136 | INSERT_PADDING_WORDS_NOINIT(1); | ||
| 2137 | Controller_Palma::PalmaConnectionHandle connection_handle; | ||
| 2138 | }; | ||
| 2139 | static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); | ||
| 2140 | |||
| 2141 | const auto parameters{rp.PopRaw<Parameters>()}; | ||
| 2142 | |||
| 2143 | LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, database_id_version={}", | ||
| 2144 | parameters.connection_handle.npad_id, parameters.database_id_version); | ||
| 2145 | |||
| 2146 | applet_resource->GetController<Controller_Palma>(HidController::Palma) | ||
| 2147 | .SetPalmaDataBaseIdentificationVersion(parameters.connection_handle, | ||
| 2148 | parameters.database_id_version); | ||
| 2149 | |||
| 2150 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2151 | rb.Push(ResultSuccess); | ||
| 2152 | } | ||
| 2153 | |||
| 2154 | void Hid::GetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx) { | ||
| 2155 | IPC::RequestParser rp{ctx}; | ||
| 2156 | const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()}; | ||
| 2157 | |||
| 2158 | LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id); | ||
| 2159 | |||
| 2160 | applet_resource->GetController<Controller_Palma>(HidController::Palma) | ||
| 2161 | .GetPalmaDataBaseIdentificationVersion(connection_handle); | ||
| 2162 | |||
| 2163 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2164 | rb.Push(ResultSuccess); | ||
| 2165 | } | ||
| 2166 | |||
| 2167 | void Hid::SuspendPalmaFeature(Kernel::HLERequestContext& ctx) { | ||
| 2168 | LOG_WARNING(Service_HID, "(STUBBED) called"); | ||
| 2169 | |||
| 2170 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2171 | rb.Push(ResultSuccess); | ||
| 2172 | } | ||
| 2173 | |||
| 2174 | void Hid::GetPalmaOperationResult(Kernel::HLERequestContext& ctx) { | ||
| 2175 | IPC::RequestParser rp{ctx}; | ||
| 2176 | const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()}; | ||
| 2177 | |||
| 2178 | LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id); | ||
| 2179 | |||
| 2180 | const auto result = applet_resource->GetController<Controller_Palma>(HidController::Palma) | ||
| 2181 | .GetPalmaOperationResult(connection_handle); | ||
| 2182 | |||
| 2183 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2184 | rb.Push(result); | ||
| 2185 | } | ||
| 2186 | |||
| 2187 | void Hid::ReadPalmaPlayLog(Kernel::HLERequestContext& ctx) { | ||
| 2188 | LOG_WARNING(Service_HID, "(STUBBED) called"); | ||
| 2189 | |||
| 2190 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2191 | rb.Push(ResultSuccess); | ||
| 2192 | } | ||
| 2193 | |||
| 2194 | void Hid::ResetPalmaPlayLog(Kernel::HLERequestContext& ctx) { | ||
| 2195 | LOG_WARNING(Service_HID, "(STUBBED) called"); | ||
| 2196 | |||
| 2197 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2198 | rb.Push(ResultSuccess); | ||
| 2199 | } | ||
| 2200 | |||
| 1881 | void Hid::SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) { | 2201 | void Hid::SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) { |
| 1882 | IPC::RequestParser rp{ctx}; | 2202 | IPC::RequestParser rp{ctx}; |
| 1883 | const auto applet_resource_user_id{rp.Pop<u64>()}; | 2203 | struct Parameters { |
| 1884 | const auto is_palma_all_connectable{rp.Pop<bool>()}; | 2204 | bool is_palma_all_connectable; |
| 2205 | INSERT_PADDING_BYTES_NOINIT(7); | ||
| 2206 | u64 applet_resource_user_id; | ||
| 2207 | }; | ||
| 2208 | static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size."); | ||
| 2209 | |||
| 2210 | const auto parameters{rp.PopRaw<Parameters>()}; | ||
| 1885 | 2211 | ||
| 1886 | LOG_WARNING(Service_HID, | 2212 | LOG_WARNING(Service_HID, |
| 1887 | "(STUBBED) called, applet_resource_user_id={}, is_palma_all_connectable={}", | 2213 | "(STUBBED) called, is_palma_all_connectable={},applet_resource_user_id={}", |
| 1888 | applet_resource_user_id, is_palma_all_connectable); | 2214 | parameters.is_palma_all_connectable, parameters.applet_resource_user_id); |
| 2215 | |||
| 2216 | applet_resource->GetController<Controller_Palma>(HidController::Palma) | ||
| 2217 | .SetIsPalmaAllConnectable(parameters.is_palma_all_connectable); | ||
| 2218 | |||
| 2219 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2220 | rb.Push(ResultSuccess); | ||
| 2221 | } | ||
| 2222 | |||
| 2223 | void Hid::SetIsPalmaPairedConnectable(Kernel::HLERequestContext& ctx) { | ||
| 2224 | LOG_WARNING(Service_HID, "(STUBBED) called"); | ||
| 2225 | |||
| 2226 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2227 | rb.Push(ResultSuccess); | ||
| 2228 | } | ||
| 2229 | |||
| 2230 | void Hid::PairPalma(Kernel::HLERequestContext& ctx) { | ||
| 2231 | IPC::RequestParser rp{ctx}; | ||
| 2232 | const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()}; | ||
| 2233 | |||
| 2234 | LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id); | ||
| 2235 | |||
| 2236 | applet_resource->GetController<Controller_Palma>(HidController::Palma) | ||
| 2237 | .PairPalma(connection_handle); | ||
| 1889 | 2238 | ||
| 1890 | IPC::ResponseBuilder rb{ctx, 2}; | 2239 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1891 | rb.Push(ResultSuccess); | 2240 | rb.Push(ResultSuccess); |
| @@ -1897,6 +2246,37 @@ void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) { | |||
| 1897 | 2246 | ||
| 1898 | LOG_WARNING(Service_HID, "(STUBBED) called, palma_boost_mode={}", palma_boost_mode); | 2247 | LOG_WARNING(Service_HID, "(STUBBED) called, palma_boost_mode={}", palma_boost_mode); |
| 1899 | 2248 | ||
| 2249 | applet_resource->GetController<Controller_Palma>(HidController::Palma) | ||
| 2250 | .SetPalmaBoostMode(palma_boost_mode); | ||
| 2251 | |||
| 2252 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2253 | rb.Push(ResultSuccess); | ||
| 2254 | } | ||
| 2255 | |||
| 2256 | void Hid::CancelWritePalmaWaveEntry(Kernel::HLERequestContext& ctx) { | ||
| 2257 | LOG_WARNING(Service_HID, "(STUBBED) called"); | ||
| 2258 | |||
| 2259 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2260 | rb.Push(ResultSuccess); | ||
| 2261 | } | ||
| 2262 | |||
| 2263 | void Hid::EnablePalmaBoostMode(Kernel::HLERequestContext& ctx) { | ||
| 2264 | LOG_WARNING(Service_HID, "(STUBBED) called"); | ||
| 2265 | |||
| 2266 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2267 | rb.Push(ResultSuccess); | ||
| 2268 | } | ||
| 2269 | |||
| 2270 | void Hid::GetPalmaBluetoothAddress(Kernel::HLERequestContext& ctx) { | ||
| 2271 | LOG_WARNING(Service_HID, "(STUBBED) called"); | ||
| 2272 | |||
| 2273 | IPC::ResponseBuilder rb{ctx, 2}; | ||
| 2274 | rb.Push(ResultSuccess); | ||
| 2275 | } | ||
| 2276 | |||
| 2277 | void Hid::SetDisallowedPalmaConnection(Kernel::HLERequestContext& ctx) { | ||
| 2278 | LOG_WARNING(Service_HID, "(STUBBED) called"); | ||
| 2279 | |||
| 1900 | IPC::ResponseBuilder rb{ctx, 2}; | 2280 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1901 | rb.Push(ResultSuccess); | 2281 | rb.Push(ResultSuccess); |
| 1902 | } | 2282 | } |
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 | ||
| 543 | Core::IrSensor::DeviceFormat& IRS::GetIrCameraSharedMemoryDeviceEntry( | 543 | Core::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 | ||
| 45 | MiiInfo ConvertStoreDataToInfo(const MiiStoreData& data) { | 45 | CharInfo 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 | ||
| 412 | ResultVal<MiiInfo> MiiManager::UpdateLatest([[maybe_unused]] const MiiInfo& info, | 412 | ResultVal<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 | ||
| 422 | MiiInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { | 422 | CharInfo 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 | ||
| 426 | MiiInfo MiiManager::BuildDefault(std::size_t index) { | 426 | CharInfo 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 | ||
| 430 | CharInfo 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 | |||
| 430 | ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) { | 507 | ResultVal<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 | ||
| 444 | Result MiiManager::GetIndex([[maybe_unused]] const MiiInfo& info, u32& index) { | 521 | Result 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 | ||
| 28 | private: | 29 | private: |
| 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 | }; |
| 87 | DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); | 87 | DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); |
| 88 | 88 | ||
| 89 | struct MiiInfo { | 89 | // nn::mii::CharInfo |
| 90 | struct 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 | }; |
| 143 | static_assert(sizeof(MiiInfo) == 0x58, "MiiInfo has incorrect size."); | 144 | static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size."); |
| 144 | static_assert(std::has_unique_object_representations_v<MiiInfo>, | 145 | static_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 | ||
| 149 | struct MiiInfoElement { | 150 | struct 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 | }; |
| 155 | static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size."); | 156 | static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size."); |
| @@ -243,6 +244,131 @@ static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrec | |||
| 243 | static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>, | 244 | static_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 | ||
| 251 | struct 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 | }; | ||
| 370 | static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size"); | ||
| 371 | |||
| 246 | struct MiiStoreData { | 372 | struct 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 | |||
| 17 | namespace Service::NFP::AmiiboCrypto { | ||
| 18 | |||
| 19 | bool 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 | |||
| 70 | NTAG215File 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 | |||
| 99 | EncryptedNTAG215File 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 | |||
| 128 | u32 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 | |||
| 137 | HashSeed 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 | |||
| 153 | std::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 | |||
| 178 | void 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 | |||
| 193 | void 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 | |||
| 212 | DerivedKeys 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 | |||
| 236 | void 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 | |||
| 269 | bool 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 | |||
| 293 | bool 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 | |||
| 335 | bool 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 | |||
| 10 | struct mbedtls_md_context_t; | ||
| 11 | |||
| 12 | namespace Service::NFP::AmiiboCrypto { | ||
| 13 | // Byte locations in Service::NFP::NTAG215File | ||
| 14 | constexpr std::size_t HMAC_DATA_START = 0x8; | ||
| 15 | constexpr std::size_t SETTINGS_START = 0x2c; | ||
| 16 | constexpr std::size_t WRITE_COUNTER_START = 0x29; | ||
| 17 | constexpr std::size_t HMAC_TAG_START = 0x1B4; | ||
| 18 | constexpr std::size_t UUID_START = 0x1D4; | ||
| 19 | constexpr std::size_t DYNAMIC_LOCK_START = 0x208; | ||
| 20 | |||
| 21 | using HmacKey = std::array<u8, 0x10>; | ||
| 22 | using DrgbOutput = std::array<u8, 0x20>; | ||
| 23 | |||
| 24 | struct 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 | }; | ||
| 31 | static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size"); | ||
| 32 | |||
| 33 | struct 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 | }; | ||
| 41 | static_assert(sizeof(InternalKey) == 0x50, "InternalKey is an invalid size"); | ||
| 42 | static_assert(std::is_trivially_copyable_v<InternalKey>, "InternalKey must be trivially copyable."); | ||
| 43 | |||
| 44 | struct CryptoCtx { | ||
| 45 | std::array<char, 480> buffer; | ||
| 46 | bool used; | ||
| 47 | std::size_t buffer_size; | ||
| 48 | s16 counter; | ||
| 49 | }; | ||
| 50 | |||
| 51 | struct DerivedKeys { | ||
| 52 | std::array<u8, 0x10> aes_key; | ||
| 53 | std::array<u8, 0x10> aes_iv; | ||
| 54 | std::array<u8, 0x10> hmac_key; | ||
| 55 | }; | ||
| 56 | static_assert(sizeof(DerivedKeys) == 0x30, "DerivedKeys is an invalid size"); | ||
| 57 | |||
| 58 | /// Validates that the amiibo file is not corrupted | ||
| 59 | bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file); | ||
| 60 | |||
| 61 | /// Converts from encrypted file format to encoded file format | ||
| 62 | NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data); | ||
| 63 | |||
| 64 | /// Converts from encoded file format to encrypted file format | ||
| 65 | EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data); | ||
| 66 | |||
| 67 | /// Returns password needed to allow write access to protected memory | ||
| 68 | u32 GetTagPassword(const TagUuid& uuid); | ||
| 69 | |||
| 70 | // Generates Seed needed for key derivation | ||
| 71 | HashSeed GetSeed(const NTAG215File& data); | ||
| 72 | |||
| 73 | // Middle step on the generation of derived keys | ||
| 74 | std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed); | ||
| 75 | |||
| 76 | // Initializes mbedtls context | ||
| 77 | void 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 | ||
| 81 | void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output); | ||
| 82 | |||
| 83 | // Generates the derived key from amiibo data | ||
| 84 | DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data); | ||
| 85 | |||
| 86 | // Encodes or decodes amiibo data | ||
| 87 | void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data); | ||
| 88 | |||
| 89 | /// Loads both amiibo keys from key_retail.bin | ||
| 90 | bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info); | ||
| 91 | |||
| 92 | /// Decodes encripted amiibo data returns true if output is valid | ||
| 93 | bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data); | ||
| 94 | |||
| 95 | /// Encodes plain amiibo data returns true if output is valid | ||
| 96 | bool 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 | |||
| 10 | namespace Service::NFP { | ||
| 11 | static constexpr std::size_t amiibo_name_length = 0xA; | ||
| 12 | |||
| 13 | enum class ServiceType : u32 { | ||
| 14 | User, | ||
| 15 | Debug, | ||
| 16 | System, | ||
| 17 | }; | ||
| 18 | |||
| 19 | enum class State : u32 { | ||
| 20 | NonInitialized, | ||
| 21 | Initialized, | ||
| 22 | }; | ||
| 23 | |||
| 24 | enum class DeviceState : u32 { | ||
| 25 | Initialized, | ||
| 26 | SearchingForTag, | ||
| 27 | TagFound, | ||
| 28 | TagRemoved, | ||
| 29 | TagMounted, | ||
| 30 | Unaviable, | ||
| 31 | Finalized, | ||
| 32 | }; | ||
| 33 | |||
| 34 | enum class ModelType : u32 { | ||
| 35 | Amiibo, | ||
| 36 | }; | ||
| 37 | |||
| 38 | enum class MountTarget : u32 { | ||
| 39 | Rom, | ||
| 40 | Ram, | ||
| 41 | All, | ||
| 42 | }; | ||
| 43 | |||
| 44 | enum class AmiiboType : u8 { | ||
| 45 | Figure, | ||
| 46 | Card, | ||
| 47 | Yarn, | ||
| 48 | }; | ||
| 49 | |||
| 50 | enum 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 | |||
| 76 | using TagUuid = std::array<u8, 10>; | ||
| 77 | using HashData = std::array<u8, 0x20>; | ||
| 78 | using ApplicationArea = std::array<u8, 0xD8>; | ||
| 79 | |||
| 80 | struct 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 | }; | ||
| 93 | static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size"); | ||
| 94 | |||
| 95 | struct Settings { | ||
| 96 | union { | ||
| 97 | u8 raw{}; | ||
| 98 | |||
| 99 | BitField<4, 1, u8> amiibo_initialized; | ||
| 100 | BitField<5, 1, u8> appdata_initialized; | ||
| 101 | }; | ||
| 102 | }; | ||
| 103 | static_assert(sizeof(Settings) == 1, "AmiiboDate is an invalid size"); | ||
| 104 | |||
| 105 | struct 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 | }; | ||
| 114 | static_assert(sizeof(AmiiboSettings) == 0x20, "AmiiboSettings is an invalid size"); | ||
| 115 | |||
| 116 | struct 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 | }; | ||
| 125 | static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size"); | ||
| 126 | |||
| 127 | struct NTAG215Password { | ||
| 128 | u32 PWD; // Password to allow write access | ||
| 129 | u16 PACK; // Password acknowledge reply | ||
| 130 | u16 RFUI; // Reserved for future use | ||
| 131 | }; | ||
| 132 | static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size"); | ||
| 133 | |||
| 134 | #pragma pack(1) | ||
| 135 | struct 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 | }; | ||
| 152 | static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size"); | ||
| 153 | |||
| 154 | struct 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 | }; | ||
| 179 | static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size"); | ||
| 180 | static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be trivially copyable."); | ||
| 181 | #pragma pack() | ||
| 182 | |||
| 183 | struct 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 | }; | ||
| 193 | static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an invalid size"); | ||
| 194 | static_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 { | |||
| 19 | namespace ErrCodes { | 23 | namespace ErrCodes { |
| 20 | constexpr Result DeviceNotFound(ErrorModule::NFP, 64); | 24 | constexpr Result DeviceNotFound(ErrorModule::NFP, 64); |
| 21 | constexpr Result WrongDeviceState(ErrorModule::NFP, 73); | 25 | constexpr Result WrongDeviceState(ErrorModule::NFP, 73); |
| 26 | constexpr Result NfcDisabled(ErrorModule::NFP, 80); | ||
| 27 | constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88); | ||
| 28 | constexpr Result TagRemoved(ErrorModule::NFP, 97); | ||
| 22 | constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); | 29 | constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); |
| 30 | constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152); | ||
| 23 | constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168); | 31 | constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168); |
| 24 | } // namespace ErrCodes | 32 | } // namespace ErrCodes |
| 25 | 33 | ||
| 26 | constexpr u32 ApplicationAreaSize = 0xD8; | ||
| 27 | |||
| 28 | IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_) | 34 | IUser::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) { | |||
| 87 | void IUser::ListDevices(Kernel::HLERequestContext& ctx) { | 93 | void 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 | ||
| 306 | void 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 | |||
| 246 | void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) { | 331 | void 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) { | |||
| 455 | void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) { | 588 | void 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 | ||
| 602 | void 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 | |||
| 463 | Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_, | 630 | Module::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 | ||
| 481 | bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) { | 648 | bool 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()) { | 675 | bool 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) { | |||
| 517 | void Module::Interface::CloseAmiibo() { | 690 | void 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 | ||
| 526 | bool 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 | |||
| 569 | Kernel::KReadableEvent& Module::Interface::GetActivateEvent() const { | 700 | Kernel::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 | ||
| 577 | void Module::Interface::Initialize() { | 708 | void 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 | ||
| 581 | void Module::Interface::Finalize() { | 716 | void 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 | ||
| 588 | Result Module::Interface::StartDetection(s32 protocol_) { | 726 | Result 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 | ||
| 621 | Result Module::Interface::Mount() { | 759 | Result 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 | |||
| 815 | Result 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 | ||
| 631 | Result Module::Interface::Unmount() { | 829 | Result 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 | ||
| 644 | Result Module::Interface::GetTagInfo(TagInfo& tag_info) const { | 841 | Result 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 | ||
| 659 | Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const { | 857 | Result 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 | ||
| 687 | Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const { | 907 | Result 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 { | |||
| 707 | Result Module::Interface::OpenApplicationArea(u32 access_id) { | 946 | Result 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 | ||
| 724 | Result Module::Interface::GetApplicationArea(std::vector<u8>& data) const { | 975 | Result 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 { | |||
| 739 | Result Module::Interface::SetApplicationArea(const std::vector<u8>& data) { | 994 | Result 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 | ||
| 753 | Result Module::Interface::CreateApplicationArea(u32 access_id, const std::vector<u8>& data) { | 1017 | Result 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 | ||
| 768 | bool Module::Interface::AmiiboApplicationDataExist(u32 access_id) const { | 1042 | Result 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 | ||
| 773 | std::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 | ||
| 779 | void 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 | ||
| 784 | u64 Module::Interface::GetHandle() const { | 1062 | u64 Module::Interface::GetHandle() const { |
| @@ -791,16 +1069,25 @@ DeviceState Module::Interface::GetCurrentState() const { | |||
| 791 | } | 1069 | } |
| 792 | 1070 | ||
| 793 | Core::HID::NpadIdType Module::Interface::GetNpadId() const { | 1071 | Core::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 | ||
| 797 | u32 Module::Interface::GetTagPassword(const TagUuid& uuid) const { | 1077 | AmiiboName 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 | ||
| 806 | void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { | 1093 | void 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 | ||
| 14 | namespace Kernel { | 15 | namespace Kernel { |
| @@ -21,71 +22,7 @@ enum class NpadIdType : u32; | |||
| 21 | } // namespace Core::HID | 22 | } // namespace Core::HID |
| 22 | 23 | ||
| 23 | namespace Service::NFP { | 24 | namespace Service::NFP { |
| 24 | 25 | using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>; | |
| 25 | enum class ServiceType : u32 { | ||
| 26 | User, | ||
| 27 | Debug, | ||
| 28 | System, | ||
| 29 | }; | ||
| 30 | |||
| 31 | enum class State : u32 { | ||
| 32 | NonInitialized, | ||
| 33 | Initialized, | ||
| 34 | }; | ||
| 35 | |||
| 36 | enum class DeviceState : u32 { | ||
| 37 | Initialized, | ||
| 38 | SearchingForTag, | ||
| 39 | TagFound, | ||
| 40 | TagRemoved, | ||
| 41 | TagMounted, | ||
| 42 | Unaviable, | ||
| 43 | Finalized, | ||
| 44 | }; | ||
| 45 | |||
| 46 | enum class ModelType : u32 { | ||
| 47 | Amiibo, | ||
| 48 | }; | ||
| 49 | |||
| 50 | enum class MountTarget : u32 { | ||
| 51 | Rom, | ||
| 52 | Ram, | ||
| 53 | All, | ||
| 54 | }; | ||
| 55 | |||
| 56 | enum class AmiiboType : u8 { | ||
| 57 | Figure, | ||
| 58 | Card, | ||
| 59 | Yarn, | ||
| 60 | }; | ||
| 61 | |||
| 62 | enum 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 | |||
| 88 | using TagUuid = std::array<u8, 10>; | ||
| 89 | 26 | ||
| 90 | struct TagInfo { | 27 | struct 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 | }; |
| 122 | static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size"); | 57 | static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size"); |
| 123 | 58 | ||
| 124 | struct RegisterInfo { | 59 | struct 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 | }; |
| 133 | static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size"); | 68 | static_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 | ||
| 68 | void nvhost_nvdec::OnOpen(DeviceFD fd) {} | 69 | void nvhost_nvdec::OnOpen(DeviceFD fd) { |
| 70 | LOG_INFO(Service_NVDRV, "NVDEC video stream started"); | ||
| 71 | system.AudioCore().SetNVDECActive(true); | ||
| 72 | } | ||
| 69 | 73 | ||
| 70 | void nvhost_nvdec::OnClose(DeviceFD fd) { | 74 | void 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 | ||
| 87 | NVFlinger::~NVFlinger() { | 92 | NVFlinger::~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 | |||
| 100 | protected: | ||
| 92 | SOCKET fd = INVALID_SOCKET; | 101 | SOCKET fd = INVALID_SOCKET; |
| 93 | }; | 102 | }; |
| 94 | 103 | ||
| 95 | class Socket : public SocketBase { | 104 | class Socket : public SocketBase { |
| 96 | public: | 105 | public: |
| 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 | ||
| 245 | std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file, | 245 | std::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/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 | ||
| 16 | bool RoomNetwork::Init() { | 16 | bool 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 | ||
| 177 | void PrecolorInst(IR::Inst& phi) { | 177 | void 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 | ||
| 103 | void PrecolorInst(IR::Inst& phi) { | 103 | void 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/textures/astc.cpp b/src/video_core/textures/astc.cpp index a4d4e0ed5..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 | ||
| 19 | class InputBitStream { | 21 | class InputBitStream { |
| @@ -1650,29 +1652,41 @@ static void DecompressBlock(std::span<const u8, 16> inBuf, const u32 blockWidth, | |||
| 1650 | 1652 | ||
| 1651 | void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth, | 1653 | void 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 | ||
| 211 | if (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) | ||
| 219 | endif() | ||
| 220 | |||
| 211 | file(GLOB COMPAT_LIST | 221 | file(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 | ||
| 16 | namespace FS = Common::FS; | 16 | namespace FS = Common::FS; |
| 17 | 17 | ||
| 18 | Config::Config(Core::System& system_, const std::string& config_name, ConfigType config_type) | 18 | Config::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 | ||
| 1553 | void Config::Save() { | 1555 | void 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 | ||
| 31 | ConfigureDebug::~ConfigureDebug() = default; | 42 | ConfigureDebug::~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 | ||
| 66 | ConfigureInput::ConfigureInput(Core::System& system_, QWidget* parent) | 66 | ConfigureInput::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 9b4f765ce..9e5a40fe7 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp | |||
| @@ -1417,7 +1417,7 @@ void ConfigureInputPlayer::HandleClick( | |||
| 1417 | ui->controllerFrame->BeginMappingAnalog(button_id); | 1417 | ui->controllerFrame->BeginMappingAnalog(button_id); |
| 1418 | } | 1418 | } |
| 1419 | 1419 | ||
| 1420 | timeout_timer->start(2500); // Cancel after 2.5 seconds | 1420 | timeout_timer->start(4000); // Cancel after 4 seconds |
| 1421 | poll_timer->start(25); // Check for new inputs every 25ms | 1421 | poll_timer->start(25); // Check for new inputs every 25ms |
| 1422 | } | 1422 | } |
| 1423 | 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><html><head/><body><p>Reads controller input from scripts in the same format as TAS-nx scripts.<br/>For a more detailed explanation, please consult the <a href="https://yuzu-emu.org/help/feature/tas/"><span style=" text-decoration: underline; color:#039be5;">help page</span></a> on the yuzu website.</p></body></html></string> | 17 | <string><html><head/><body><p>Reads controller input from scripts in the same format as TAS-nx scripts.<br/>For a more detailed explanation, please consult the <a href="https://yuzu-emu.org/help/feature/tas/"><span style=" text-decoration: underline; color:#039be5;">help page</span></a> on the yuzu website.</p></body></html></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() { | |||
| 128 | void ConfigureWeb::OnLoginChanged() { | 128 | void 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 | ||
| 142 | void ConfigureWeb::VerifyLogin() { | 145 | void 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 | ||
| 30 | InputProfiles::InputProfiles(Core::System& system_) : system{system_} { | 30 | InputProfiles::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; | |||
| 15 | class InputProfiles { | 15 | class InputProfiles { |
| 16 | 16 | ||
| 17 | public: | 17 | public: |
| 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 a85adc072..e2c2b9292 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 | |||
| 141 | using namespace Common::Literals; | 145 | using 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 | ||
| 272 | GMainWindow::GMainWindow(bool has_broken_vulkan) | 276 | GMainWindow::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__ |
| @@ -1637,7 +1640,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t | |||
| 1637 | const auto config_file_name = title_id == 0 | 1640 | const auto config_file_name = title_id == 0 |
| 1638 | ? Common::FS::PathToUTF8String(file_path.filename()) | 1641 | ? Common::FS::PathToUTF8String(file_path.filename()) |
| 1639 | : fmt::format("{:016X}", title_id); | 1642 | : fmt::format("{:016X}", title_id); |
| 1640 | Config per_game_config(*system, config_file_name, Config::ConfigType::PerGameConfig); | 1643 | Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig); |
| 1644 | system->ApplySettings(); | ||
| 1641 | } | 1645 | } |
| 1642 | 1646 | ||
| 1643 | // Save configurations | 1647 | // Save configurations |
| @@ -1996,7 +2000,7 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src | |||
| 1996 | } | 2000 | } |
| 1997 | 2001 | ||
| 1998 | void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) { | 2002 | void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) { |
| 1999 | const QString entry_type = [this, type] { | 2003 | const QString entry_type = [type] { |
| 2000 | switch (type) { | 2004 | switch (type) { |
| 2001 | case InstalledEntryType::Game: | 2005 | case InstalledEntryType::Game: |
| 2002 | return tr("Contents"); | 2006 | return tr("Contents"); |
| @@ -2093,7 +2097,7 @@ void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type) | |||
| 2093 | 2097 | ||
| 2094 | void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, | 2098 | void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, |
| 2095 | const std::string& game_path) { | 2099 | const std::string& game_path) { |
| 2096 | const QString question = [this, target] { | 2100 | const QString question = [target] { |
| 2097 | switch (target) { | 2101 | switch (target) { |
| 2098 | case GameListRemoveTarget::GlShaderCache: | 2102 | case GameListRemoveTarget::GlShaderCache: |
| 2099 | return tr("Delete OpenGL Transferable Shader Cache?"); | 2103 | return tr("Delete OpenGL Transferable Shader Cache?"); |
| @@ -2981,7 +2985,7 @@ void GMainWindow::OnConfigure() { | |||
| 2981 | 2985 | ||
| 2982 | Settings::values.disabled_addons.clear(); | 2986 | Settings::values.disabled_addons.clear(); |
| 2983 | 2987 | ||
| 2984 | config = std::make_unique<Config>(*system); | 2988 | config = std::make_unique<Config>(); |
| 2985 | UISettings::values.reset_to_defaults = false; | 2989 | UISettings::values.reset_to_defaults = false; |
| 2986 | 2990 | ||
| 2987 | UISettings::values.game_dirs = std::move(old_game_dirs); | 2991 | UISettings::values.game_dirs = std::move(old_game_dirs); |
| @@ -3042,6 +3046,7 @@ void GMainWindow::OnConfigure() { | |||
| 3042 | 3046 | ||
| 3043 | UpdateStatusButtons(); | 3047 | UpdateStatusButtons(); |
| 3044 | controller_dialog->refreshConfiguration(); | 3048 | controller_dialog->refreshConfiguration(); |
| 3049 | system->ApplySettings(); | ||
| 3045 | } | 3050 | } |
| 3046 | 3051 | ||
| 3047 | void GMainWindow::OnConfigureTas() { | 3052 | void GMainWindow::OnConfigureTas() { |
| @@ -3254,26 +3259,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) { | |||
| 3254 | return; | 3259 | return; |
| 3255 | } | 3260 | } |
| 3256 | 3261 | ||
| 3257 | QFile nfc_file{filename}; | 3262 | if (!nfc->LoadAmiibo(filename.toStdString())) { |
| 3258 | if (!nfc_file.open(QIODevice::ReadOnly)) { | ||
| 3259 | QMessageBox::warning(this, tr("Error opening Amiibo data file"), | ||
| 3260 | tr("Unable to open Amiibo file \"%1\" for reading.").arg(filename)); | ||
| 3261 | return; | ||
| 3262 | } | ||
| 3263 | |||
| 3264 | const u64 nfc_file_size = nfc_file.size(); | ||
| 3265 | std::vector<u8> buffer(nfc_file_size); | ||
| 3266 | const u64 read_size = nfc_file.read(reinterpret_cast<char*>(buffer.data()), nfc_file_size); | ||
| 3267 | if (nfc_file_size != read_size) { | ||
| 3268 | QMessageBox::warning(this, tr("Error reading Amiibo data file"), | ||
| 3269 | tr("Unable to fully read Amiibo data. Expected to read %1 bytes, but " | ||
| 3270 | "was only able to read %2 bytes.") | ||
| 3271 | .arg(nfc_file_size) | ||
| 3272 | .arg(read_size)); | ||
| 3273 | return; | ||
| 3274 | } | ||
| 3275 | |||
| 3276 | if (!nfc->LoadAmiibo(buffer)) { | ||
| 3277 | QMessageBox::warning(this, tr("Error loading Amiibo data"), | 3263 | QMessageBox::warning(this, tr("Error loading Amiibo data"), |
| 3278 | tr("Unable to load Amiibo data.")); | 3264 | tr("Unable to load Amiibo data.")); |
| 3279 | } | 3265 | } |
| @@ -4082,8 +4068,26 @@ void GMainWindow::changeEvent(QEvent* event) { | |||
| 4082 | #endif | 4068 | #endif |
| 4083 | 4069 | ||
| 4084 | int main(int argc, char* argv[]) { | 4070 | int main(int argc, char* argv[]) { |
| 4071 | std::unique_ptr<Config> config = std::make_unique<Config>(); | ||
| 4085 | bool has_broken_vulkan = false; | 4072 | bool has_broken_vulkan = false; |
| 4086 | if (StartupChecks(argv[0], &has_broken_vulkan)) { | 4073 | bool is_child = false; |
| 4074 | if (CheckEnvVars(&is_child)) { | ||
| 4075 | return 0; | ||
| 4076 | } | ||
| 4077 | |||
| 4078 | #ifdef YUZU_DBGHELP | ||
| 4079 | PROCESS_INFORMATION pi; | ||
| 4080 | if (!is_child && Settings::values.create_crash_dumps.GetValue() && | ||
| 4081 | MiniDump::SpawnDebuggee(argv[0], pi)) { | ||
| 4082 | // Delete the config object so that it doesn't save when the program exits | ||
| 4083 | config.reset(nullptr); | ||
| 4084 | MiniDump::DebugDebuggee(pi); | ||
| 4085 | return 0; | ||
| 4086 | } | ||
| 4087 | #endif | ||
| 4088 | |||
| 4089 | if (StartupChecks(argv[0], &has_broken_vulkan, | ||
| 4090 | Settings::values.perform_vulkan_check.GetValue())) { | ||
| 4087 | return 0; | 4091 | return 0; |
| 4088 | } | 4092 | } |
| 4089 | 4093 | ||
| @@ -4135,7 +4139,7 @@ int main(int argc, char* argv[]) { | |||
| 4135 | // generating shaders | 4139 | // generating shaders |
| 4136 | setlocale(LC_ALL, "C"); | 4140 | setlocale(LC_ALL, "C"); |
| 4137 | 4141 | ||
| 4138 | GMainWindow main_window{has_broken_vulkan}; | 4142 | GMainWindow main_window{std::move(config), has_broken_vulkan}; |
| 4139 | // After settings have been loaded by GMainWindow, apply the filter | 4143 | // After settings have been loaded by GMainWindow, apply the filter |
| 4140 | main_window.show(); | 4144 | main_window.show(); |
| 4141 | 4145 | ||
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 1ae2b93d9..716aef063 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -120,7 +120,7 @@ class GMainWindow : public QMainWindow { | |||
| 120 | public: | 120 | public: |
| 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 | |||
| 16 | namespace MiniDump { | ||
| 17 | |||
| 18 | void 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 | |||
| 49 | void 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 | |||
| 83 | bool 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 | |||
| 107 | static 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 | |||
| 158 | void 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 | |||
| 10 | namespace MiniDump { | ||
| 11 | |||
| 12 | void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, | ||
| 13 | EXCEPTION_POINTERS* pep); | ||
| 14 | |||
| 15 | void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi); | ||
| 16 | bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi); | ||
| 17 | void 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 | ||
| 34 | bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { | 34 | bool 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 | |||
| 60 | bool 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 |
| 109 | bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) { | 131 | bool 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 | ||
| 10 | constexpr char IS_CHILD_ENV_VAR[] = "YUZU_IS_CHILD"; | ||
| 10 | constexpr char STARTUP_CHECK_ENV_VAR[] = "YUZU_DO_STARTUP_CHECKS"; | 11 | constexpr char STARTUP_CHECK_ENV_VAR[] = "YUZU_DO_STARTUP_CHECKS"; |
| 12 | constexpr char ENV_VAR_ENABLED_TEXT[] = "ON"; | ||
| 11 | 13 | ||
| 12 | void CheckVulkan(); | 14 | void CheckVulkan(); |
| 13 | bool StartupChecks(const char* arg0, bool* has_broken_vulkan); | 15 | bool CheckEnvVars(bool* is_child); |
| 16 | bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulkan_check); | ||
| 14 | 17 | ||
| 15 | #ifdef _WIN32 | 18 | #ifdef _WIN32 |
| 16 | bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi); | 19 | bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags); |
| 17 | #endif | 20 | #endif |